Browse Source

New Material icons, reworking main screen design, corrting action bars... WIP

material
Eric Kok 9 years ago
parent
commit
676a21fb92
  1. 18
      app/build.gradle
  2. 2
      app/src/main/java/org/transdroid/core/app/settings/SystemSettings.java
  3. 91
      app/src/main/java/org/transdroid/core/gui/DetailsActivity.java
  4. 202
      app/src/main/java/org/transdroid/core/gui/DetailsFragment.java
  5. 59
      app/src/main/java/org/transdroid/core/gui/ServerSelectionView.java
  6. 39
      app/src/main/java/org/transdroid/core/gui/ServerStatusView.java
  7. 18
      app/src/main/java/org/transdroid/core/gui/TorrentTasksExecutor.java
  8. 267
      app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java
  9. 103
      app/src/main/java/org/transdroid/core/gui/TorrentsFragment.java
  10. 16
      app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemView.java
  11. 45
      app/src/main/java/org/transdroid/core/gui/lists/TorrentDetailsView.java
  12. 13
      app/src/main/java/org/transdroid/core/gui/lists/TorrentFileView.java
  13. 2
      app/src/main/java/org/transdroid/core/gui/lists/TorrentStatusLayout.java
  14. 17
      app/src/main/java/org/transdroid/core/gui/lists/TorrentView.java
  15. 24
      app/src/main/java/org/transdroid/core/gui/lists/TorrentsAdapter.java
  16. 20
      app/src/main/java/org/transdroid/core/gui/navigation/FilterListAdapter.java
  17. 10
      app/src/main/java/org/transdroid/core/gui/navigation/FilterListItemAdapter.java
  18. 15
      app/src/main/java/org/transdroid/core/gui/navigation/FilterListItemView.java
  19. 18
      app/src/main/java/org/transdroid/core/gui/navigation/FilterSeparatorView.java
  20. 38
      app/src/main/java/org/transdroid/core/gui/navigation/Label.java
  21. 14
      app/src/main/java/org/transdroid/core/gui/navigation/NavigationFilter.java
  22. 43
      app/src/main/java/org/transdroid/core/gui/navigation/NavigationHelper.java
  23. 9
      app/src/main/java/org/transdroid/core/gui/navigation/RefreshableActivity.java
  24. 23
      app/src/main/java/org/transdroid/core/gui/rss/RssfeedLoader.java
  25. 30
      app/src/main/java/org/transdroid/core/gui/rss/RssfeedView.java
  26. 57
      app/src/main/java/org/transdroid/core/gui/rss/RssfeedsActivity.java
  27. 24
      app/src/main/java/org/transdroid/core/gui/rss/RssfeedsAdapter.java
  28. 46
      app/src/main/java/org/transdroid/core/gui/rss/RssfeedsFragment.java
  29. 4
      app/src/main/java/org/transdroid/core/gui/rss/RssitemStatusLayout.java
  30. 17
      app/src/main/java/org/transdroid/core/gui/rss/RssitemView.java
  31. 23
      app/src/main/java/org/transdroid/core/gui/rss/RssitemsActivity.java
  32. 22
      app/src/main/java/org/transdroid/core/gui/rss/RssitemsAdapter.java
  33. 21
      app/src/main/java/org/transdroid/core/gui/rss/RssitemsFragment.java
  34. 3
      app/src/main/java/org/transdroid/core/gui/search/BarcodeHelper.java
  35. 32
      app/src/main/java/org/transdroid/core/gui/search/SearchActivity.java
  36. 18
      app/src/main/java/org/transdroid/core/gui/search/SearchResultsAdapter.java
  37. 158
      app/src/main/java/org/transdroid/core/gui/search/SearchResultsFragment.java
  38. 2
      app/src/main/java/org/transdroid/core/gui/search/SearchSetting.java
  39. 9
      app/src/main/java/org/transdroid/core/gui/search/SearchSettingSelectionView.java
  40. 11
      app/src/main/java/org/transdroid/core/gui/search/SearchSettingsDropDownAdapter.java
  41. 20
      app/src/main/java/org/transdroid/core/gui/search/SearchSiteView.java
  42. 24
      app/src/main/java/org/transdroid/core/gui/search/SearchSitesAdapter.java
  43. 60
      app/src/main/java/org/transdroid/core/gui/search/UrlEntryDialog.java
  44. BIN
      app/src/main/res/drawable-hdpi/ic_action_add.png
  45. BIN
      app/src/main/res/drawable-hdpi/ic_action_discard_dark.png
  46. BIN
      app/src/main/res/drawable-hdpi/ic_action_discard_light.png
  47. BIN
      app/src/main/res/drawable-hdpi/ic_action_done.png
  48. BIN
      app/src/main/res/drawable-hdpi/ic_action_done_dark.png
  49. BIN
      app/src/main/res/drawable-hdpi/ic_action_done_light.png
  50. BIN
      app/src/main/res/drawable-hdpi/ic_action_drawer.png
  51. BIN
      app/src/main/res/drawable-hdpi/ic_action_filter_dark.png
  52. BIN
      app/src/main/res/drawable-hdpi/ic_action_filter_light.png
  53. BIN
      app/src/main/res/drawable-hdpi/ic_action_force_recheck.png
  54. BIN
      app/src/main/res/drawable-hdpi/ic_action_forcerecheck_dark.png
  55. BIN
      app/src/main/res/drawable-hdpi/ic_action_forcerecheck_light.png
  56. BIN
      app/src/main/res/drawable-hdpi/ic_action_info.png
  57. BIN
      app/src/main/res/drawable-hdpi/ic_action_info_dark.png
  58. BIN
      app/src/main/res/drawable-hdpi/ic_action_info_light.png
  59. BIN
      app/src/main/res/drawable-hdpi/ic_action_labels.png
  60. BIN
      app/src/main/res/drawable-hdpi/ic_action_labels_dark.png
  61. BIN
      app/src/main/res/drawable-hdpi/ic_action_labels_light.png
  62. BIN
      app/src/main/res/drawable-hdpi/ic_action_pause.png
  63. BIN
      app/src/main/res/drawable-hdpi/ic_action_pause_dark.png
  64. BIN
      app/src/main/res/drawable-hdpi/ic_action_pause_light.png
  65. BIN
      app/src/main/res/drawable-hdpi/ic_action_priority_high.png
  66. BIN
      app/src/main/res/drawable-hdpi/ic_action_priority_high_dark.png
  67. BIN
      app/src/main/res/drawable-hdpi/ic_action_priority_high_light.png
  68. BIN
      app/src/main/res/drawable-hdpi/ic_action_priority_low.png
  69. BIN
      app/src/main/res/drawable-hdpi/ic_action_priority_low_dark.png
  70. BIN
      app/src/main/res/drawable-hdpi/ic_action_priority_low_light.png
  71. BIN
      app/src/main/res/drawable-hdpi/ic_action_priority_medium.png
  72. BIN
      app/src/main/res/drawable-hdpi/ic_action_priority_normal_dark.png
  73. BIN
      app/src/main/res/drawable-hdpi/ic_action_priority_normal_light.png
  74. BIN
      app/src/main/res/drawable-hdpi/ic_action_priority_off.png
  75. BIN
      app/src/main/res/drawable-hdpi/ic_action_priority_off_dark.png
  76. BIN
      app/src/main/res/drawable-hdpi/ic_action_priority_off_light.png
  77. BIN
      app/src/main/res/drawable-hdpi/ic_action_refresh.png
  78. BIN
      app/src/main/res/drawable-hdpi/ic_action_refresh_dark.png
  79. BIN
      app/src/main/res/drawable-hdpi/ic_action_refresh_light.png
  80. BIN
      app/src/main/res/drawable-hdpi/ic_action_remove.png
  81. BIN
      app/src/main/res/drawable-hdpi/ic_action_remove_dark.png
  82. BIN
      app/src/main/res/drawable-hdpi/ic_action_remove_light.png
  83. BIN
      app/src/main/res/drawable-hdpi/ic_action_resume.png
  84. BIN
      app/src/main/res/drawable-hdpi/ic_action_resume_dark.png
  85. BIN
      app/src/main/res/drawable-hdpi/ic_action_resume_light.png
  86. BIN
      app/src/main/res/drawable-hdpi/ic_action_rss.png.png
  87. BIN
      app/src/main/res/drawable-hdpi/ic_action_rss_dark.png
  88. BIN
      app/src/main/res/drawable-hdpi/ic_action_rss_light.png
  89. BIN
      app/src/main/res/drawable-hdpi/ic_action_save.png
  90. BIN
      app/src/main/res/drawable-hdpi/ic_action_save_dark.png
  91. BIN
      app/src/main/res/drawable-hdpi/ic_action_save_light.png
  92. BIN
      app/src/main/res/drawable-hdpi/ic_action_sort.png
  93. BIN
      app/src/main/res/drawable-hdpi/ic_action_sort_by_size_dark.png
  94. BIN
      app/src/main/res/drawable-hdpi/ic_action_sort_by_size_light.png
  95. BIN
      app/src/main/res/drawable-hdpi/ic_action_start.png
  96. BIN
      app/src/main/res/drawable-hdpi/ic_action_start_dark.png
  97. BIN
      app/src/main/res/drawable-hdpi/ic_action_start_light.png
  98. BIN
      app/src/main/res/drawable-hdpi/ic_action_stop.png
  99. BIN
      app/src/main/res/drawable-hdpi/ic_action_stop_dark.png
  100. BIN
      app/src/main/res/drawable-hdpi/ic_action_stop_light.png
  101. Some files were not shown because too many files have changed in this diff Show More

18
app/build.gradle

@ -2,12 +2,12 @@ apply plugin: 'com.android.application'
apply plugin: 'android-apt' apply plugin: 'android-apt'
android { android {
compileSdkVersion 21 compileSdkVersion 22
buildToolsVersion '21.1.1' buildToolsVersion '21.1.2'
defaultConfig { defaultConfig {
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 21 targetSdkVersion 22
versionCode 217 versionCode 217
versionName '2.5.0-SNAPSHOT' versionName '2.5.0-SNAPSHOT'
} }
@ -33,16 +33,16 @@ 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 'de.keyboardsurfer.android.widget:crouton:1.8.5@aar' compile 'de.keyboardsurfer.android.widget:crouton:1.8.5@aar'
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.+' compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.3'
compile 'com.android.support:appcompat-v7:21.0.0' compile 'com.android.support:appcompat-v7:22.0.0'
compile 'com.android.support:support-annotations:20.0.0' compile 'com.android.support:support-annotations:20.0.0'
compile 'com.getbase:floatingactionbutton:1.2.1' compile 'com.getbase:floatingactionbutton:1.8.0'
compile 'com.afollestad:material-dialogs:0.3.6' compile 'com.afollestad:material-dialogs:0.6.3.3'
apt 'org.androidannotations:androidannotations:3.1' apt 'org.androidannotations:androidannotations:3.2'
} }
apt { apt {

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();
} }
} }

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

@ -16,8 +16,14 @@
*/ */
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.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import android.widget.Toast;
import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Background; import org.androidannotations.annotations.Background;
@ -29,8 +35,11 @@ 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.log.Log; import org.transdroid.core.gui.log.Log;
import org.transdroid.core.gui.navigation.Label; import org.transdroid.core.gui.navigation.Label;
@ -63,23 +72,19 @@ 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 android.annotation.TargetApi; import java.util.ArrayList;
import android.content.Intent; import java.util.List;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.Toast;
import de.keyboardsurfer.android.widget.crouton.Crouton; 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 ActionBarActivity implements TorrentTasksExecutor, RefreshableActivity { public class DetailsActivity extends ActionBarActivity implements TorrentTasksExecutor, RefreshableActivity {
@Extra @Extra
@ -101,7 +106,9 @@ public class DetailsActivity extends ActionBarActivity implements TorrentTasksEx
private IDaemonAdapter currentConnection = null; private IDaemonAdapter currentConnection = null;
// Details view components // Details view components
@FragmentById(resName = "torrentdetails_fragment") @ViewById
protected Toolbar torrentsToolbar;
@FragmentById(R.id.torrentdetails_fragment)
protected DetailsFragment fragmentDetails; protected DetailsFragment fragmentDetails;
@Override @Override
@ -123,6 +130,7 @@ public class DetailsActivity extends ActionBarActivity implements TorrentTasksEx
} }
// Simple action bar with up, torrent name as title and refresh button // Simple action bar with up, torrent name as title and refresh button
setSupportActionBar(torrentsToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(NavigationHelper.buildCondensedFontString(torrent.getName())); getSupportActionBar().setTitle(NavigationHelper.buildCondensedFontString(torrent.getName()));
@ -151,14 +159,19 @@ public class DetailsActivity extends ActionBarActivity implements TorrentTasksEx
/** /**
* Attaches some view (perhaps contained in a fragment) to this activity's pull to refresh support * Attaches some view (perhaps contained in a fragment) to this activity's pull to refresh support
* @param view The view to attach * @param container The refresh container to handle user refresh requests and show the progress
*/ */
@Override @Override
public void addRefreshableView(View view) { public void addSwipeRefreshLayout(SwipeRefreshLayout container) {
// TODO Add new style pull to refresh library container.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
refreshScreen();
}
});
} }
@OptionsItem(resName = "action_refresh") @OptionsItem(R.id.action_refresh)
public void refreshScreen() { public void refreshScreen() {
fragmentDetails.updateIsLoading(true, null); fragmentDetails.updateIsLoading(true, null);
refreshTorrent(); refreshTorrent();
@ -170,8 +183,7 @@ public class DetailsActivity extends ActionBarActivity implements TorrentTasksEx
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);
} }
@ -179,8 +191,9 @@ public class DetailsActivity extends ActionBarActivity implements TorrentTasksEx
@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());
@ -191,8 +204,9 @@ public class DetailsActivity extends ActionBarActivity implements TorrentTasksEx
@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());
@ -255,8 +269,7 @@ public class DetailsActivity extends ActionBarActivity implements TorrentTasksEx
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);
} }
@ -264,19 +277,18 @@ public class DetailsActivity extends ActionBarActivity implements TorrentTasksEx
@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(); Toast.makeText(this, closeText, Toast.LENGTH_LONG).show();
}
} }
@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 {
@ -290,8 +302,7 @@ public class DetailsActivity extends ActionBarActivity implements TorrentTasksEx
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);
} }
@ -322,8 +333,7 @@ public class DetailsActivity extends ActionBarActivity implements TorrentTasksEx
@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 {
@ -334,8 +344,7 @@ public class DetailsActivity extends ActionBarActivity implements TorrentTasksEx
@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);
@ -351,7 +360,7 @@ public class DetailsActivity extends ActionBarActivity implements TorrentTasksEx
@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
@ -359,8 +368,7 @@ public class DetailsActivity extends ActionBarActivity implements TorrentTasksEx
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())), Crouton.showText(this, getString(LocalTorrent.getResourceForDaemonException(result.getException())), NavigationHelper.CROUTON_ERROR_STYLE);
NavigationHelper.CROUTON_ERROR_STYLE);
} }
@UiThread @UiThread
@ -368,8 +376,7 @@ public class DetailsActivity extends ActionBarActivity implements TorrentTasksEx
// 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)));
} }
} }

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

@ -16,9 +16,22 @@
*/ */
package org.transdroid.core.gui; package org.transdroid.core.gui;
import java.util.ArrayList; import android.annotation.SuppressLint;
import java.util.Collections; import android.app.Fragment;
import java.util.List; import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.support.v4.widget.SwipeRefreshLayout;
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 org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Click; import org.androidannotations.annotations.Click;
@ -29,10 +42,16 @@ 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.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.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;
@ -44,33 +63,21 @@ 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.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.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; 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") @OptionsMenu(R.menu.fragment_details)
public class DetailsFragment extends Fragment implements OnTrackersUpdatedListener, OnLabelPickedListener, public class DetailsFragment extends Fragment implements OnTrackersUpdatedListener, OnLabelPickedListener, OnStorageLocationUpdatedListener {
OnStorageLocationUpdatedListener {
// Local data // Local data
@InstanceState @InstanceState
@ -90,9 +97,11 @@ 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
protected SwipeRefreshLayout swipeRefreshLayout;
@ViewById
protected ListView detailsList; protected ListView detailsList;
@ViewById @ViewById
protected TextView emptyText, errorText; protected TextView emptyText, errorText;
@ -118,16 +127,19 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
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); ((RefreshableActivity) getActivity()).addSwipeRefreshLayout(swipeRefreshLayout);
} }
// 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);
}
} }
@ -165,13 +177,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()));
} }
/** /**
@ -181,22 +194,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
@ -207,12 +221,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);
} }
/** /**
@ -238,8 +252,10 @@ 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) swipeRefreshLayout.setRefreshing(isLoading);
if (isLoading || hasCriticalError) {
clear(); clear();
}
} }
@ItemClick(resName = "details_list") @ItemClick(resName = "details_list")
@ -287,59 +303,59 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
} }
@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_default") @OptionsItem(R.id.action_remove_default)
protected void removeTorrentDefault() { protected void removeTorrentDefault() {
getTasksExecutor().removeTorrent(torrent, false); getTasksExecutor().removeTorrent(torrent, false);
} }
@OptionsItem(resName = "action_remove_withdata") @OptionsItem(R.id.action_remove_withdata)
protected void removeTorrentWithData() { protected void removeTorrentWithData() {
getTasksExecutor().removeTorrent(torrent, true); getTasksExecutor().removeTorrent(torrent, true);
} }
@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) new SetLabelDialog().setOnLabelPickedListener(this).setCurrentLabels(currentLabels).show(getFragmentManager(), "SetLabelDialog");
.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); Crouton.showText(getActivity(), R.string.error_stillloadingdetails, NavigationHelper.CROUTON_INFO_STYLE);
@ -349,7 +365,7 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
.show(getFragmentManager(), "SetTrackersDialog"); .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()) new SetStorageLocationDialog().setOnStorageLocationUpdated(this).setCurrentLocation(torrent.getLocationDir())
.show(getFragmentManager(), "SetStorageLocationDialog"); .show(getFragmentManager(), "SetStorageLocationDialog");
@ -357,22 +373,25 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
@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);
} }
@ -413,11 +432,9 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
((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());
menu.findItem(R.id.action_download).setVisible(filePaths); menu.findItem(R.id.action_download).setVisible(filePaths);
boolean filePriorities = currentServerSettings != null && boolean filePriorities = currentServerSettings != null && Daemon.supportsFilePrioritySetting(currentServerSettings.getType());
Daemon.supportsFilePrioritySetting(currentServerSettings.getType());
menu.findItem(R.id.action_priority_off).setVisible(filePriorities); menu.findItem(R.id.action_priority_off).setVisible(filePriorities);
menu.findItem(R.id.action_priority_low).setVisible(filePriorities); menu.findItem(R.id.action_priority_low).setVisible(filePriorities);
menu.findItem(R.id.action_priority_normal).setVisible(filePriorities); menu.findItem(R.id.action_priority_normal).setVisible(filePriorities);
@ -430,35 +447,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());
@ -472,8 +490,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);
} }
} }
@ -485,8 +504,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();
@ -494,8 +512,7 @@ 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), Crouton.showText(getActivity(), getString(R.string.error_noftpapp, url), NavigationHelper.CROUTON_ERROR_STYLE);
NavigationHelper.CROUTON_ERROR_STYLE);
mode.finish(); mode.finish();
return true; return true;
@ -503,24 +520,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;

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());
}
}

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

@ -16,30 +16,29 @@
*/ */
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 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.NavigationFilter;
import org.transdroid.core.gui.navigation.NavigationHelper; 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.IDaemonAdapter;
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; 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
protected TextView filterText, serverText, downcountText, upcountText, downcountSign, upcountSign, downspeedText, upspeedText; protected TextView downcountText, upcountText, downcountSign, upcountSign, downspeedText, upspeedText;
@ViewById @ViewById
protected View speedswrapperLayout; protected View speedswrapperLayout;
private TorrentsActivity activity; private TorrentsActivity activity;
@ -49,26 +48,10 @@ public class ServerStatusView extends RelativeLayout implements OnRatesPickedLis
} }
public ServerStatusView(TorrentsActivity activity) { public ServerStatusView(TorrentsActivity activity) {
super(activity.getSupportActionBar().getThemedContext()); super(activity);
this.activity = activity; this.activity = activity;
} }
/**
* 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());
}
/** /**
* Updates the statistics as shown in the action bar through this server status view. * Updates the statistics as shown in the action bar through this server status view.
* @param torrents The most recently received list of torrents * @param torrents The most recently received list of torrents
@ -115,8 +98,8 @@ public class ServerStatusView extends RelativeLayout implements OnRatesPickedLis
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( new SetTransferRatesDialog().setOnRatesPickedListener(ServerStatusView.this)
activity.getFragmentManager(), "SetTransferRatesDialog"); .show(activity.getFragmentManager(), "SetTransferRatesDialog");
} }
}; };

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

@ -16,28 +16,38 @@
*/ */
package org.transdroid.core.gui; package org.transdroid.core.gui;
import java.util.List;
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);
} }

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

@ -26,8 +26,10 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.view.MenuItemCompat; import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.Menu; import android.view.Menu;
@ -37,17 +39,19 @@ import android.view.View.OnClickListener;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView; import android.widget.ListView;
import android.widget.SearchView;
import com.getbase.floatingactionbutton.FloatingActionButton;
import com.getbase.floatingactionbutton.FloatingActionsMenu;
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;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.FragmentById; import org.androidannotations.annotations.FragmentById;
import org.androidannotations.annotations.InstanceState; import org.androidannotations.annotations.InstanceState;
import org.androidannotations.annotations.OnActivityResult; import org.androidannotations.annotations.OnActivityResult;
import org.androidannotations.annotations.OptionsItem; import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.OptionsMenu;
import org.androidannotations.annotations.SystemService; import org.androidannotations.annotations.SystemService;
import org.androidannotations.annotations.UiThread; import org.androidannotations.annotations.UiThread;
import org.androidannotations.annotations.ViewById; import org.androidannotations.annotations.ViewById;
@ -133,14 +137,12 @@ import java.util.Map.Entry;
import de.keyboardsurfer.android.widget.crouton.Crouton; import de.keyboardsurfer.android.widget.crouton.Crouton;
/** /**
* Main activity that holds the fragment that shows the torrents list, presents a way to filter the list (via an action * Main activity that holds the fragment that shows the torrents list, presents a way to filter the list (via an action bar spinner or list side list)
* bar spinner or list side list) and potentially shows a torrent details fragment too, if there is room. Task execution * and potentially shows a torrent details fragment too, if there is room. Task execution such as loading of and adding torrents is performs in this
* such as loading of and adding torrents is performs in this activity, using background methods. Finally, the activity * activity, using background methods. Finally, the activity offers navigation elements such as access to settings and showing connection issues.
* offers navigation elements such as access to settings and showing connection issues.
* @author Eric Kok * @author Eric Kok
*/ */
@EActivity(resName = "activity_torrents") @EActivity(R.layout.activity_torrents)
@OptionsMenu(resName = "activity_torrents")
public class TorrentsActivity extends ActionBarActivity implements TorrentTasksExecutor, RefreshableActivity { public class TorrentsActivity extends ActionBarActivity implements TorrentTasksExecutor, RefreshableActivity {
private static final int RESULT_DETAILS = 0; private static final int RESULT_DETAILS = 0;
@ -158,17 +160,28 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
@Bean @Bean
protected ConnectivityHelper connectivityHelper; protected ConnectivityHelper connectivityHelper;
@ViewById @ViewById
protected Toolbar selectionToolbar;
@ViewById
protected Toolbar torrentsToolbar; protected Toolbar torrentsToolbar;
@ViewById @ViewById
protected Toolbar actionsToolbar;
@ViewById
protected FloatingActionsMenu addmenuButton;
@ViewById
protected FloatingActionButton addmenuFileButton;
@ViewById
protected DrawerLayout drawerLayout; protected DrawerLayout drawerLayout;
@ViewById @ViewById
protected ListView drawerList; protected ListView drawerList;
@ViewById @ViewById
protected ListView filtersList; protected ListView filtersList;
protected ListView navigationList; @ViewById
protected FilterListAdapter navigationListAdapter; protected SearchView filterSearch;
protected ServerStatusView serverStatusView; private ListView navigationList;
protected ActionBarDrawerToggle drawerToggle; private FilterListAdapter navigationListAdapter;
private ServerSelectionView serverSelectionView;
private ServerStatusView serverStatusView;
private ActionBarDrawerToggle drawerToggle;
// Settings // Settings
@Bean @Bean
@ -184,9 +197,9 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
@InstanceState @InstanceState
protected ArrayList<Label> lastNavigationLabels; protected ArrayList<Label> lastNavigationLabels;
// Contained torrent and details fragments // Contained torrent and details fragments
@FragmentById(resName = "torrents_fragment") @FragmentById(R.id.torrents_fragment)
protected TorrentsFragment fragmentTorrents; protected TorrentsFragment fragmentTorrents;
@FragmentById(resName = "torrentdetails_fragment") @FragmentById(R.id.torrentdetails_fragment)
protected DetailsFragment fragmentDetails; protected DetailsFragment fragmentDetails;
@InstanceState @InstanceState
boolean firstStart = true; boolean firstStart = true;
@ -195,20 +208,6 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
// Auto refresh task // Auto refresh task
private AsyncTask<Void, Void, Void> autoRefreshTask; private AsyncTask<Void, Void, Void> autoRefreshTask;
/**
* Handles item selections on the dedicated list of filter items
*/
private OnItemClickListener onFilterListItemClicked = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
navigationList.setItemChecked(position, true);
Object item = navigationList.getAdapter().getItem(position);
if (item instanceof SimpleListItem) {
filterSelected((SimpleListItem) item, false);
}
drawerLayout.closeDrawer(drawerList);
}
};
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -217,18 +216,24 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
setTheme(R.style.TransdroidTheme_Dark); setTheme(R.style.TransdroidTheme_Dark);
} }
// Catch any uncaught exception to log it // Catch any uncaught exception to log it
Thread.setDefaultUncaughtExceptionHandler( Thread.setDefaultUncaughtExceptionHandler(new LogUncaughtExceptionHandler(this, Thread.getDefaultUncaughtExceptionHandler()));
new LogUncaughtExceptionHandler(this, Thread.getDefaultUncaughtExceptionHandler()));
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
} }
@AfterViews @AfterViews
protected void init() { protected void init() {
// Use a custom view as action bar content, showing filter selection and current torrent counts/speeds // Use custom views as action bar content, showing filter selection and current torrent counts/speeds
setSupportActionBar(torrentsToolbar); serverSelectionView = ServerSelectionView_.build(this);
serverStatusView = ServerStatusView_.build(this); serverStatusView = ServerStatusView_.build(this);
torrentsToolbar.addView(serverStatusView); if (selectionToolbar != null) {
selectionToolbar.addView(serverSelectionView);
} else {
torrentsToolbar.addView(serverSelectionView);
}
actionsToolbar.addView(serverStatusView);
setSupportActionBar(torrentsToolbar); // For direct menu item inflation by the contained fragments
getSupportActionBar().setDisplayShowTitleEnabled(false);
// Construct the filters list, i.e. the list of servers, status types and labels // Construct the filters list, i.e. the list of servers, status types and labels
navigationListAdapter = FilterListAdapter_.getInstance_(this); navigationListAdapter = FilterListAdapter_.getInstance_(this);
@ -242,18 +247,19 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
navigationList = filtersList; navigationList = filtersList;
} else { } else {
navigationList = drawerList; navigationList = drawerList;
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.navigation_opendrawer, drawerToggle =
R.string.navigation_closedrawer); new ActionBarDrawerToggle(this, drawerLayout, torrentsToolbar, R.string.navigation_opendrawer, R.string.navigation_closedrawer);
drawerToggle.setDrawerIndicatorEnabled(true); drawerToggle.setDrawerIndicatorEnabled(true);
drawerLayout.setDrawerListener(drawerToggle); drawerLayout.setDrawerListener(drawerToggle);
} }
navigationList.setAdapter(navigationListAdapter); navigationList.setAdapter(navigationListAdapter);
navigationList.setOnItemClickListener(onFilterListItemClicked); navigationList.setOnItemClickListener(onFilterListItemClicked);
// Now that all items (or at least their adapters) have been added, ensure a filter is selected // Now that all items (or at least their adapters) have been added, ensure a filter is selected
// NOTE When this is a fresh start, it might override the filter later (based on the last user selection) // NOTE When this is a fresh start, we might override the filter later (based on the last user selection)
if (currentFilter == null) { if (currentFilter == null) {
currentFilter = StatusType.getShowAllType(this); currentFilter = StatusType.getShowAllType(this);
} }
filterSearch.setOnQueryTextListener(filterQueryTextChanged);
// Load the default server or a server that was explicitly supplied in the starting intent // Load the default server or a server that was explicitly supplied in the starting intent
ServerSetting defaultServer = applicationSettings.getDefaultServer(); ServerSetting defaultServer = applicationSettings.getDefaultServer();
@ -293,8 +299,8 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
// Resume after instead of fully loading the torrents list; create connection and set action bar title // Resume after instead of fully loading the torrents list; create connection and set action bar title
ServerSetting lastUsed = applicationSettings.getLastUsedServer(); ServerSetting lastUsed = applicationSettings.getLastUsedServer();
currentConnection = lastUsed.createServerAdapter(connectivityHelper.getConnectedNetworkName(), this); currentConnection = lastUsed.createServerAdapter(connectivityHelper.getConnectedNetworkName(), this);
serverStatusView.updateCurrentServer(currentConnection); serverSelectionView.updateCurrentServer(currentConnection);
serverStatusView.updateCurrentFilter(currentFilter); serverSelectionView.updateCurrentFilter(currentFilter);
} }
firstStart = false; firstStart = false;
@ -378,7 +384,6 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
} }
}; };
// Executes serially by default on Honeycomb, was parallel before
autoRefreshTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); autoRefreshTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
@ -398,6 +403,9 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu); super.onCreateOptionsMenu(menu);
// Manually insert the actions into the main torrent and secondary actions toolbars
torrentsToolbar.inflateMenu(R.menu.activity_torrents_main);
actionsToolbar.inflateMenu(R.menu.activity_torrents_secondary);
if (navigationHelper.enableSearchUi()) { 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);
@ -437,16 +445,16 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
// No connection yet; hide all menu options except settings // No connection yet; hide all menu options except settings
if (currentConnection == null) { if (currentConnection == null) {
menu.findItem(R.id.action_add).setVisible(false); torrentsToolbar.setNavigationIcon(null);
menu.findItem(R.id.action_search).setVisible(false); addmenuButton.setVisibility(View.GONE);
menu.findItem(R.id.action_rss).setVisible(false); torrentsToolbar.getMenu().findItem(R.id.action_search).setVisible(false);
menu.findItem(R.id.action_enableturtle).setVisible(false); torrentsToolbar.getMenu().findItem(R.id.action_rss).setVisible(false);
menu.findItem(R.id.action_disableturtle).setVisible(false); torrentsToolbar.getMenu().findItem(R.id.action_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
menu.findItem(R.id.action_refresh).setVisible(false); torrentsToolbar.getMenu().findItem(R.id.action_help).setVisible(true);
menu.findItem(R.id.action_sort).setVisible(false); actionsToolbar.getMenu().findItem(R.id.action_enableturtle).setVisible(false);
menu.findItem(R.id.action_filter).setVisible(false); actionsToolbar.getMenu().findItem(R.id.action_disableturtle).setVisible(false);
menu.findItem(R.id.action_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); actionsToolbar.getMenu().findItem(R.id.action_refresh).setVisible(false);
menu.findItem(R.id.action_help).setVisible(true); actionsToolbar.getMenu().findItem(R.id.action_sort).setVisible(false);
if (fragmentTorrents != null) { if (fragmentTorrents != null) {
fragmentTorrents.updateConnectionStatus(false, null); fragmentTorrents.updateConnectionStatus(false, null);
} }
@ -454,20 +462,22 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
} }
// There is a connection (read: settings to some server known) // There is a connection (read: settings to some server known)
menu.findItem(R.id.action_add).setVisible(true); torrentsToolbar.setNavigationIcon(R.drawable.ic_action_drawer);
addmenuButton.setVisibility(View.VISIBLE);
boolean addByFile = Daemon.supportsAddByFile(currentConnection.getType()); boolean addByFile = Daemon.supportsAddByFile(currentConnection.getType());
menu.findItem(R.id.action_add_fromfile).setVisible(addByFile); addmenuFileButton.setVisibility(addByFile ? View.VISIBLE : View.GONE);
menu.findItem(R.id.action_search).setVisible(navigationHelper.enableSearchUi()); // Primary toolbar menu
menu.findItem(R.id.action_rss).setVisible(navigationHelper.enableRssUi()); torrentsToolbar.getMenu().findItem(R.id.action_search).setVisible(navigationHelper.enableSearchUi());
torrentsToolbar.getMenu().findItem(R.id.action_rss).setVisible(navigationHelper.enableRssUi());
torrentsToolbar.getMenu().findItem(R.id.action_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
torrentsToolbar.getMenu().findItem(R.id.action_help).setVisible(false);
// Secondary toolbar menu
boolean hasAltMode = Daemon.supportsSetAlternativeMode(currentConnection.getType()); boolean hasAltMode = Daemon.supportsSetAlternativeMode(currentConnection.getType());
menu.findItem(R.id.action_enableturtle).setVisible(hasAltMode && !turleModeEnabled); actionsToolbar.getMenu().findItem(R.id.action_enableturtle).setVisible(hasAltMode && !turleModeEnabled);
menu.findItem(R.id.action_disableturtle).setVisible(hasAltMode && turleModeEnabled); actionsToolbar.getMenu().findItem(R.id.action_disableturtle).setVisible(hasAltMode && turleModeEnabled);
menu.findItem(R.id.action_refresh).setVisible(true); actionsToolbar.getMenu().findItem(R.id.action_refresh).setVisible(true);
menu.findItem(R.id.action_sort).setVisible(true); actionsToolbar.getMenu().findItem(R.id.action_sort).setVisible(true);
menu.findItem(R.id.action_sort_added).setVisible(Daemon.supportsDateAdded(currentConnection.getType())); actionsToolbar.getMenu().findItem(R.id.action_sort_added).setVisible(Daemon.supportsDateAdded(currentConnection.getType()));
menu.findItem(R.id.action_filter).setVisible(true);
menu.findItem(R.id.action_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
menu.findItem(R.id.action_help).setVisible(false);
if (fragmentTorrents != null) { if (fragmentTorrents != null) {
fragmentTorrents.updateConnectionStatus(true, currentConnection.getType()); fragmentTorrents.updateConnectionStatus(true, currentConnection.getType());
} }
@ -481,6 +491,21 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
return drawerToggle != null && drawerToggle.onOptionsItemSelected(item); return drawerToggle != null && drawerToggle.onOptionsItemSelected(item);
} }
/**
* Handles item selections on the dedicated list of filter items
*/
private OnItemClickListener onFilterListItemClicked = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
navigationList.setItemChecked(position, true);
Object item = navigationList.getAdapter().getItem(position);
if (item instanceof SimpleListItem) {
filterSelected((SimpleListItem) item, false);
}
drawerLayout.closeDrawer(drawerList);
}
};
/** /**
* A new filter was selected; update the view over the current data * A new filter was selected; update the view over the current data
* @param item The touched filter item * @param item The touched filter item
@ -505,9 +530,9 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
// Update connection to the newly selected server and refresh // Update connection to the newly selected server and refresh
currentConnection = server.createServerAdapter(connectivityHelper.getConnectedNetworkName(), this); currentConnection = server.createServerAdapter(connectivityHelper.getConnectedNetworkName(), this);
applicationSettings.setLastUsedServer(server); applicationSettings.setLastUsedServer(server);
serverStatusView.updateCurrentServer(currentConnection); serverSelectionView.updateCurrentServer(currentConnection);
if (forceNewConnection) { if (forceNewConnection) {
serverStatusView.updateCurrentFilter(currentFilter); serverSelectionView.updateCurrentFilter(currentFilter);
} }
// Clear the currently shown list of torrents and perhaps the details // Clear the currently shown list of torrents and perhaps the details
@ -528,7 +553,7 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
// Set new filter // Set new filter
currentFilter = (NavigationFilter) item; currentFilter = (NavigationFilter) item;
fragmentTorrents.applyNavigationFilter(currentFilter); fragmentTorrents.applyNavigationFilter(currentFilter);
serverStatusView.updateCurrentFilter(currentFilter); serverSelectionView.updateCurrentFilter(currentFilter);
// Remember that the user last selected this // Remember that the user last selected this
applicationSettings.setLastUsedNavigationFilter(currentFilter); applicationSettings.setLastUsedNavigationFilter(currentFilter);
// Clear the details view // Clear the details view
@ -564,8 +589,7 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
protected void handleStartIntent() { protected void handleStartIntent() {
// For intents that come from out of the application, perhaps we can not directly add them // For intents that come from out of the application, perhaps we can not directly add them
if (applicationSettings.getDefaultServerKey() == ApplicationSettings.DEFAULTSERVER_ASKONADD && if (applicationSettings.getDefaultServerKey() == ApplicationSettings.DEFAULTSERVER_ASKONADD && getIntent().getData() != null) {
getIntent().getData() != null) {
// First ask which server to use before adding any intent from the extras // First ask which server to use before adding any intent from the extras
ServerPickerDialog.startServerPicker(this, applicationSettings.getAllServerSettings()); ServerPickerDialog.startServerPicker(this, applicationSettings.getAllServerSettings());
return; return;
@ -597,8 +621,7 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
String[] titles = intent.getStringArrayExtra("TORRENT_TITLES"); String[] titles = intent.getStringArrayExtra("TORRENT_TITLES");
if (urls != null) { if (urls != null) {
for (int i = 0; i < urls.length; i++) { for (int i = 0; i < urls.length; i++) {
String title = (titles != null && titles.length >= i ? titles[i] : String title = (titles != null && titles.length >= i ? titles[i] : NavigationHelper.extractNameFromUri(Uri.parse(urls[i])));
NavigationHelper.extractNameFromUri(Uri.parse(urls[i])));
if (intent.hasExtra("PRIVATE_SOURCE")) { if (intent.hasExtra("PRIVATE_SOURCE")) {
// This is marked by the Search Module as being a private source site; get the url locally first // This is marked by the Search Module as being a private source site; get the url locally first
addTorrentFromPrivateSource(urls[i], title, intent.getStringExtra("PRIVATE_SOURCE")); addTorrentFromPrivateSource(urls[i], title, intent.getStringExtra("PRIVATE_SOURCE"));
@ -694,12 +717,12 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
return true; return true;
} }
@OptionsItem(resName = "action_add_fromurl") @Click(R.id.addmenu_link_button)
protected void startUrlEntryDialog() { protected void startUrlEntryDialog() {
UrlEntryDialog.startUrlEntry(this); UrlEntryDialog.startUrlEntry(this);
} }
@OptionsItem(resName = "action_add_fromfile") @Click(R.id.addmenu_file_button)
protected void startFilePicker() { protected void startFilePicker() {
FilePickerHelper.startFilePicker(this); FilePickerHelper.startFilePicker(this);
} }
@ -714,7 +737,7 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
} }
} }
@OptionsItem(resName = "action_add_frombarcode") @Click(R.id.addmenu_barcode_button)
protected void startBarcodeScanner() { protected void startBarcodeScanner() {
BarcodeHelper.startBarcodeScanner(this, BarcodeHelper.ACTIVITY_BARCODE_ADDTORRENT); BarcodeHelper.startBarcodeScanner(this, BarcodeHelper.ACTIVITY_BARCODE_ADDTORRENT);
} }
@ -741,14 +764,19 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
/** /**
* Attaches some view (perhaps contained in a fragment) to this activity's pull to refresh support * Attaches some view (perhaps contained in a fragment) to this activity's pull to refresh support
* @param view The view to attach * @param container The refresh container to handle user refresh requests and show the progress
*/ */
@Override @Override
public void addRefreshableView(View view) { public void addSwipeRefreshLayout(SwipeRefreshLayout container) {
// TODO Add new style pull to refresh library (using SwipeRefreshLayout?) container.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
refreshScreen();
}
});
} }
@OptionsItem(resName = "action_refresh") @OptionsItem(R.id.action_refresh)
public void refreshScreen() { public void refreshScreen() {
fragmentTorrents.updateIsLoading(true); fragmentTorrents.updateIsLoading(true);
refreshTorrents(); refreshTorrents();
@ -757,80 +785,89 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
} }
} }
@OptionsItem(resName = "action_enableturtle") @OptionsItem(R.id.action_enableturtle)
protected void enableTurtleMode() { protected void enableTurtleMode() {
updateTurtleMode(true); updateTurtleMode(true);
} }
@OptionsItem(resName = "action_disableturtle") @OptionsItem(R.id.action_disableturtle)
protected void disableTurtleMode() { protected void disableTurtleMode() {
updateTurtleMode(false); updateTurtleMode(false);
} }
@OptionsItem(resName = "action_rss") @OptionsItem(R.id.action_rss)
protected void openRss() { protected void openRss() {
RssfeedsActivity_.intent(this).start(); RssfeedsActivity_.intent(this).start();
} }
@OptionsItem(resName = "action_settings") @OptionsItem(R.id.action_settings)
protected void openSettings() { protected void openSettings() {
MainSettingsActivity_.intent(this).start(); MainSettingsActivity_.intent(this).start();
} }
@OptionsItem(resName = "action_help") @OptionsItem(R.id.action_help)
protected void openHelp() { protected void openHelp() {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.transdroid.org/download/"))); startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.transdroid.org/download/")));
} }
@OptionsItem(resName = "action_sort_byname") @OptionsItem(R.id.action_sort_byname)
protected void sortByName() { protected void sortByName() {
fragmentTorrents.sortBy(TorrentsSortBy.Alphanumeric); fragmentTorrents.sortBy(TorrentsSortBy.Alphanumeric);
} }
@OptionsItem(resName = "action_sort_status") @OptionsItem(R.id.action_sort_status)
protected void sortByStatus() { protected void sortByStatus() {
fragmentTorrents.sortBy(TorrentsSortBy.Status); fragmentTorrents.sortBy(TorrentsSortBy.Status);
} }
@OptionsItem(resName = "action_sort_done") @OptionsItem(R.id.action_sort_done)
protected void sortByDateDone() { protected void sortByDateDone() {
fragmentTorrents.sortBy(TorrentsSortBy.DateDone); fragmentTorrents.sortBy(TorrentsSortBy.DateDone);
} }
@OptionsItem(resName = "action_sort_added") @OptionsItem(R.id.action_sort_added)
protected void sortByDateAdded() { protected void sortByDateAdded() {
fragmentTorrents.sortBy(TorrentsSortBy.DateAdded); fragmentTorrents.sortBy(TorrentsSortBy.DateAdded);
} }
@OptionsItem(resName = "action_sort_percent") @OptionsItem(R.id.action_sort_percent)
protected void sortByPercent() { protected void sortByPercent() {
fragmentTorrents.sortBy(TorrentsSortBy.Percent); fragmentTorrents.sortBy(TorrentsSortBy.Percent);
} }
@OptionsItem(resName = "action_sort_downspeed") @OptionsItem(R.id.action_sort_downspeed)
protected void sortByDownspeed() { protected void sortByDownspeed() {
fragmentTorrents.sortBy(TorrentsSortBy.DownloadSpeed); fragmentTorrents.sortBy(TorrentsSortBy.DownloadSpeed);
} }
@OptionsItem(resName = "action_sort_upspeed") @OptionsItem(R.id.action_sort_upspeed)
protected void sortByUpspeed() { protected void sortByUpspeed() {
fragmentTorrents.sortBy(TorrentsSortBy.UploadSpeed); fragmentTorrents.sortBy(TorrentsSortBy.UploadSpeed);
} }
@OptionsItem(resName = "action_sort_ratio") @OptionsItem(R.id.action_sort_ratio)
protected void sortByRatio() { protected void sortByRatio() {
fragmentTorrents.sortBy(TorrentsSortBy.Ratio); fragmentTorrents.sortBy(TorrentsSortBy.Ratio);
} }
@OptionsItem(resName = "action_sort_size") @OptionsItem(R.id.action_sort_size)
protected void sortBySize() { protected void sortBySize() {
fragmentTorrents.sortBy(TorrentsSortBy.Size); fragmentTorrents.sortBy(TorrentsSortBy.Size);
} }
@OptionsItem(resName = "action_filter") private SearchView.OnQueryTextListener filterQueryTextChanged = new SearchView.OnQueryTextListener() {
protected void startFilterEntryDialog() { @Override
FilterEntryDialog.startFilterEntry(this); public boolean onQueryTextSubmit(String query) {
} return false;
}
@Override
public boolean onQueryTextChange(String newText) {
// Redirect to filter method which will directly apply it
filterTorrents(newText);
return true;
}
};
/** /**
* Redirect the newly entered list filter to the torrents fragment. * Redirect the newly entered list filter to the torrents fragment.
@ -841,16 +878,15 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
} }
/** /**
* Shows the a details fragment for the given torrent, either in the dedicated details fragment pane, in the same * Shows the a details fragment for the given torrent, either in the dedicated details fragment pane, in the same pane as the torrent list was
* pane as the torrent list was displayed or by starting a details activity. * displayed or by starting a details activity.
* @param torrent The torrent to show detailed statistics for * @param torrent The torrent to show detailed statistics for
*/ */
public void openDetails(Torrent torrent) { public void openDetails(Torrent torrent) {
if (fragmentDetails != null && fragmentDetails.isAdded()) { if (fragmentDetails != null && fragmentDetails.isAdded()) {
fragmentDetails.updateTorrent(torrent); fragmentDetails.updateTorrent(torrent);
} else { } else {
DetailsActivity_.intent(this).torrent(torrent).currentLabels(lastNavigationLabels) DetailsActivity_.intent(this).torrent(torrent).currentLabels(lastNavigationLabels).startForResult(RESULT_DETAILS);
.startForResult(RESULT_DETAILS);
} }
} }
@ -863,8 +899,7 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
return; return;
} }
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);
} }
@ -961,9 +996,8 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
AddByMagnetUrlTask addByMagnetUrlTask = AddByMagnetUrlTask.create(currentConnection, url); AddByMagnetUrlTask addByMagnetUrlTask = AddByMagnetUrlTask.create(currentConnection, url);
if (!Daemon.supportsAddByMagnetUrl(currentConnection.getType())) { if (!Daemon.supportsAddByMagnetUrl(currentConnection.getType())) {
// No support for magnet links: forcefully let the task fail to report the error // No support for magnet links: forcefully let the task fail to report the error
onCommunicationError(new DaemonTaskFailureResult(addByMagnetUrlTask, onCommunicationError(new DaemonTaskFailureResult(addByMagnetUrlTask, new DaemonException(DaemonException.ExceptionType.MethodUnsupported,
new DaemonException(DaemonException.ExceptionType.MethodUnsupported, currentConnection.getType().name() + " does not support magnet links")), false);
currentConnection.getType().name() + " does not support magnet links")), false);
return; return;
} }
@ -1021,8 +1055,7 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
try { try {
// Cookies are taken from the websearchSetting that we already matched against this target URL // Cookies are taken from the websearchSetting that we already matched against this target URL
DefaultHttpClient httpclient = DefaultHttpClient httpclient = HttpHelper.createStandardHttpClient(false, null, null, true, null, 10000, null, -1);
HttpHelper.createStandardHttpClient(false, null, null, true, null, 10000, null, -1);
Map<String, String> cookies = HttpHelper.parseCookiePairs(websearchSetting.getCookies()); Map<String, String> cookies = HttpHelper.parseCookiePairs(websearchSetting.getCookies());
String domain = Uri.parse(url).getHost(); String domain = Uri.parse(url).getHost();
for (Entry<String, String> pair : cookies.entrySet()) { for (Entry<String, String> pair : cookies.entrySet()) {
@ -1140,8 +1173,7 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
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) {
onTaskSucceeded((DaemonTaskSuccessResult) result, onTaskSucceeded((DaemonTaskSuccessResult) result,
getString(withData ? R.string.result_removed_with_data : R.string.result_removed, 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);
} }
@ -1151,12 +1183,10 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
@Override @Override
public void updateLabel(Torrent torrent, String newLabel) { public void updateLabel(Torrent torrent, String newLabel) {
torrent.mimicNewLabel(newLabel); torrent.mimicNewLabel(newLabel);
DaemonTaskResult result = DaemonTaskResult result = SetLabelTask.create(currentConnection, torrent, newLabel == null ? "" : newLabel).execute(log);
SetLabelTask.create(currentConnection, torrent, newLabel == null ? "" : newLabel).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, onTaskSucceeded((DaemonTaskSuccessResult) result,
newLabel == null ? getString(R.string.result_labelremoved) : newLabel == null ? getString(R.string.result_labelremoved) : getString(R.string.result_labelset, newLabel));
getString(R.string.result_labelset, newLabel));
} else { } else {
onCommunicationError((DaemonTaskFailureResult) result, false); onCommunicationError((DaemonTaskFailureResult) result, false);
} }
@ -1168,8 +1198,7 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
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);
} }
@ -1200,9 +1229,7 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
@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 = DaemonTaskResult result = SetFilePriorityTask.create(currentConnection, torrent, priority, new ArrayList<TorrentFile>(files)).execute(log);
SetFilePriorityTask.create(currentConnection, torrent, priority, 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 {
@ -1212,8 +1239,7 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
@Background @Background
public void updateMaxSpeeds(Integer maxDownloadSpeed, Integer maxUploadSpeed) { public void updateMaxSpeeds(Integer maxDownloadSpeed, Integer maxUploadSpeed) {
DaemonTaskResult result = DaemonTaskResult result = SetTransferRatesTask.create(currentConnection, maxUploadSpeed, maxDownloadSpeed).execute(log);
SetTransferRatesTask.create(currentConnection, maxUploadSpeed, maxDownloadSpeed).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_maxspeedsset)); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_maxspeedsset));
} else { } else {
@ -1246,8 +1272,7 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
@UiThread @UiThread
protected void onTorrentsRetrieved(List<Torrent> torrents, List<org.transdroid.daemon.Label> labels) { protected void onTorrentsRetrieved(List<Torrent> torrents, List<org.transdroid.daemon.Label> labels) {
lastNavigationLabels = lastNavigationLabels = Label.convertToNavigationLabels(labels, getResources().getString(R.string.labels_unlabeled));
Label.convertToNavigationLabels(labels, getResources().getString(R.string.labels_unlabeled));
// Report the newly retrieved list of torrents to the torrents fragment // Report the newly retrieved list of torrents to the torrents fragment
fragmentTorrents.updateIsLoading(false); fragmentTorrents.updateIsLoading(false);
@ -1281,8 +1306,8 @@ public class TorrentsActivity extends ActionBarActivity implements TorrentTasksE
} }
// Update the server status (counts and speeds) in the action bar // Update the server status (counts and speeds) in the action bar
serverStatusView.updateStatus(torrents, systemSettings.treatDormantAsInactive(), serverStatusView
Daemon.supportsSetTransferRates(currentConnection.getType())); .updateStatus(torrents, systemSettings.treatDormantAsInactive(), Daemon.supportsSetTransferRates(currentConnection.getType()));
} }

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

@ -16,10 +16,16 @@
*/ */
package org.transdroid.core.gui; package org.transdroid.core.gui;
import java.util.ArrayList; import android.app.Fragment;
import java.util.Collections; import android.support.v4.widget.SwipeRefreshLayout;
import java.util.Iterator; import android.view.ActionMode;
import java.util.Locale; 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 org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.Bean;
@ -31,7 +37,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 +50,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 +92,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 +116,12 @@ 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); ((RefreshableActivity) getActivity()).addSwipeRefreshLayout(swipeRefreshLayout);
} }
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 +153,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 +167,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 +178,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 +218,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();
@ -270,11 +276,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();
@ -316,9 +322,10 @@ 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) new SetLabelDialog().setOnLabelPickedListener(TorrentsFragment.this).setCurrentLabels(currentLabels)
.show(getFragmentManager(), "SetLabelDialog"); .show(getFragmentManager(), "SetLabelDialog");
}
mode.finish(); mode.finish();
return true; return true;
} else { } else {
@ -359,7 +366,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);
@ -373,8 +380,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) {
@ -393,12 +400,12 @@ 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) {
this.isLoading = isLoading; this.isLoading = isLoading;
swipeRefreshLayout.setRefreshing(isLoading);
if (isLoading) { if (isLoading) {
clear(true, false); // Indirectly also calls updateViewVisibility() clear(true, false); // Indirectly also calls updateViewVisibility()
} else { } else {
@ -407,10 +414,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;

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

@ -16,31 +16,33 @@
*/ */
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
protected TextView itemText; protected TextView itemText;
public SimpleListItemView(Context context) { public SimpleListItemView(Context context) {
super(context); super(context);
} }
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);
}
} }
} }

45
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,36 +73,31 @@ 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);
} }
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"));
} }

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

@ -16,23 +16,24 @@
*/ */
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
protected TextView nameText, progressText, sizesText; protected TextView nameText, progressText, sizesText;
public TorrentFileView(Context context) { public TorrentFileView(Context context) {
super(context, null); super(context, null);
} }
@ -43,5 +44,5 @@ public class TorrentFileView extends TorrentFilePriorityLayout {
progressText.setText(torrentFile.getProgressText()); progressText.setText(torrentFile.getProgressText());
setPriority(torrentFile.getPriority()); setPriority(torrentFile.getPriority());
} }
} }

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());

24
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.
@ -33,9 +33,9 @@ import android.widget.BaseAdapter;
*/ */
@EBean @EBean
public class TorrentsAdapter extends BaseAdapter { public class TorrentsAdapter extends BaseAdapter {
private ArrayList<Torrent> torrents = null; private ArrayList<Torrent> torrents = null;
@RootContext @RootContext
protected Context context; protected Context context;
@ -47,23 +47,25 @@ public class TorrentsAdapter extends BaseAdapter {
this.torrents = newTorrents; this.torrents = newTorrents;
notifyDataSetChanged(); notifyDataSetChanged();
} }
@Override @Override
public boolean hasStableIds() { public boolean hasStableIds() {
return true; return true;
} }
@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);
} }

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);

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;

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

@ -16,24 +16,25 @@
*/ */
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
protected TextView itemText; protected TextView itemText;
public FilterListItemView(Context context) { public FilterListItemView(Context context) {
super(context); super(context);
} }
@ -41,5 +42,5 @@ public class FilterListItemView extends FrameLayout {
public void bind(SimpleListItem filterItem) { public void bind(SimpleListItem filterItem) {
itemText.setText(filterItem.getName()); itemText.setText(filterItem.getName());
} }
} }

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

@ -16,26 +16,27 @@
*/ */
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;
@ViewById @ViewById
protected TextView separatorText; protected TextView separatorText;
public FilterSeparatorView(Context context) { public FilterSeparatorView(Context context) {
super(context); super(context);
} }
@ -47,9 +48,8 @@ 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;
} }
} }

38
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.
@ -44,18 +44,19 @@ public class Label implements SimpleListItem, NavigationFilter, Comparable<Label
this.count = count; this.count = count;
this.isEmptyLabel = isEmptyLabel; this.isEmptyLabel = isEmptyLabel;
} }
public Label(org.transdroid.daemon.Label daemonLabel) { public Label(org.transdroid.daemon.Label daemonLabel) {
this(daemonLabel.getName(), daemonLabel.getCount(), false); this(daemonLabel.getName(), daemonLabel.getCount(), false);
} }
@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;
} }
@Override @Override
public String getCode() { public String getCode() {
// Use the class name and label name to provide a unique navigation filter code // Use the class name and label name to provide a unique navigation filter code
@ -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,22 +90,22 @@ 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;
@ -134,7 +136,7 @@ public class Label implements SimpleListItem, NavigationFilter, Comparable<Label
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name); dest.writeString(name);
dest.writeInt(count); dest.writeInt(count);
dest.writeInt(isEmptyLabel? 1: 0); dest.writeInt(isEmptyLabel ? 1 : 0);
} }
} }

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();

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

@ -42,8 +42,8 @@ import de.keyboardsurfer.android.widget.crouton.Crouton;
import de.keyboardsurfer.android.widget.crouton.Style; 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")
@ -53,13 +53,11 @@ public class NavigationHelper {
/** /**
* Use with {@link Crouton#showText(android.app.Activity, int, Style)} (and variants) to display error messages. * Use with {@link Crouton#showText(android.app.Activity, int, Style)} (and variants) to display error messages.
*/ */
public static Style CROUTON_ERROR_STYLE = public static Style CROUTON_ERROR_STYLE = new Style.Builder().setBackgroundColor(R.color.crouton_error).setTextSize(13).build();
new Style.Builder().setBackgroundColor(R.color.crouton_error).setTextSize(13).build();
/** /**
* Use with {@link Crouton#showText(android.app.Activity, int, Style)} (and variants) to display info messages. * Use with {@link Crouton#showText(android.app.Activity, int, Style)} (and variants) to display info messages.
*/ */
public static Style CROUTON_INFO_STYLE = public static Style CROUTON_INFO_STYLE = new Style.Builder().setBackgroundColor(R.color.crouton_info).setTextSize(13).build();
new Style.Builder().setBackgroundColor(R.color.crouton_info).setTextSize(13).build();
private static ImageLoader imageCache; private static ImageLoader imageCache;
@RootContext @RootContext
protected Context context; protected Context context;
@ -67,8 +65,8 @@ public class NavigationHelper {
/** /**
* Converts a string into a {@link Spannable} that displays the string in the Roboto Condensed font * Converts a string into a {@link Spannable} that displays the string in the Roboto Condensed font
* @param string A plain text {@link String} * @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 * @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
* input string will be displayed using the Roboto Condensed font (if the OS has this) * using the Roboto Condensed font (if the OS has this)
*/ */
public static SpannableString buildCondensedFontString(String string) { public static SpannableString buildCondensedFontString(String string) {
if (string == null) { if (string == null) {
@ -136,8 +134,7 @@ public class NavigationHelper {
if (imageCache == null) { if (imageCache == null) {
imageCache = ImageLoader.getInstance(); imageCache = ImageLoader.getInstance();
try { try {
LruDiscCache diskCache = LruDiscCache diskCache = new LruDiscCache(context.getCacheDir(), null, new Md5FileNameGenerator(), 640000, 25);
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(
@ -158,19 +155,16 @@ public class NavigationHelper {
} }
/** /**
* Returns the application name (like Transdroid) and version name (like 1.5.0), appended by the version code (like * Returns the application name (like Transdroid) and version name (like 1.5.0), appended by the version code (like 180).
* 180).
* @return The app name and version, such as 'Transdroid 1.5.0 (180)' * @return The app name and version, such as 'Transdroid 1.5.0 (180)'
*/ */
public String getAppNameAndVersion() { public String getAppNameAndVersion() {
return context.getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME + " (" + return context.getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME + " (" + Integer.toString(BuildConfig.VERSION_CODE) + ")";
Integer.toString(BuildConfig.VERSION_CODE) + ")";
} }
/** /**
* Returns whether the device is considered small (i.e. a phone) rather than large (i.e. a tablet). Can, for * 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() {
@ -178,8 +172,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() {
@ -187,8 +181,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() {
@ -196,8 +189,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() {
@ -206,8 +199,8 @@ 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);

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

@ -16,17 +16,16 @@
*/ */
package org.transdroid.core.gui.navigation; package org.transdroid.core.gui.navigation;
import android.view.View; import android.support.v4.widget.SwipeRefreshLayout;
/** /**
* 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); void addSwipeRefreshLayout(SwipeRefreshLayout container);
} }

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);
} }
} }

30
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,19 +23,26 @@ 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 GETFVO_URL = "http://g.etfv.co/%1$s";
@Bean @Bean
protected NavigationHelper navigationHelper; protected NavigationHelper navigationHelper;
// Views // Views
@ViewById @ViewById
protected ImageView faviconImage; protected ImageView faviconImage;
@ -61,18 +62,17 @@ public class RssfeedView extends LinearLayout {
if (rssfeedLoader.hasError() || rssfeedLoader.getChannel() != null) { if (rssfeedLoader.hasError() || rssfeedLoader.getChannel() != null) {
loadingProgress.setVisibility(View.GONE); loadingProgress.setVisibility(View.GONE);
newcountText.setVisibility(View.VISIBLE); newcountText.setVisibility(View.VISIBLE);
newcountText.setText(rssfeedLoader.hasError()? "?": Integer.toString(rssfeedLoader.getNewCount())); newcountText.setText(rssfeedLoader.hasError() ? "?" : Integer.toString(rssfeedLoader.getNewCount()));
} else { } else {
loadingProgress.setVisibility(View.VISIBLE); loadingProgress.setVisibility(View.VISIBLE);
newcountText.setVisibility(View.GONE); newcountText.setVisibility(View.GONE);
} }
// 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(GETFVO_URL, rssfeedLoader.getSetting().getUrl()), faviconImage);
faviconImage);
} }
} }

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

@ -16,9 +16,14 @@
*/ */
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 org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Background; import org.androidannotations.annotations.Background;
@ -29,24 +34,22 @@ import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.UiThread; import org.androidannotations.annotations.UiThread;
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.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.content.Intent; import java.util.Date;
import android.net.Uri; import java.util.List;
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 de.keyboardsurfer.android.widget.crouton.Crouton; import de.keyboardsurfer.android.widget.crouton.Crouton;
@EActivity(resName = "activity_rssfeeds") @EActivity(R.layout.activity_rssfeeds)
public class RssfeedsActivity extends ActionBarActivity { public class RssfeedsActivity extends ActionBarActivity {
// Settings and local data // Settings and local data
@ -57,9 +60,9 @@ public class RssfeedsActivity extends ActionBarActivity {
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 @ViewById
protected Toolbar rssfeedsToolbar; protected Toolbar rssfeedsToolbar;
@ -96,7 +99,7 @@ public class RssfeedsActivity extends ActionBarActivity {
* 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()) {
@ -127,8 +130,7 @@ public class RssfeedsActivity extends ActionBarActivity {
} }
/** /**
* 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
@ -140,12 +142,10 @@ public class RssfeedsActivity extends ActionBarActivity {
} }
/** /**
* 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) {
@ -156,8 +156,9 @@ public class RssfeedsActivity extends ActionBarActivity {
// 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());
@ -178,14 +179,16 @@ public class RssfeedsActivity extends ActionBarActivity {
// 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();
} }

24
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}.
@ -33,9 +33,9 @@ import android.widget.BaseAdapter;
*/ */
@EBean @EBean
public class RssfeedsAdapter extends BaseAdapter { public class RssfeedsAdapter extends BaseAdapter {
private List<RssfeedLoader> loaders = null; private List<RssfeedLoader> loaders = null;
@RootContext @RootContext
protected Context context; protected Context context;
@ -47,23 +47,25 @@ public class RssfeedsAdapter extends BaseAdapter {
this.loaders = loaders; this.loaders = loaders;
notifyDataSetChanged(); notifyDataSetChanged();
} }
@Override @Override
public boolean hasStableIds() { public boolean hasStableIds() {
return true; return true;
} }
@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);
} }

46
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;
@ -59,39 +59,39 @@ public class RssfeedsFragment extends Fragment {
public void update(List<RssfeedLoader> loaders) { public void update(List<RssfeedLoader> loaders) {
rssfeedsAdapter.update(loaders); rssfeedsAdapter.update(loaders);
boolean hasSettings = !(loaders == null || loaders.size() == 0); boolean hasSettings = !(loaders == null || loaders.size() == 0);
feedsList.setVisibility(hasSettings ? View.VISIBLE: View.GONE); feedsList.setVisibility(hasSettings ? View.VISIBLE : View.GONE);
nosettingsText.setVisibility(hasSettings ? View.GONE: View.VISIBLE); nosettingsText.setVisibility(hasSettings ? View.GONE : View.VISIBLE);
getActivity().invalidateOptionsMenu(); getActivity().invalidateOptionsMenu();
} }
@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).setShowAsAction(hasFeeds ? MenuItem.SHOW_AS_ACTION_ALWAYS : MenuItem.SHOW_AS_ACTION_NEVER);
: 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);
} }
/** /**
* Notifies the contained list of RSS feeds that the underlying data has been changed. * Notifies the contained list of RSS feeds that the underlying data has been changed.
*/ */
public void notifyDataSetChanged() { public void notifyDataSetChanged() {
rssfeedsAdapter.notifyDataSetChanged(); rssfeedsAdapter.notifyDataSetChanged();
} }
} }

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

@ -26,8 +26,8 @@ import android.widget.RelativeLayout;
import org.transdroid.R; 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 {

17
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,9 +43,9 @@ 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());
} }

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

@ -16,6 +16,13 @@
*/ */
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;
@ -23,20 +30,12 @@ import org.androidannotations.annotations.FragmentById;
import org.androidannotations.annotations.OptionsItem; import org.androidannotations.annotations.OptionsItem;
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.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;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
@EActivity(resName = "activity_rssitems")
public class RssitemsActivity extends ActionBarActivity { public class RssitemsActivity extends ActionBarActivity {
@Extra @Extra
@ -44,7 +43,7 @@ public class RssitemsActivity extends ActionBarActivity {
@Extra @Extra
protected String rssfeedName; protected String rssfeedName;
@FragmentById(resName = "rssitems_fragment") @FragmentById(R.id.rssitems_fragment)
protected RssitemsFragment fragmentItems; protected RssitemsFragment fragmentItems;
@ViewById @ViewById
protected Toolbar rssfeedsToolbar; protected Toolbar rssfeedsToolbar;

22
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
@ -34,7 +34,7 @@ import android.widget.BaseAdapter;
public class RssitemsAdapter extends BaseAdapter { public class RssitemsAdapter extends BaseAdapter {
private Channel rssfeed = null; private Channel rssfeed = null;
@RootContext @RootContext
protected Context context; protected Context context;
@ -46,23 +46,25 @@ public class RssitemsAdapter extends BaseAdapter {
this.rssfeed = rssfeed; this.rssfeed = rssfeed;
notifyDataSetChanged(); notifyDataSetChanged();
} }
@Override @Override
public boolean hasStableIds() { public boolean hasStableIds() {
return true; return true;
} }
@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);
} }

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

@ -44,10 +44,10 @@ 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.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;
@ -60,7 +60,7 @@ 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 +69,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() {
@ -92,7 +92,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 +126,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,13 +145,10 @@ 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 {
@ -201,8 +197,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);

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

@ -106,6 +106,9 @@ 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) {
if (data == null || !data.hasExtra("SCAN_RESULT")) {
return null;
}
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")) { if (formatName != null && formatName.equals("QR_CODE")) {

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

@ -45,23 +45,24 @@ 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 ActionBarActivity implements ActionBar.OnNavigationListener { public class SearchActivity extends ActionBarActivity implements ActionBar.OnNavigationListener {
@FragmentById(resName = "searchresults_fragment") @FragmentById(R.id.searchresults_fragment)
protected SearchResultsFragment fragmentResults; protected SearchResultsFragment fragmentResults;
@ViewById @ViewById
protected ListView searchsitesList; protected ListView searchsitesList;
@ -76,8 +77,7 @@ public class SearchActivity extends ActionBarActivity implements ActionBar.OnNav
@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;
@ -96,7 +96,6 @@ public class SearchActivity extends ActionBarActivity implements ActionBar.OnNav
// 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);
getSupportActionBar().setIcon(R.drawable.ic_activity_torrents);
} }
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
} }
@ -252,7 +251,7 @@ public class SearchActivity extends ActionBarActivity implements ActionBar.OnNav
} }
@OptionsItem(resName = "action_refresh") @OptionsItem(R.id.action_refresh)
protected void refreshSearch() { protected void refreshSearch() {
if (searchMenu != null) { if (searchMenu != null) {
@ -264,8 +263,7 @@ public class SearchActivity extends ActionBarActivity implements ActionBar.OnNav
// 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) {
@ -273,15 +271,15 @@ public class SearchActivity extends ActionBarActivity implements ActionBar.OnNav
// 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)
getSupportActionBar().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);
} }

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

@ -42,9 +42,11 @@ 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.NavigationHelper_;
import org.transdroid.core.gui.navigation.SelectionManagerMode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -55,7 +57,7 @@ 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 +68,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,7 +126,7 @@ 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); Crouton.showText(getActivity(), R.string.error_notorrentfile, NavigationHelper.CROUTON_ERROR_STYLE);
@ -212,4 +142,76 @@ 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);
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);
}
};
} }

2
app/src/main/java/org/transdroid/core/gui/search/SearchSetting.java

@ -25,7 +25,7 @@ public interface SearchSetting extends SimpleListItem {
* @return A unique string identifying this search setting * @return A unique string identifying this search setting
*/ */
public String getKey(); public String getKey();
/** /**
* Should return an URL (which may still be abstract and not the actual search URL) specific to the search site * Should return an URL (which may still be abstract and not the actual search URL) specific to the search site
* @return A clean URL directing to the search site, to, for example, get the favicon of the site * @return A clean URL directing to the search site, to, for example, get the favicon of the site

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);
} }

24
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}.
@ -34,9 +34,9 @@ import android.widget.BaseAdapter;
*/ */
@EBean @EBean
public class SearchSitesAdapter extends BaseAdapter { public class SearchSitesAdapter extends BaseAdapter {
private List<SearchSetting> sites = null; private List<SearchSetting> sites = null;
@RootContext @RootContext
protected Context context; protected Context context;
@ -48,23 +48,25 @@ public class SearchSitesAdapter extends BaseAdapter {
this.sites = sites; this.sites = sites;
notifyDataSetChanged(); notifyDataSetChanged();
} }
@Override @Override
public boolean hasStableIds() { public boolean hasStableIds() {
return true; return true;
} }
@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);
} }

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

@ -16,9 +16,6 @@
*/ */
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.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.AlertDialog; import android.app.AlertDialog;
@ -34,11 +31,14 @@ import android.text.TextUtils;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.EditText; import android.widget.EditText;
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") @SuppressLint("ValidFragment")
@ -48,34 +48,32 @@ public class UrlEntryDialog {
public android.app.Dialog onCreateDialog(android.os.Bundle savedInstanceState) { public android.app.Dialog onCreateDialog(android.os.Bundle savedInstanceState) {
final EditText urlInput = new EditText(activity); final EditText urlInput = new EditText(activity);
urlInput.setInputType(InputType.TYPE_TEXT_VARIATION_URI); urlInput.setInputType(InputType.TYPE_TEXT_VARIATION_URI);
if (android.os.Build.VERSION.SDK_INT >= 11) { ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
ClipboardManager clipboard = (ClipboardManager) activity if (clipboard.hasPrimaryClip() && clipboard.getPrimaryClip().getItemCount() > 0) {
.getSystemService(Context.CLIPBOARD_SERVICE); CharSequence content = clipboard.getPrimaryClip().getItemAt(0).coerceToText(activity);
if (clipboard.hasPrimaryClip() && clipboard.getPrimaryClip().getItemCount() > 0) { urlInput.setText(content);
CharSequence content = clipboard.getPrimaryClip().getItemAt(0).coerceToText(activity);
urlInput.setText(content);
}
} }
((InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE)).toggleSoftInput( ((InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE))
InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); .toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY);
return new AlertDialog.Builder(activity).setView(urlInput) return new AlertDialog.Builder(activity).setView(urlInput).setPositiveButton(android.R.string.ok, new OnClickListener() {
.setPositiveButton(android.R.string.ok, new OnClickListener() { @Override
@Override public void onClick(DialogInterface dialog, int which) {
public void onClick(DialogInterface dialog, int which) { // Assume text entry box input as URL and treat the filename (after the last /) as title
// Assume text entry box input as URL and treat the filename (after the last /) as title String url = urlInput.getText().toString();
String url = urlInput.getText().toString(); Uri uri = Uri.parse(url);
Uri uri = Uri.parse(url); if (!TextUtils.isEmpty(url)) {
if (activity != null && !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); } else {
} else { activity.addTorrentByUrl(url, title);
activity.addTorrentByUrl(url, title);
}
}
} }
}).setNegativeButton(android.R.string.cancel, null).create(); }
}; }
}).setNegativeButton(android.R.string.cancel, null).create();
}
;
}.show(activity.getFragmentManager(), "urlentry"); }.show(activity.getFragmentManager(), "urlentry");
} }

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_discard_dark.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 580 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 678 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 556 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 652 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 911 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 994 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 750 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 867 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 892 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 842 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 961 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 912 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 956 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 394 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 398 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

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

Loading…
Cancel
Save