Browse Source

Merge pull request #559 from TacoTheDank/master

Lots of code cleanup
pull/565/head
Eric Kok 4 years ago committed by GitHub
parent
commit
6f07529b6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      .editorconfig
  2. 107
      README.md
  3. 2
      app/build.gradle
  4. 3
      app/src/full/res/values/bools.xml
  5. 3
      app/src/lite/res/values/bools.xml
  6. 4
      app/src/lite/res/values/strings.xml
  7. 24
      app/src/main/AndroidManifest.xml
  8. 3
      app/src/main/java/androidx/preference/PreferenceManagerBinder.java
  9. 32
      app/src/main/java/org/transdroid/core/app/search/SearchHelper.java
  10. 48
      app/src/main/java/org/transdroid/core/app/search/SearchResult.java
  11. 1
      app/src/main/java/org/transdroid/core/app/search/SearchSite.java
  12. 93
      app/src/main/java/org/transdroid/core/app/settings/ApplicationSettings.java
  13. 8
      app/src/main/java/org/transdroid/core/app/settings/NotificationSettings.java
  14. 12
      app/src/main/java/org/transdroid/core/app/settings/RssfeedSetting.java
  15. 6
      app/src/main/java/org/transdroid/core/app/settings/ServerSetting.java
  16. 14
      app/src/main/java/org/transdroid/core/app/settings/SettingsPersistence.java
  17. 3
      app/src/main/java/org/transdroid/core/app/settings/SettingsUtils.java
  18. 4
      app/src/main/java/org/transdroid/core/app/settings/SystemSettings.java
  19. 13
      app/src/main/java/org/transdroid/core/app/settings/WebsearchSetting.java
  20. 9
      app/src/main/java/org/transdroid/core/gui/DetailsActivity.java
  21. 363
      app/src/main/java/org/transdroid/core/gui/DetailsFragment.java
  22. 37
      app/src/main/java/org/transdroid/core/gui/ServerPickerDialog.java
  23. 2
      app/src/main/java/org/transdroid/core/gui/ServerSelectionView.java
  24. 9
      app/src/main/java/org/transdroid/core/gui/ServerStatusView.java
  25. 4
      app/src/main/java/org/transdroid/core/gui/TorrentTasksExecutor.java
  26. 131
      app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java
  27. 280
      app/src/main/java/org/transdroid/core/gui/TorrentsFragment.java
  28. 12
      app/src/main/java/org/transdroid/core/gui/TransdroidApp.java
  29. 31
      app/src/main/java/org/transdroid/core/gui/lists/DetailsAdapter.java
  30. 86
      app/src/main/java/org/transdroid/core/gui/lists/LocalTorrent.java
  31. 21
      app/src/main/java/org/transdroid/core/gui/lists/MergeAdapter.java
  32. 30
      app/src/main/java/org/transdroid/core/gui/lists/PiecesMapView.java
  33. 3
      app/src/main/java/org/transdroid/core/gui/lists/SimpleListItem.java
  34. 25
      app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemAdapter.java
  35. 6
      app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemSpinnerAdapter.java
  36. 1
      app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemView.java
  37. 3
      app/src/main/java/org/transdroid/core/gui/lists/SortByListItem.java
  38. 2
      app/src/main/java/org/transdroid/core/gui/lists/TorrentDetailsView.java
  39. 14
      app/src/main/java/org/transdroid/core/gui/lists/TorrentFilePriorityLayout.java
  40. 1
      app/src/main/java/org/transdroid/core/gui/lists/TorrentFileView.java
  41. 41
      app/src/main/java/org/transdroid/core/gui/lists/TorrentProgressBar.java
  42. 15
      app/src/main/java/org/transdroid/core/gui/lists/TorrentStatusLayout.java
  43. 1
      app/src/main/java/org/transdroid/core/gui/lists/TorrentView.java
  44. 5
      app/src/main/java/org/transdroid/core/gui/lists/TorrentsAdapter.java
  45. 4
      app/src/main/java/org/transdroid/core/gui/lists/ViewHolderAdapter.java
  46. 10
      app/src/main/java/org/transdroid/core/gui/log/DatabaseHelper.java
  47. 39
      app/src/main/java/org/transdroid/core/gui/log/ErrorLogEntry.java
  48. 16
      app/src/main/java/org/transdroid/core/gui/log/ErrorLogSender.java
  49. 1
      app/src/main/java/org/transdroid/core/gui/log/Log.java
  50. 66
      app/src/main/java/org/transdroid/core/gui/navigation/DialogHelper.java
  51. 17
      app/src/main/java/org/transdroid/core/gui/navigation/FilterListAdapter.java
  52. 3
      app/src/main/java/org/transdroid/core/gui/navigation/FilterListItemAdapter.java
  53. 1
      app/src/main/java/org/transdroid/core/gui/navigation/FilterListItemView.java
  54. 2
      app/src/main/java/org/transdroid/core/gui/navigation/FilterSeparatorView.java
  55. 87
      app/src/main/java/org/transdroid/core/gui/navigation/Label.java
  56. 4
      app/src/main/java/org/transdroid/core/gui/navigation/NavigationFilter.java
  57. 154
      app/src/main/java/org/transdroid/core/gui/navigation/NavigationHelper.java
  58. 1
      app/src/main/java/org/transdroid/core/gui/navigation/RefreshableActivity.java
  59. 9
      app/src/main/java/org/transdroid/core/gui/navigation/SelectionManagerMode.java
  60. 39
      app/src/main/java/org/transdroid/core/gui/navigation/SelectionModificationSpinner.java
  61. 21
      app/src/main/java/org/transdroid/core/gui/navigation/SetLabelDialog.java
  62. 6
      app/src/main/java/org/transdroid/core/gui/navigation/SetStorageLocationDialog.java
  63. 9
      app/src/main/java/org/transdroid/core/gui/navigation/SetTrackersDialog.java
  64. 37
      app/src/main/java/org/transdroid/core/gui/navigation/SetTransferRatesDialog.java
  65. 44
      app/src/main/java/org/transdroid/core/gui/navigation/StatusType.java
  66. 7
      app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssFragment.java
  67. 1
      app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssItemView.java
  68. 3
      app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssItemsAdapter.java
  69. 159
      app/src/main/java/org/transdroid/core/gui/rss/RssFeedsActivity.java
  70. 4
      app/src/main/java/org/transdroid/core/gui/rss/RssFeedsFragment.java
  71. 1
      app/src/main/java/org/transdroid/core/gui/rss/RssItemsActivity.java
  72. 15
      app/src/main/java/org/transdroid/core/gui/rss/RssItemsFragment.java
  73. 13
      app/src/main/java/org/transdroid/core/gui/rss/RssfeedLoader.java
  74. 1
      app/src/main/java/org/transdroid/core/gui/rss/RssfeedView.java
  75. 5
      app/src/main/java/org/transdroid/core/gui/rss/RssfeedsAdapter.java
  76. 5
      app/src/main/java/org/transdroid/core/gui/rss/RssitemStatusLayout.java
  77. 1
      app/src/main/java/org/transdroid/core/gui/rss/RssitemView.java
  78. 5
      app/src/main/java/org/transdroid/core/gui/rss/RssitemsAdapter.java
  79. 11
      app/src/main/java/org/transdroid/core/gui/search/BarcodeHelper.java
  80. 14
      app/src/main/java/org/transdroid/core/gui/search/FilePickerHelper.java
  81. 62
      app/src/main/java/org/transdroid/core/gui/search/SearchActivity.java
  82. 1
      app/src/main/java/org/transdroid/core/gui/search/SearchHistoryProvider.java
  83. 11
      app/src/main/java/org/transdroid/core/gui/search/SearchResultView.java
  84. 5
      app/src/main/java/org/transdroid/core/gui/search/SearchResultsAdapter.java
  85. 144
      app/src/main/java/org/transdroid/core/gui/search/SearchResultsFragment.java
  86. 6
      app/src/main/java/org/transdroid/core/gui/search/SearchSetting.java
  87. 1
      app/src/main/java/org/transdroid/core/gui/search/SearchSettingSelectionView.java
  88. 3
      app/src/main/java/org/transdroid/core/gui/search/SearchSettingsDropDownAdapter.java
  89. 1
      app/src/main/java/org/transdroid/core/gui/search/SearchSiteView.java
  90. 5
      app/src/main/java/org/transdroid/core/gui/search/SearchSitesAdapter.java
  91. 2
      app/src/main/java/org/transdroid/core/gui/search/SendIntentHelper.java
  92. 12
      app/src/main/java/org/transdroid/core/gui/search/UrlEntryDialog.java
  93. 7
      app/src/main/java/org/transdroid/core/gui/settings/AboutDialog.java
  94. 7
      app/src/main/java/org/transdroid/core/gui/settings/ChangelogDialog.java
  95. 53
      app/src/main/java/org/transdroid/core/gui/settings/HelpSettingsActivity.java
  96. 22
      app/src/main/java/org/transdroid/core/gui/settings/KeyBoundPreferencesActivity.java
  97. 75
      app/src/main/java/org/transdroid/core/gui/settings/MainSettingsActivity.java
  98. 2
      app/src/main/java/org/transdroid/core/gui/settings/PreferenceCompatActivity.java
  99. 2
      app/src/main/java/org/transdroid/core/gui/settings/RingtonePreference.java
  100. 17
      app/src/main/java/org/transdroid/core/gui/settings/RssfeedPreference.java
  101. Some files were not shown because too many files have changed in this diff Show More

9
.editorconfig

@ -0,0 +1,9 @@
# editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

107
README.md

@ -1,44 +1,65 @@
Transdroid Transdroid
========== ==========
[www.transdroid.org](http://www.transdroid.org) [www.transdroid.org](https://www.transdroid.org/)
[Twitter](https://twitter.com/transdroid) - [transdroid@2312.nl](transdroid@2312.nl) [Twitter](https://twitter.com/transdroid) - [transdroid@2312.nl](transdroid@2312.nl)
"Manage your torrents from your Android device" Manage torrents from your Android device.
<a href="https://transdroid.org/latest" target="_blank"> <a href="https://transdroid.org/latest">
<img src="https://transdroid.org/images/getontransdroid.png" alt="Get it on transdroid.org" height="80"/></a> <img src="https://transdroid.org/images/getontransdroid.png"
<a href="https://f-droid.org/repository/browse/?fdid=org.transdroid.full" target="_blank"> alt="Get it on transdroid.org"
<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="80"/></a> height="80">
<a href="https://play.google.com/store/apps/details?id=org.transdroid.lite" target="_blank"> </a>
<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" alt="Get it on Google Play" height="80"/></a> <a href="https://f-droid.org/packages/org.transdroid.full/">
<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
<img src="http://2312.nl/images/screenshot_transdroid_main.png" alt="Screen shot of the main torrents listing screen" width="280" /> alt="Get it on F-Droid"
height="80">
</a>
Manage your torrents from your Android device with Transdroid. All popular clients are supported: µTorrent, Transmission, rTorrent, Vuze, Deluge, BitTorrent 6, qBittorrent and many more. You can view and manage the running torrents and individual files. Adding is easy via the integrated search or RSS feeds (full version required). Monitor progress using the home screen widget or background alarm service. <a href="https://play.google.com/store/apps/details?id=org.transdroid.lite">
<img src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png"
alt="Get it on Google Play"
height="80">
</a>
<img src="https://2312.nl/images/screenshot_transdroid_main.png" alt="Screen shot of the main torrents listing screen" width="280" />
Manage your torrents from your Android device with Transdroid.
All popular clients are supported: µTorrent, Transmission, rTorrent, Vuze, Deluge, BitTorrent 6, qBittorrent, and many more.
You can view and manage running torrents and individual files.
Adding is easy via the integrated search or RSS feeds (full version required).
Monitor progress using the home screen widget or background alarm service.
Contributions Contributions
============= =============
Code and design contributions are very welcome. You might want to contact me via social networks (G+, Twitter) or e-mail first. Please note all code will be GNU GPL v3 licensed. Code and design contributions are very welcome.
You might want to contact me via social networks (Twitter) or e-mail first.
Please note that all code will be licensed in GNU GPLv3.
Please respect the coding standards for easier merging. master contains the current release version of Transdroid while dev contains the active development version. However, larger, new features are developed in their own branch. Please respect the coding standards for easier merging.
`master` contains the current release version of Transdroid while `dev` contains the active development version.
However, larger and new features will be developed in their own branch.
Code structure Code structure
============== ==============
Starting with version 2.3.0, Transdroid is developed in Android Studio, fully integrating with the Gradle build system. It is (since version 2.5.0) compiled against Android 5.1 (API level 22) and (since version 2.2.0) supporting ICS (API level 15) and up only. To support lite (Transdrone, specially for the Play Store) and full (Transdroid) versions of the app, build flavours are defined in gradle, which contain version-specific resources. Dependencies are managed via JCentral et al. in the app's build.gradle file. Starting with version 2.3.0, Transdroid is developed in Android Studio, fully integrating with the Gradle build system.
It is (since version 2.5.18) compiled against Android 10 (API level 29) and (since version 2.2.0) supporting Android ICS (API level 15) and up only.
To support lite (Transdrone, specially for the Play Store) and full (Transdroid) versions of the app, build flavours are defined in gradle, which contain version-specific resources.
Dependencies are managed via JCentral et al. in the app's build.gradle file.
Developed By Developed By
============ ============
Designed and developed by [Eric Kok](eric@2312.nl) of [2312 development](http://2312.nl). Contributions by various others (see commit log). Designed and developed by [Eric Kok](eric@2312.nl) of [2312 development](https://2312.nl/).
Contributions by various others (see commit log).
License License
======= =======
Copyright 2010-2018 Eric Kok et al. Copyright 2010-2020 Eric Kok et al.
Transdroid is free software: you can redistribute it and/or modify Transdroid is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -51,39 +72,51 @@ License
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with Transdroid. If not, see <http://www.gnu.org/licenses/>. along with Transdroid. If not, see <https://www.gnu.org/licenses/>.
Some code/libraries/resources are used in the project: Some code/libraries/resources are used in the project:
* [Android Jetpack (AndroidX)](https://developer.android.com/jetpack)
The Android Open Source Project
Apache License, Version 2.0
* [AndroidAnnotations](http://androidannotations.org/) * [AndroidAnnotations](http://androidannotations.org/)
Pierre-Yves Ricau (eBusinessInformations) et al. Pierre-Yves Ricau (eBusinessInformations) et al.
Apache License, Version 2.0 Apache License, Version 2.0
* [ActionBar-PullToRefresh](https://github.com/chrisbanes/ActionBar-PullToRefresh) * [ORMLite](https://github.com/j256/ormlite-core) and [ORMLite Android](https://github.com/j256/ormlite-android)
Chris Banes Gray Watson
ISC License
* [Android Universal Image Loader](https://github.com/nostra13/Android-Universal-Image-Loader)
Sergey Tarasevich
Apache License, Version 2.0 Apache License, Version 2.0
* [Crouton](https://github.com/keyboardsurfer/Crouton) * [FloatingActionButton](https://github.com/zendesk/android-floating-action-button)
Code: Benjamin Weiss (Neofonie Mobile Gmbh) et al. Oleksandr Melnykov, Zendesk
Idea: Cyril Mottier
Apache License, Version 2.0 Apache License, Version 2.0
* [Base16Encoder](http://openjpa.apache.org/) * [Snackbar](https://github.com/nispok/snackbar)
William Mora
MIT License
* [Java implementation of Rencode](https://github.com/aegnor/rencode-java)
Daniel Dimovski
MIT License
* [OpenJPA's Base16Encoder](https://github.com/apache/openjpa)
Marc Prud'hommeaux Marc Prud'hommeaux
Apache OpenJPA Apache OpenJPA
* MultipartEntity * [Base64](http://iharder.sourceforge.net/current/java/base64/)
Apache Software Foundation
Apache License, Version 2.0
* RssParser ([learning-android](http://github.com/digitalspaghetti/learning-android))
Tane Piper
Public Domain
* [Base64](http://iharder.net/base64)
Robert Harder Robert Harder
Public Domain Public Domain
* [aXMLRPC](https://github.com/timroes/aXMLRPC) * [aXMLRPC](https://github.com/gturri/aXMLRPC)
Tim Roes Tim Roes
MIT License MIT License
* [Material Dialogs](https://github.com/afollestad/material-dialogs)
Aidan Follestad
Apache License, Version 2.0
* [Android-Job](https://github.com/evernote/android-job)
Evernote Corporation
Apache License, Version 2.0
* [android-ColorPickerPreference](https://github.com/attenzione/android-ColorPickerPreference) * [android-ColorPickerPreference](https://github.com/attenzione/android-ColorPickerPreference)
Daniel Nilsson and Sergey Margaritov Daniel Nilsson and Sergey Margaritov
Apache License, Version 2.0 Apache License, Version 2.0
* [Funnel icon](http://thenounproject.com/noun/funnel/#icon-No5608) * RssParser ([learning-android](https://github.com/tanepiper/learning-android))
Tane Piper
Public Domain
* [Funnel icon](https://thenounproject.com/term/funnel/5608/)
Naomi Atkinson from The Noun Project Naomi Atkinson from The Noun Project
Creative Commons Attribution 3.0 Creative Commons Attribution 3.0

2
app/build.gradle

@ -79,7 +79,7 @@ dependencies {
// Android support // Android support
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.preference:preference:1.1.1' implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.google.android.material:material:1.1.0' implementation 'com.google.android.material:material:1.1.0'
// Other // Other

3
app/src/full/res/values/bools.xml

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!--
<!--
Copyright 2010-2018 Eric Kok et al. Copyright 2010-2018 Eric Kok et al.
Transdroid is free software: you can redistribute it and/or modify Transdroid is free software: you can redistribute it and/or modify

3
app/src/lite/res/values/bools.xml

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!--
<!--
Copyright 2010-2018 Eric Kok et al. Copyright 2010-2018 Eric Kok et al.
Transdroid is free software: you can redistribute it and/or modify Transdroid is free software: you can redistribute it and/or modify

4
app/src/lite/res/values/strings.xml

@ -18,7 +18,7 @@
<string name="app_name" translatable="false">Transdrone</string> <string name="app_name" translatable="false">Transdrone</string>
<string name="donate_text"></string> <string name="donate_text" />
<string name="donate_url"></string> <string name="donate_url" />
</resources> </resources>

24
app/src/main/AndroidManifest.xml

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!--
<!--
Copyright 2010-2018 Eric Kok et al. Copyright 2010-2018 Eric Kok et al.
Transdroid is free software: you can redistribute it and/or modify Transdroid is free software: you can redistribute it and/or modify
@ -17,7 +16,7 @@
--> -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="org.transdroid" > package="org.transdroid">
<uses-sdk /> <uses-sdk />
@ -51,10 +50,11 @@
<application <application
android:name=".core.gui.TransdroidApp_" android:name=".core.gui.TransdroidApp_"
android:allowBackup="true" android:allowBackup="true"
android:banner="@drawable/banner"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:banner="@drawable/banner"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat" android:theme="@style/Theme.AppCompat"
android:usesCleartextTraffic="true"> android:usesCleartextTraffic="true">
@ -69,7 +69,7 @@
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/TransdroidTheme" android:theme="@style/TransdroidTheme"
android:windowSoftInputMode="stateHidden" > android:windowSoftInputMode="stateHidden">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -193,8 +193,7 @@
<activity <activity
android:name="org.transdroid.core.gui.DetailsActivity_" android:name="org.transdroid.core.gui.DetailsActivity_"
android:theme="@style/TransdroidTheme" android:theme="@style/TransdroidTheme"
android:uiOptions="splitActionBarWhenNarrow" > android:uiOptions="splitActionBarWhenNarrow" />
</activity>
<!-- Settings screens --> <!-- Settings screens -->
<activity <activity
@ -245,7 +244,7 @@
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:label="@string/search_torrentsearch" android:label="@string/search_torrentsearch"
android:launchMode="singleTask" android:launchMode="singleTask"
android:theme="@style/TransdroidTheme" > android:theme="@style/TransdroidTheme">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEARCH" /> <action android:name="android.intent.action.SEARCH" />
@ -283,7 +282,7 @@
android:label="@string/rss_feeds" android:label="@string/rss_feeds"
android:theme="@style/TransdroidTheme" /> android:theme="@style/TransdroidTheme" />
<receiver android:name="org.transdroid.core.service.BootReceiver_" > <receiver android:name="org.transdroid.core.service.BootReceiver_">
<intent-filter> <intent-filter>
<action <action
android:name="android.intent.action.BOOT_COMPLETED" android:name="android.intent.action.BOOT_COMPLETED"
@ -294,7 +293,7 @@
<service <service
android:name="org.transdroid.core.service.ControlService_" android:name="org.transdroid.core.service.ControlService_"
android:exported="true" android:exported="true"
tools:ignore="ExportedService" > tools:ignore="ExportedService">
<intent-filter> <intent-filter>
<action android:name="org.transdroid.control.SET_TRANSFER_RATES" /> <action android:name="org.transdroid.control.SET_TRANSFER_RATES" />
<action android:name="org.transdroid.control.PAUSE_ALL" /> <action android:name="org.transdroid.control.PAUSE_ALL" />
@ -307,7 +306,7 @@
<!-- Home screen widget --> <!-- Home screen widget -->
<activity <activity
android:name="org.transdroid.core.widget.ListWidgetConfigActivity_" android:name="org.transdroid.core.widget.ListWidgetConfigActivity_"
android:theme="@style/TransdroidTheme.WidgetConfig" > android:theme="@style/TransdroidTheme.WidgetConfig">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" /> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter> </intent-filter>
@ -318,8 +317,7 @@
android:exported="false" android:exported="false"
android:permission="android.permission.BIND_REMOTEVIEWS" /> android:permission="android.permission.BIND_REMOTEVIEWS" />
<receiver <receiver android:name="org.transdroid.core.widget.ListWidgetProvider_">
android:name="org.transdroid.core.widget.ListWidgetProvider_">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter> </intent-filter>

3
app/src/main/java/androidx/preference/PreferenceManagerBinder.java

@ -5,7 +5,8 @@ package androidx.preference;
* around the protected visibility of {@link Preference#onAttachedToHierarchy(PreferenceManager)}. * around the protected visibility of {@link Preference#onAttachedToHierarchy(PreferenceManager)}.
*/ */
public class PreferenceManagerBinder { public class PreferenceManagerBinder {
private PreferenceManagerBinder() {} private PreferenceManagerBinder() {
}
public static void bind(Preference pref, PreferenceManager manager) { public static void bind(Preference pref, PreferenceManager manager) {
pref.onAttachedToHierarchy(manager); pref.onAttachedToHierarchy(manager);

32
app/src/main/java/org/transdroid/core/app/search/SearchHelper.java

@ -16,6 +16,15 @@
*/ */
package org.transdroid.core.app.search; package org.transdroid.core.app.search;
import android.content.ContentProviderClient;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import org.androidannotations.annotations.EBean;
import org.androidannotations.annotations.EBean.Scope;
import org.androidannotations.annotations.RootContext;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
@ -23,15 +32,6 @@ import java.net.URLEncoder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.androidannotations.annotations.EBean;
import org.androidannotations.annotations.EBean.Scope;
import org.androidannotations.annotations.RootContext;
import android.content.ContentProviderClient;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
@EBean(scope = Scope.Singleton) @EBean(scope = Scope.Singleton)
public class SearchHelper { public class SearchHelper {
@ -53,12 +53,9 @@ public class SearchHelper {
@RootContext @RootContext
protected Context context; protected Context context;
public enum SearchSortOrder {
Combined, BySeeders
}
/** /**
* Return whether the Torrent Search package is installed and available to query against * Return whether the Torrent Search package is installed and available to query against
*
* @return True if the available sites can be retrieved from the content provider, false otherwise * @return True if the available sites can be retrieved from the content provider, false otherwise
*/ */
public boolean isTorrentSearchInstalled() { public boolean isTorrentSearchInstalled() {
@ -67,6 +64,7 @@ public class SearchHelper {
/** /**
* Queries the Torrent Search package for all available in-app search sites. This method is synchronous. * Queries the Torrent Search package for all available in-app search sites. This method is synchronous.
*
* @return A list of available search sites as POJOs, or null if the Torrent Search package is not installed * @return A list of available search sites as POJOs, or null if the Torrent Search package is not installed
*/ */
public List<SearchSite> getAvailableSites() { public List<SearchSite> getAvailableSites() {
@ -103,6 +101,7 @@ public class SearchHelper {
/** /**
* Queries the Torrent Search module to search for torrents on the web. This method is synchronous and should always * Queries the Torrent Search module to search for torrents on the web. This method is synchronous and should always
* be called in a background thread. * be called in a background thread.
*
* @param query The search query to pass to the torrent site * @param query The search query to pass to the torrent site
* @param site The site to search, as retrieved from the TorrentSitesProvider, or null if the Torrent Search package * @param site The site to search, as retrieved from the TorrentSitesProvider, or null if the Torrent Search package
* @param sortBy The sort order to request from the torrent site, if supported * @param sortBy The sort order to request from the torrent site, if supported
@ -118,7 +117,7 @@ public class SearchHelper {
// If no explicit site was supplied, rely on the Torrent Search package's default // If no explicit site was supplied, rely on the Torrent Search package's default
cursor = context.getContentResolver().query(uri, null, null, null, sortBy.name()); cursor = context.getContentResolver().query(uri, null, null, null, sortBy.name());
} else { } else {
cursor = context.getContentResolver().query(uri, null, "SITE = ?", new String[] { site.getKey() }, cursor = context.getContentResolver().query(uri, null, "SITE = ?", new String[]{site.getKey()},
sortBy.name()); sortBy.name());
} }
if (cursor == null) { if (cursor == null) {
@ -148,6 +147,7 @@ public class SearchHelper {
* Asks the Torrent Search module to download a torrent file given the provided url, while using the specifics of * Asks the Torrent Search module to download a torrent file given the provided url, while using the specifics of
* the supplied torrent search site to do so. This way the Search Module can take care of user credentials, for * the supplied torrent search site to do so. This way the Search Module can take care of user credentials, for
* example. * example.
*
* @param site The unique key of the search site that this url belongs to, which is used to create a connection * @param site The unique key of the search site that this url belongs to, which is used to create a connection
* specific to this (private) site * specific to this (private) site
* @param url The full url of the torrent to download * @param url The full url of the torrent to download
@ -165,4 +165,8 @@ public class SearchHelper {
} }
} }
public enum SearchSortOrder {
Combined, BySeeders
}
} }

48
app/src/main/java/org/transdroid/core/app/search/SearchResult.java

@ -16,17 +16,27 @@
*/ */
package org.transdroid.core.app.search; package org.transdroid.core.app.search;
import java.util.Date;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import java.util.Date;
/** /**
* Represents a search result as retrieved by querying the Torrent Search package. * Represents a search result as retrieved by querying the Torrent Search package.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class SearchResult implements Parcelable { public class SearchResult implements Parcelable {
public static final Parcelable.Creator<SearchResult> CREATOR = new Parcelable.Creator<SearchResult>() {
public SearchResult createFromParcel(Parcel in) {
return new SearchResult(in);
}
public SearchResult[] newArray(int size) {
return new SearchResult[size];
}
};
private final int id; private final int id;
private final String name; private final String name;
private final String torrentUrl; private final String torrentUrl;
@ -48,6 +58,18 @@ public class SearchResult implements Parcelable {
this.leechers = leechers; this.leechers = leechers;
} }
public SearchResult(Parcel in) {
id = in.readInt();
name = in.readString();
torrentUrl = in.readString();
detailsUrl = in.readString();
size = in.readString();
long addedOnIn = in.readLong();
addedOn = addedOnIn == -1 ? null : new Date(addedOnIn);
seeders = in.readString();
leechers = in.readString();
}
public int getId() { public int getId() {
return id; return id;
} }
@ -97,26 +119,4 @@ public class SearchResult implements Parcelable {
out.writeString(leechers); out.writeString(leechers);
} }
public static final Parcelable.Creator<SearchResult> CREATOR = new Parcelable.Creator<SearchResult>() {
public SearchResult createFromParcel(Parcel in) {
return new SearchResult(in);
}
public SearchResult[] newArray(int size) {
return new SearchResult[size];
}
};
public SearchResult(Parcel in) {
id = in.readInt();
name = in.readString();
torrentUrl = in.readString();
detailsUrl = in.readString();
size = in.readString();
long addedOnIn = in.readLong();
addedOn = addedOnIn == -1 ? null : new Date(addedOnIn);
seeders = in.readString();
leechers = in.readString();
}
} }

1
app/src/main/java/org/transdroid/core/app/search/SearchSite.java

@ -21,6 +21,7 @@ import org.transdroid.core.gui.search.SearchSetting;
/** /**
* Represents an available torrent site that can be searched using the Torrent Search package. * Represents an available torrent site that can be searched using the Torrent Search package.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class SearchSite implements SimpleListItem, SearchSetting { public class SearchSite implements SimpleListItem, SearchSetting {

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

@ -49,6 +49,7 @@ import java.util.List;
/** /**
* Singleton object to access all application settings, including stored servers, web search sites and RSS feeds. * Singleton object to access all application settings, including stored servers, web search sites and RSS feeds.
*
* @author Eric Kok * @author Eric Kok
*/ */
@EBean(scope = Scope.Singleton) @EBean(scope = Scope.Singleton)
@ -59,9 +60,9 @@ public class ApplicationSettings {
@RootContext @RootContext
protected Context context; protected Context context;
private SharedPreferences prefs;
@Bean @Bean
protected SearchHelper searchHelper; protected SearchHelper searchHelper;
private SharedPreferences prefs;
protected ApplicationSettings(Context context) { protected ApplicationSettings(Context context) {
prefs = PreferenceManager.getDefaultSharedPreferences(context); prefs = PreferenceManager.getDefaultSharedPreferences(context);
@ -69,11 +70,11 @@ public class ApplicationSettings {
/** /**
* Returns all available user-configured normal and seed servers * Returns all available user-configured normal and seed servers
*
* @return A list of all stored server settings objects * @return A list of all stored server settings objects
*/ */
public List<ServerSetting> getAllServerSettings() { public List<ServerSetting> getAllServerSettings() {
List<ServerSetting> all = new ArrayList<>(); List<ServerSetting> all = new ArrayList<>(getNormalServerSettings());
all.addAll(getNormalServerSettings());
for (SeedboxProvider provider : SeedboxProvider.values()) { for (SeedboxProvider provider : SeedboxProvider.values()) {
all.addAll(provider.getSettings().getAllServerSettings(prefs, all.size())); all.addAll(provider.getSettings().getAllServerSettings(prefs, all.size()));
} }
@ -82,6 +83,7 @@ public class ApplicationSettings {
/** /**
* Returns the order number/identifying key of the last server, normal or seedbox configured * Returns the order number/identifying key of the last server, normal or seedbox configured
*
* @return The zero-based order number (index) of the last stored server settings * @return The zero-based order number (index) of the last stored server settings
*/ */
public int getMaxOfAllServers() { public int getMaxOfAllServers() {
@ -95,6 +97,7 @@ public class ApplicationSettings {
/** /**
* Returns the server settings for either a normal or a seedbox server as the user configured. WARNING: This method * Returns the server settings for either a normal or a seedbox server as the user configured. WARNING: This method
* does not check if the settings actually exist and may reply on empty default if called for a non-existing server. * does not check if the settings actually exist and may reply on empty default if called for a non-existing server.
*
* @param order The order number/identifying key of the server's settings to retrieve, where the normal servers are * @param order The order number/identifying key of the server's settings to retrieve, where the normal servers are
* first and the seedboxes are numbers thereafter onwards * first and the seedboxes are numbers thereafter onwards
* @return The server settings object, loaded from shared preferences * @return The server settings object, loaded from shared preferences
@ -116,6 +119,7 @@ public class ApplicationSettings {
/** /**
* Returns all available normal, user-configured servers (so no seedbox settings) * Returns all available normal, user-configured servers (so no seedbox settings)
*
* @return A list of all stored server settings objects * @return A list of all stored server settings objects
*/ */
public List<ServerSetting> getNormalServerSettings() { public List<ServerSetting> getNormalServerSettings() {
@ -128,6 +132,7 @@ public class ApplicationSettings {
/** /**
* Returns the order number/identifying key of the last normal server * Returns the order number/identifying key of the last normal server
*
* @return The zero-based order number (index) of the last stored normal server settings * @return The zero-based order number (index) of the last stored normal server settings
*/ */
public int getMaxNormalServer() { public int getMaxNormalServer() {
@ -140,6 +145,7 @@ public class ApplicationSettings {
/** /**
* Returns the user-specified server settings for a normal (non-seedbox) server. WARNING: This method does not check * Returns the user-specified server settings for a normal (non-seedbox) server. WARNING: This method does not check
* if the settings actually exist and may rely on empty defaults if called for a non-existing server. * if the settings actually exist and may rely on empty defaults if called for a non-existing server.
*
* @param order The order number/identifying key of the normal server's settings to retrieve * @param order The order number/identifying key of the normal server's settings to retrieve
* @return The server settings object, loaded from shared preferences * @return The server settings object, loaded from shared preferences
*/ */
@ -198,6 +204,7 @@ public class ApplicationSettings {
/** /**
* Removes all settings related to a configured server. Since servers are ordered, the order of the remaining * Removes all settings related to a configured server. Since servers are ordered, the order of the remaining
* servers will be updated accordingly. * servers will be updated accordingly.
*
* @param order The identifying order number/key of the settings to remove * @param order The identifying order number/key of the settings to remove
*/ */
public void removeNormalServerSettings(int order) { public void removeNormalServerSettings(int order) {
@ -275,6 +282,7 @@ public class ApplicationSettings {
* specific default server was selected, the last used server settings. As opposed to getDefaultServerKey(int), this * specific default server was selected, the last used server settings. As opposed to getDefaultServerKey(int), this
* method checks whether the particular server still exists (and returns the first server if not). If no servers are * method checks whether the particular server still exists (and returns the first server if not). If no servers are
* configured, null is returned. * configured, null is returned.
*
* @return A server settings object of the server to use by default, or null if no server is yet configured * @return A server settings object of the server to use by default, or null if no server is yet configured
*/ */
public ServerSetting getDefaultServer() { public ServerSetting getDefaultServer() {
@ -302,6 +310,7 @@ public class ApplicationSettings {
* Returns the unique key of the server setting that the user selected as their default server, or code indicating * Returns the unique key of the server setting that the user selected as their default server, or code indicating
* that the last used server should be selected by default; use with getDefaultServer directly. WARNING: the * that the last used server should be selected by default; use with getDefaultServer directly. WARNING: the
* returned string may no longer refer to a known server setting key. * returned string may no longer refer to a known server setting key.
*
* @return An integer; if it is 0 or higher it represents the unique key of a configured server setting, -2 means * @return An integer; if it is 0 or higher it represents the unique key of a configured server setting, -2 means
* the last used server should be selected as default instead and -1 means the last used server should be * the last used server should be selected as default instead and -1 means the last used server should be
* selected by default for viewing yet it should always ask when adding a new torrent * selected by default for viewing yet it should always ask when adding a new torrent
@ -320,6 +329,7 @@ public class ApplicationSettings {
* Returns the settings of the server that was last used by the user. As opposed to getLastUsedServerKey(int), this * Returns the settings of the server that was last used by the user. As opposed to getLastUsedServerKey(int), this
* method checks whether a server was already registered as being last used and check whether the server still * method checks whether a server was already registered as being last used and check whether the server still
* exists. It returns the first server if that fails. If no servers are configured, null is returned. * exists. It returns the first server if that fails. If no servers are configured, null is returned.
*
* @return A server settings object of the last used server (or, if not known, the first server), or null if no * @return A server settings object of the last used server (or, if not known, the first server), or null if no
* servers exist * servers exist
*/ */
@ -337,26 +347,29 @@ public class ApplicationSettings {
return getServerSetting(last); return getServerSetting(last);
} }
/**
* Registers some server as being the last used by the user
*
* @param server The settings of the server that the user last used
*/
public void setLastUsedServer(ServerSetting server) {
setLastUsedServerKey(server.getOrder());
}
/** /**
* Returns the order number/unique key of the server that the used last used; use with getServerSettings(int) or * Returns the order number/unique key of the server that the used last used; use with getServerSettings(int) or
* call getLastUsedServer directly. WARNING: the returned integer may no longer refer to a known server settings * call getLastUsedServer directly. WARNING: the returned integer may no longer refer to a known server settings
* object: check the bounds. * object: check the bounds.
*
* @return An integer indicating the order number/key or the last used server, or -1 if it was not set * @return An integer indicating the order number/key or the last used server, or -1 if it was not set
*/ */
public int getLastUsedServerKey() { public int getLastUsedServerKey() {
return prefs.getInt("system_lastusedserver", -1); return prefs.getInt("system_lastusedserver", -1);
} }
/**
* Registers some server as being the last used by the user
* @param server The settings of the server that the user last used
*/
public void setLastUsedServer(ServerSetting server) {
setLastUsedServerKey(server.getOrder());
}
/** /**
* Registers the order number/unique key of some server as being last used by the user * Registers the order number/unique key of some server as being last used by the user
*
* @param order The key identifying the specific server * @param order The key identifying the specific server
*/ */
public void setLastUsedServerKey(int order) { public void setLastUsedServerKey(int order) {
@ -366,6 +379,7 @@ public class ApplicationSettings {
/** /**
* Returns the unique code that (should) uniquely identify a navigation filter, such as a label, in the list of all * Returns the unique code that (should) uniquely identify a navigation filter, such as a label, in the list of all
* available filters * available filters
*
* @return A code that the last used navigation filter reported as uniquely identifying itself, or null if no last * @return A code that the last used navigation filter reported as uniquely identifying itself, or null if no last
* used filter is known * used filter is known
*/ */
@ -375,6 +389,7 @@ public class ApplicationSettings {
/** /**
* Registers some navigation filter as being the last used by the user * Registers some navigation filter as being the last used by the user
*
* @param filter The navigation filter that the user last used in the interface * @param filter The navigation filter that the user last used in the interface
*/ */
public void setLastUsedNavigationFilter(NavigationFilter filter) { public void setLastUsedNavigationFilter(NavigationFilter filter) {
@ -383,6 +398,7 @@ public class ApplicationSettings {
/** /**
* Returns all available user-configured web-based (as opped to in-app) search sites * Returns all available user-configured web-based (as opped to in-app) search sites
*
* @return A list of all stored web search site settings objects * @return A list of all stored web search site settings objects
*/ */
public List<WebsearchSetting> getWebsearchSettings() { public List<WebsearchSetting> getWebsearchSettings() {
@ -395,6 +411,7 @@ public class ApplicationSettings {
/** /**
* Returns the order number/identifying key of the last web search site * Returns the order number/identifying key of the last web search site
*
* @return The zero-based order number (index) of the last stored web search site * @return The zero-based order number (index) of the last stored web search site
*/ */
public int getMaxWebsearch() { public int getMaxWebsearch() {
@ -406,6 +423,7 @@ public class ApplicationSettings {
/** /**
* Returns the user-specified web-based search site setting for a specific site * Returns the user-specified web-based search site setting for a specific site
*
* @param order The order number/identifying key of the settings to retrieve * @param order The order number/identifying key of the settings to retrieve
* @return The web search site settings object, loaded from shared preferences * @return The web search site settings object, loaded from shared preferences
*/ */
@ -421,6 +439,7 @@ public class ApplicationSettings {
/** /**
* Removes all settings related to a configured web-based search site. Since sites are ordered, the order of the * Removes all settings related to a configured web-based search site. Since sites are ordered, the order of the
* remaining sites will be updated accordingly. * remaining sites will be updated accordingly.
*
* @param order The identifying order number/key of the settings to remove * @param order The identifying order number/key of the settings to remove
*/ */
public void removeWebsearchSettings(int order) { public void removeWebsearchSettings(int order) {
@ -446,6 +465,7 @@ public class ApplicationSettings {
/** /**
* Returns all available user-configured RSS feeds * Returns all available user-configured RSS feeds
*
* @return A list of all stored RSS feed settings objects * @return A list of all stored RSS feed settings objects
*/ */
public List<RssfeedSetting> getRssfeedSettings() { public List<RssfeedSetting> getRssfeedSettings() {
@ -458,6 +478,7 @@ public class ApplicationSettings {
/** /**
* Returns the order number/identifying key of the last stored RSS feed * Returns the order number/identifying key of the last stored RSS feed
*
* @return The zero-based order number (index) of the last stored RSS feed * @return The zero-based order number (index) of the last stored RSS feed
*/ */
public int getMaxRssfeed() { public int getMaxRssfeed() {
@ -469,6 +490,7 @@ public class ApplicationSettings {
/** /**
* Returns the user-specified RSS feed setting for a specific feed * Returns the user-specified RSS feed setting for a specific feed
*
* @param order The order number/identifying key of the settings to retrieve * @param order The order number/identifying key of the settings to retrieve
* @return The RSS feed settings object, loaded from shared preferences * @return The RSS feed settings object, loaded from shared preferences
*/ */
@ -490,6 +512,7 @@ public class ApplicationSettings {
/** /**
* Removes all settings related to a configured RSS feed. Since feeds are ordered, the order of the remaining feeds * Removes all settings related to a configured RSS feed. Since feeds are ordered, the order of the remaining feeds
* will be updated accordingly. * will be updated accordingly.
*
* @param order The identifying order number/key of the settings to remove * @param order The identifying order number/key of the settings to remove
*/ */
public void removeRssfeedSettings(int order) { public void removeRssfeedSettings(int order) {
@ -528,6 +551,7 @@ public class ApplicationSettings {
* the user. This is used to determine which items in an RSS feed are 'new'. Warning: any previously retrieved * the user. This is used to determine which items in an RSS feed are 'new'. Warning: any previously retrieved
* {@link RssfeedSetting} object is now no longer in sync, as this will not automatically be updated in the object. * {@link RssfeedSetting} object is now no longer in sync, as this will not automatically be updated in the object.
* Use {@link #getRssfeedSetting(int)} to get fresh data. * Use {@link #getRssfeedSetting(int)} to get fresh data.
*
* @param order The identifying order number/key of the settings of te RSS feed that was viewed * @param order The identifying order number/key of the settings of te RSS feed that was viewed
* @param lastViewed The date and time that the feed was last viewed; typically now * @param lastViewed The date and time that the feed was last viewed; typically now
* @param lastViewedItemUrl The url of the last item the last time that the feed was viewed * @param lastViewedItemUrl The url of the last item the last time that the feed was viewed
@ -543,6 +567,7 @@ public class ApplicationSettings {
/** /**
* Registers the torrents list sort order as being last used by the user * Registers the torrents list sort order as being last used by the user
*
* @param currentSortOrder The sort order property the user selected last * @param currentSortOrder The sort order property the user selected last
* @param currentSortAscending The sort order direction that was last used * @param currentSortAscending The sort order direction that was last used
*/ */
@ -556,6 +581,7 @@ public class ApplicationSettings {
/** /**
* Returns the sort order property that the user last used. Use together with {@link #getLastUsedSortDescending()} * Returns the sort order property that the user last used. Use together with {@link #getLastUsedSortDescending()}
* to get the full last used sort settings. * to get the full last used sort settings.
*
* @return The last used sort order enumeration value * @return The last used sort order enumeration value
*/ */
public TorrentsSortBy getLastUsedSortOrder() { public TorrentsSortBy getLastUsedSortOrder() {
@ -563,8 +589,18 @@ public class ApplicationSettings {
.getStatus(prefs.getInt("system_lastusedsortorder", TorrentsSortBy.Alphanumeric.getCode())); .getStatus(prefs.getInt("system_lastusedsortorder", TorrentsSortBy.Alphanumeric.getCode()));
} }
/**
* Returns the search sort order property that the user last used.
*
* @return The last used sort order enumeration value
*/
public SearchSortOrder getLastUsedSearchSortOrder() {
return SearchSortOrder.values()[(prefs.getInt("system_lastusedsearchsortorder", SearchSortOrder.BySeeders.ordinal()))];
}
/** /**
* Registers the search list sort order as being last used by the user * Registers the search list sort order as being last used by the user
*
* @param currentSortOrder The sort order property the user selected last * @param currentSortOrder The sort order property the user selected last
*/ */
public void setLastUsedSearchSortOrder(SearchSortOrder currentSortOrder) { public void setLastUsedSearchSortOrder(SearchSortOrder currentSortOrder) {
@ -573,17 +609,10 @@ public class ApplicationSettings {
edit.apply(); edit.apply();
} }
/**
* Returns the search sort order property that the user last used.
* @return The last used sort order enumeration value
*/
public SearchSortOrder getLastUsedSearchSortOrder() {
return SearchSortOrder.values()[(prefs.getInt("system_lastusedsearchsortorder", SearchSortOrder.BySeeders.ordinal()))];
}
/** /**
* Returns the sort order direction that the user last used. Use together with {@link #getLastUsedSortOrder()} to * Returns the sort order direction that the user last used. Use together with {@link #getLastUsedSortOrder()} to
* get the full last used sort settings. * get the full last used sort settings.
*
* @return True if the last used sort direction was descending, false otherwise (i.e. the default ascending * @return True if the last used sort direction was descending, false otherwise (i.e. the default ascending
* direction) * direction)
*/ */
@ -593,6 +622,7 @@ public class ApplicationSettings {
/** /**
* Returns the list of all available in-app search sites as well as all web searches that the user configured. * Returns the list of all available in-app search sites as well as all web searches that the user configured.
*
* @return A list of search settings, all of which are either a {@link SearchSite} or {@link WebsearchSetting} * @return A list of search settings, all of which are either a {@link SearchSite} or {@link WebsearchSetting}
*/ */
public List<SearchSetting> getSearchSettings() { public List<SearchSetting> getSearchSettings() {
@ -607,6 +637,7 @@ public class ApplicationSettings {
* site in the main settings. As opposed to getLastUsedSearchSiteKey(int), this method checks whether a site was * site in the main settings. As opposed to getLastUsedSearchSiteKey(int), this method checks whether a site was
* already registered as being last used (or set as default) and checks whether the site still exists. It returns * already registered as being last used (or set as default) and checks whether the site still exists. It returns
* the first in-app search site if that fails. * the first in-app search site if that fails.
*
* @return A site settings object of the last used server (or, if not known, the first server), or null if no * @return A site settings object of the last used server (or, if not known, the first server), or null if no
* servers exist * servers exist
*/ */
@ -654,10 +685,20 @@ public class ApplicationSettings {
return null; return null;
} }
/**
* Registers the unique key of some web search or in-app search site as being last used by the user
*
* @param site The site settings to register as being last used
*/
public void setLastUsedSearchSite(SearchSetting site) {
prefs.edit().putString("header_setsearchsite", site.getKey()).apply();
}
/** /**
* Returns the unique key of the site that the used last used or selected as default form the main settings; use * Returns the unique key of the site that the used last used or selected as default form the main settings; use
* with getLastUsedSearchSite directly. WARNING: the returned string may no longer refer to a known web search site * with getLastUsedSearchSite directly. WARNING: the returned string may no longer refer to a known web search site
* or in-app search settings object. * or in-app search settings object.
*
* @return A string indicating the key of the last used search site, or null if no site was yet used or set as * @return A string indicating the key of the last used search site, or null if no site was yet used or set as
* default * default
*/ */
@ -665,16 +706,9 @@ public class ApplicationSettings {
return prefs.getString("header_setsearchsite", null); return prefs.getString("header_setsearchsite", null);
} }
/**
* Registers the unique key of some web search or in-app search site as being last used by the user
* @param site The site settings to register as being last used
*/
public void setLastUsedSearchSite(SearchSetting site) {
prefs.edit().putString("header_setsearchsite", site.getKey()).apply();
}
/** /**
* Returns the statistics of this server as it was last seen by the background server checker service. * Returns the statistics of this server as it was last seen by the background server checker service.
*
* @param server The server for which to retrieved the statistics from the stored preferences * @param server The server for which to retrieved the statistics from the stored preferences
* @return A JSON array of JSON objects, each which represent a since torrent * @return A JSON array of JSON objects, each which represent a since torrent
*/ */
@ -692,6 +726,7 @@ public class ApplicationSettings {
/** /**
* Stores the now-last seen statistics of the supplied server by the background server checker service to the * Stores the now-last seen statistics of the supplied server by the background server checker service to the
* internal stored preferences. * internal stored preferences.
*
* @param server The server to which the statistics apply to * @param server The server to which the statistics apply to
* @param lastStats A JSON array of JSON objects that each represent a single seen torrent * @param lastStats A JSON array of JSON objects that each represent a single seen torrent
*/ */
@ -701,6 +736,7 @@ public class ApplicationSettings {
/** /**
* Returns the user configuration for some specific app widget, if the widget is known at all. * Returns the user configuration for some specific app widget, if the widget is known at all.
*
* @param appWidgetId The unique ID of the app widget to retrieve settings for, as supplied by the AppWidgetManager * @param appWidgetId The unique ID of the app widget to retrieve settings for, as supplied by the AppWidgetManager
* @return A widget configuration object, or null if no settings were stored for the widget ID * @return A widget configuration object, or null if no settings were stored for the widget ID
*/ */
@ -720,6 +756,7 @@ public class ApplicationSettings {
/** /**
* Stores the user settings for some specific app widget. Existing settings for the supplied app widget ID will be * Stores the user settings for some specific app widget. Existing settings for the supplied app widget ID will be
* overridden. * overridden.
*
* @param appWidgetId The unique ID of the app widget to store settings for, as supplied by the AppWidgetManager * @param appWidgetId The unique ID of the app widget to store settings for, as supplied by the AppWidgetManager
* @param settings A widget configuration object, which may not be null * @param settings A widget configuration object, which may not be null
*/ */
@ -738,6 +775,7 @@ public class ApplicationSettings {
/** /**
* Remove the setting for some specific app widget. * Remove the setting for some specific app widget.
*
* @param appWidgetId The unique ID of the app widget to store settings for, as supplied by the AppWidgetManager * @param appWidgetId The unique ID of the app widget to store settings for, as supplied by the AppWidgetManager
*/ */
public void removeWidgetConfig(int appWidgetId) { public void removeWidgetConfig(int appWidgetId) {
@ -753,6 +791,7 @@ public class ApplicationSettings {
/** /**
* Trims away whitespace around a string, or returns null if str is null * Trims away whitespace around a string, or returns null if str is null
*
* @param str The string to trim, or null * @param str The string to trim, or null
* @return The trimmed string, or null if str is null * @return The trimmed string, or null if str is null
*/ */

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

@ -30,6 +30,7 @@ import org.transdroid.R;
/** /**
* Allows instantiation of the settings specified in R.xml.pref_notifications. * Allows instantiation of the settings specified in R.xml.pref_notifications.
*
* @author Eric Kok * @author Eric Kok
*/ */
@EBean(scope = Scope.Singleton) @EBean(scope = Scope.Singleton)
@ -47,6 +48,7 @@ public class NotificationSettings {
/** /**
* Whether the background service is enabled and the user wants to receive RSS-related notifications * Whether the background service is enabled and the user wants to receive RSS-related notifications
*
* @return True if the server should be checked for RSS feed updates * @return True if the server should be checked for RSS feed updates
*/ */
public boolean isEnabledForRss() { public boolean isEnabledForRss() {
@ -55,6 +57,7 @@ public class NotificationSettings {
/** /**
* Whether the background service is enabled and the user wants to receive torrent-related notifications * Whether the background service is enabled and the user wants to receive torrent-related notifications
*
* @return True if the server should be checked for torrent status updates * @return True if the server should be checked for torrent status updates
*/ */
public boolean isEnabledForTorrents() { public boolean isEnabledForTorrents() {
@ -67,6 +70,7 @@ public class NotificationSettings {
/** /**
* Returns the interval between two server checks * Returns the interval between two server checks
*
* @return The interval, in milliseconds * @return The interval, in milliseconds
*/ */
public Long getInvervalInMilliseconds() { public Long getInvervalInMilliseconds() {
@ -79,6 +83,7 @@ public class NotificationSettings {
/** /**
* Returns the sound (ring tone) to play on a new notification, or null if it should not play any * Returns the sound (ring tone) to play on a new notification, or null if it should not play any
*
* @return Either the user-specified sound, null if the user specified 'Silent' or the system default notification sound * @return Either the user-specified sound, null if the user specified 'Silent' or the system default notification sound
*/ */
public Uri getSound() { public Uri getSound() {
@ -100,6 +105,7 @@ public class NotificationSettings {
/** /**
* Returns the default vibrate pattern to use if the user enabled notification vibrations; check * Returns the default vibrate pattern to use if the user enabled notification vibrations; check
* {@link #shouldVibrate()}, * {@link #shouldVibrate()},
*
* @return A unique pattern for vibrations in Transdroid * @return A unique pattern for vibrations in Transdroid
*/ */
public long[] getDefaultVibratePattern() { public long[] getDefaultVibratePattern() {
@ -112,6 +118,7 @@ public class NotificationSettings {
/** /**
* Returns the LED colour to use on a new notification * Returns the LED colour to use on a new notification
*
* @return The integer value of the user-specified or default colour * @return The integer value of the user-specified or default colour
*/ */
public int getDesiredLedColour() { public int getDesiredLedColour() {
@ -123,6 +130,7 @@ public class NotificationSettings {
/** /**
* Whether the background service should report to ADW Launcher * Whether the background service should report to ADW Launcher
*
* @return True if the user want Transdroid to report to ADW Launcher * @return True if the user want Transdroid to report to ADW Launcher
*/ */
public boolean shouldReportToAdwLauncher() { public boolean shouldReportToAdwLauncher() {

12
app/src/main/java/org/transdroid/core/app/settings/RssfeedSetting.java

@ -16,15 +16,16 @@
*/ */
package org.transdroid.core.app.settings; package org.transdroid.core.app.settings;
import java.util.Date; import android.net.Uri;
import android.text.TextUtils;
import org.transdroid.core.gui.lists.SimpleListItem; import org.transdroid.core.gui.lists.SimpleListItem;
import android.net.Uri; import java.util.Date;
import android.text.TextUtils;
/** /**
* Represents a user-specified RSS feed. * Represents a user-specified RSS feed.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class RssfeedSetting implements SimpleListItem { public class RssfeedSetting implements SimpleListItem {
@ -38,8 +39,8 @@ public class RssfeedSetting implements SimpleListItem {
private final boolean alarm; private final boolean alarm;
private final String excludeFilter; private final String excludeFilter;
private final String includeFilter; private final String includeFilter;
private Date lastViewed;
private final String lastViewedItemUrl; private final String lastViewedItemUrl;
private Date lastViewed;
public RssfeedSetting(int order, String name, String baseUrl, boolean needsAuth, boolean alarm, String excludeFilter, String includeFilter, Date lastViewed, public RssfeedSetting(int order, String name, String baseUrl, boolean needsAuth, boolean alarm, String excludeFilter, String includeFilter, Date lastViewed,
String lastViewedItemUrl) { String lastViewedItemUrl) {
@ -93,6 +94,7 @@ public class RssfeedSetting implements SimpleListItem {
* Returns the date on which we last checked this feed. Note that this is NOT updated automatically after the * Returns the date on which we last checked this feed. Note that this is NOT updated automatically after the
* settings were loaded from {@link ApplicationSettings}; instead the settings have to be manually loaded again * settings were loaded from {@link ApplicationSettings}; instead the settings have to be manually loaded again
* using {@link ApplicationSettings#getRssfeedSetting(int)}. * using {@link ApplicationSettings#getRssfeedSetting(int)}.
*
* @return The last new item's URL as URL-encoded string * @return The last new item's URL as URL-encoded string
*/ */
public Date getLastViewed() { public Date getLastViewed() {
@ -103,6 +105,7 @@ public class RssfeedSetting implements SimpleListItem {
* Returns the URL of the item that was the newest last time we checked this feed. Note that this is NOT updated * Returns the URL of the item that was the newest last time we checked this feed. Note that this is NOT updated
* automatically after the settings were loaded from {@link ApplicationSettings}; instead the settings have to be * automatically after the settings were loaded from {@link ApplicationSettings}; instead the settings have to be
* manually loaded again using {@link ApplicationSettings#getRssfeedSetting(int)}. * manually loaded again using {@link ApplicationSettings#getRssfeedSetting(int)}.
*
* @return The last new item's URL as URL-encoded string * @return The last new item's URL as URL-encoded string
*/ */
public String getLastViewedItemUrl() { public String getLastViewedItemUrl() {
@ -111,6 +114,7 @@ public class RssfeedSetting implements SimpleListItem {
/** /**
* Returns a nicely formatted identifier containing (a portion of) the feed URL * Returns a nicely formatted identifier containing (a portion of) the feed URL
*
* @return A string to identify this feed's URL * @return A string to identify this feed's URL
*/ */
public String getHumanReadableIdentifier() { public String getHumanReadableIdentifier() {

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

@ -29,6 +29,7 @@ import org.transdroid.daemon.OS;
/** /**
* Represents a user-configured remote server. * Represents a user-configured remote server.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class ServerSetting implements SimpleListItem { public class ServerSetting implements SimpleListItem {
@ -65,6 +66,7 @@ public class ServerSetting implements SimpleListItem {
/** /**
* Creates a daemon settings instance, providing full connection details * Creates a daemon settings instance, providing full connection details
*
* @param name A name used to identify this server to the user * @param name A name used to identify this server to the user
* @param type The server daemon type * @param type The server daemon type
* @param address The server domain name or IP address * @param address The server domain name or IP address
@ -235,6 +237,7 @@ public class ServerSetting implements SimpleListItem {
/** /**
* Returns a string that the user can use to identify the server by internal settings (rather than the name). * Returns a string that the user can use to identify the server by internal settings (rather than the name).
*
* @return A human-readable identifier in the form [https://]username@address:port/folder * @return A human-readable identifier in the form [https://]username@address:port/folder
*/ */
public String getHumanReadableIdentifier() { public String getHumanReadableIdentifier() {
@ -253,6 +256,7 @@ public class ServerSetting implements SimpleListItem {
* Returns a string that acts as a unique identifier for this server, non-depending on the internal storage * Returns a string that acts as a unique identifier for this server, non-depending on the internal storage
* order/index. THis may be used to store additional details about this server elsewhere. It may change if the user * order/index. THis may be used to store additional details about this server elsewhere. It may change if the user
* changes server settings, but not with name or notification settings. * changes server settings, but not with name or notification settings.
*
* @return A unique identifying string, based primarily on the configured address, port number, SSL settings and * @return A unique identifying string, based primarily on the configured address, port number, SSL settings and
* user name; returns null if the server is not yet fully identifiable (during configuration, for example) * user name; returns null if the server is not yet fully identifiable (during configuration, for example)
*/ */
@ -284,6 +288,7 @@ public class ServerSetting implements SimpleListItem {
/** /**
* Returns the appropriate daemon adapter to which tasks can be executed, in accordance with this server's settings * Returns the appropriate daemon adapter to which tasks can be executed, in accordance with this server's settings
*
* @param connectedToNetwork The name of the (wifi) network we are currently connected to, or null if this could not * @param connectedToNetwork The name of the (wifi) network we are currently connected to, or null if this could not
* be determined * be determined
* @param context A context to access the logger * @param context A context to access the logger
@ -295,6 +300,7 @@ public class ServerSetting implements SimpleListItem {
/** /**
* Converts local server settings into an old-style {@link DaemonSettings} object. * Converts local server settings into an old-style {@link DaemonSettings} object.
*
* @param connectedToNetwork The name of the (wifi) network we are currently connected to, or null if this could not * @param connectedToNetwork The name of the (wifi) network we are currently connected to, or null if this could not
* be determined * be determined
* @param caller A context to access the logger * @param caller A context to access the logger

14
app/src/main/java/org/transdroid/core/app/settings/SettingsPersistence.java

@ -44,18 +44,18 @@ import java.io.OutputStream;
@EBean(scope = Scope.Singleton) @EBean(scope = Scope.Singleton)
public class SettingsPersistence { public class SettingsPersistence {
@Bean
protected ApplicationSettings applicationSettings;
@Bean
protected SystemSettings systemSettings;
public static final String DEFAULT_SETTINGS_DIR = Environment.getExternalStorageDirectory().toString() public static final String DEFAULT_SETTINGS_DIR = Environment.getExternalStorageDirectory().toString()
+ "/Transdroid/"; + "/Transdroid/";
public static final String DEFAULT_SETTINGS_FILENAME = "settings.json"; public static final String DEFAULT_SETTINGS_FILENAME = "settings.json";
public static final File DEFAULT_SETTINGS_FILE = new File(DEFAULT_SETTINGS_DIR + DEFAULT_SETTINGS_FILENAME); public static final File DEFAULT_SETTINGS_FILE = new File(DEFAULT_SETTINGS_DIR + DEFAULT_SETTINGS_FILENAME);
@Bean
protected ApplicationSettings applicationSettings;
@Bean
protected SystemSettings systemSettings;
/** /**
* Reads the server, web searches, RSS feed, background service and system settings from a JSON-encoded String, such as when read via a QR code. * Reads the server, web searches, RSS feed, background service and system settings from a JSON-encoded String, such as when read via a QR code.
*
* @param prefs The application-global preferences object to write settings to * @param prefs The application-global preferences object to write settings to
* @param contents The JSON-encoded settings as raw String * @param contents The JSON-encoded settings as raw String
* @throws JSONException Thrown when the file did not contain valid JSON content * @throws JSONException Thrown when the file did not contain valid JSON content
@ -67,6 +67,7 @@ public class SettingsPersistence {
/** /**
* Synchronously reads the server, web searches, RSS feed, background service and system settings from a file in * Synchronously reads the server, web searches, RSS feed, background service and system settings from a file in
* JSON format. * JSON format.
*
* @param prefs The application-global preferences object to write settings to * @param prefs The application-global preferences object to write settings to
* @param settingsFile The local file to read the settings from * @param settingsFile The local file to read the settings from
* @throws FileNotFoundException Thrown when the settings file doesn't exist or couldn't be read * @throws FileNotFoundException Thrown when the settings file doesn't exist or couldn't be read
@ -79,6 +80,7 @@ public class SettingsPersistence {
/** /**
* Synchronously reads the server, web searches, RSS feed, background service and system settings from a stream (file) in * Synchronously reads the server, web searches, RSS feed, background service and system settings from a stream (file) in
* JSON format. * JSON format.
*
* @param prefs The application-global preferences object to write settings to * @param prefs The application-global preferences object to write settings to
* @param settingsStream The stream to read the settings from * @param settingsStream The stream to read the settings from
* @throws JSONException Thrown when the file did not contain valid JSON content * @throws JSONException Thrown when the file did not contain valid JSON content
@ -229,6 +231,7 @@ public class SettingsPersistence {
/** /**
* Returns encoded server, web searches, RSS feed, background service and system settings as a JSON data object structure, serialized to a String. * Returns encoded server, web searches, RSS feed, background service and system settings as a JSON data object structure, serialized to a String.
*
* @param prefs The application-global preferences object to read settings from * @param prefs The application-global preferences object to read settings from
* @throws JSONException Thrown when the JSON content could not be constructed properly * @throws JSONException Thrown when the JSON content could not be constructed properly
*/ */
@ -239,6 +242,7 @@ public class SettingsPersistence {
/** /**
* Synchronously writes the server, web searches, RSS feed, background service and system settings to a file in JSON * Synchronously writes the server, web searches, RSS feed, background service and system settings to a file in JSON
* format. * format.
*
* @param prefs The application-global preferences object to read settings from * @param prefs The application-global preferences object to read settings from
* @param settingsFile The local file to read the settings from * @param settingsFile The local file to read the settings from
* @throws JSONException Thrown when the JSON content could not be constructed properly * @throws JSONException Thrown when the JSON content could not be constructed properly

3
app/src/main/java/org/transdroid/core/app/settings/SettingsUtils.java

@ -1,7 +1,6 @@
package org.transdroid.core.app.settings; package org.transdroid.core.app.settings;
import android.content.Context;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.app.AppCompatDelegate;
@ -33,6 +32,6 @@ public class SettingsUtils {
return builder; return builder;
} }
return builder.theme(settings.useDarkTheme() ? Theme.DARK: Theme.LIGHT); return builder.theme(settings.useDarkTheme() ? Theme.DARK : Theme.LIGHT);
} }
} }

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

@ -29,6 +29,7 @@ import java.util.Date;
/** /**
* Allows instantiation of the settings specified in R.xml.pref_system. * Allows instantiation of the settings specified in R.xml.pref_system.
*
* @author Eric Kok * @author Eric Kok
*/ */
@EBean(scope = Scope.Singleton) @EBean(scope = Scope.Singleton)
@ -48,6 +49,7 @@ public class SystemSettings {
/** /**
* Returns the interval in which automatic screen refreshes should be scheduled. * Returns the interval in which automatic screen refreshes should be scheduled.
*
* @return The selected refresh interval in milliseconds or 0 if automatic refreshes should be disabled * @return The selected refresh interval in milliseconds or 0 if automatic refreshes should be disabled
*/ */
public long getRefreshIntervalMilliseconds() { public long getRefreshIntervalMilliseconds() {
@ -68,6 +70,7 @@ public class SystemSettings {
/** /**
* Returns the date when we last checked transdroid.org for the latest app version. * Returns the date when we last checked transdroid.org for the latest app version.
*
* @return The date/time when the {@link org.transdroid.core.service.AppUpdateJob} checked on the server for updates * @return The date/time when the {@link org.transdroid.core.service.AppUpdateJob} checked on the server for updates
*/ */
public Date getLastCheckedForAppUpdates() { public Date getLastCheckedForAppUpdates() {
@ -77,6 +80,7 @@ public class SystemSettings {
/** /**
* Stores the date at which was last successfully, fully checked for new updates to the app. * Stores the date at which was last successfully, fully checked for new updates to the app.
*
* @param lastChecked The date/time at which the {@link org.transdroid.core.service.AppUpdateJob} last checked the server for updates * @param lastChecked The date/time at which the {@link org.transdroid.core.service.AppUpdateJob} last checked the server for updates
*/ */
public void setLastCheckedForAppUpdates(Date lastChecked) { public void setLastCheckedForAppUpdates(Date lastChecked) {

13
app/src/main/java/org/transdroid/core/app/settings/WebsearchSetting.java

@ -16,21 +16,21 @@
*/ */
package org.transdroid.core.app.settings; package org.transdroid.core.app.settings;
import org.transdroid.core.gui.lists.SimpleListItem;
import org.transdroid.core.gui.search.SearchSetting;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import org.transdroid.core.gui.lists.SimpleListItem;
import org.transdroid.core.gui.search.SearchSetting;
/** /**
* Represents a user-specified website that can be searched (by starting the browser, rather than in-app) * Represents a user-specified website that can be searched (by starting the browser, rather than in-app)
*
* @author Eric Kok * @author Eric Kok
*/ */
public class WebsearchSetting implements SimpleListItem, SearchSetting { public class WebsearchSetting implements SimpleListItem, SearchSetting {
private static final String DEFAULT_NAME = "Default";
public static final String KEY_PREFIX = "websearch_"; public static final String KEY_PREFIX = "websearch_";
private static final String DEFAULT_NAME = "Default";
private final int order; private final int order;
private final String name; private final String name;
private final String baseUrl; private final String baseUrl;
@ -53,7 +53,7 @@ public class WebsearchSetting implements SimpleListItem, SearchSetting {
return name; return name;
if (!TextUtils.isEmpty(baseUrl)) { if (!TextUtils.isEmpty(baseUrl)) {
String host = Uri.parse(baseUrl).getHost(); String host = Uri.parse(baseUrl).getHost();
return host == null? DEFAULT_NAME: host; return host == null ? DEFAULT_NAME : host;
} }
return DEFAULT_NAME; return DEFAULT_NAME;
} }
@ -72,6 +72,7 @@ public class WebsearchSetting implements SimpleListItem, SearchSetting {
/** /**
* Returns a nicely formatted identifier containing (a portion of) the search base URL * Returns a nicely formatted identifier containing (a portion of) the search base URL
*
* @return A string to identify this site's search URL * @return A string to identify this site's search URL
*/ */
public String getHumanReadableIdentifier() { public String getHumanReadableIdentifier() {

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

@ -20,6 +20,7 @@ import android.annotation.TargetApi;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
@ -56,8 +57,6 @@ import org.transdroid.daemon.TorrentFile;
import org.transdroid.daemon.task.DaemonTaskFailureResult; import org.transdroid.daemon.task.DaemonTaskFailureResult;
import org.transdroid.daemon.task.DaemonTaskResult; import org.transdroid.daemon.task.DaemonTaskResult;
import org.transdroid.daemon.task.DaemonTaskSuccessResult; import org.transdroid.daemon.task.DaemonTaskSuccessResult;
import org.transdroid.daemon.task.ToggleSequentialDownloadTask;
import org.transdroid.daemon.task.ToggleFirstLastPieceDownloadTask;
import org.transdroid.daemon.task.ForceRecheckTask; import org.transdroid.daemon.task.ForceRecheckTask;
import org.transdroid.daemon.task.GetFileListTask; import org.transdroid.daemon.task.GetFileListTask;
import org.transdroid.daemon.task.GetFileListTaskSuccessResult; import org.transdroid.daemon.task.GetFileListTaskSuccessResult;
@ -74,6 +73,8 @@ import org.transdroid.daemon.task.SetLabelTask;
import org.transdroid.daemon.task.SetTrackersTask; import org.transdroid.daemon.task.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 org.transdroid.daemon.task.ToggleFirstLastPieceDownloadTask;
import org.transdroid.daemon.task.ToggleSequentialDownloadTask;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -82,6 +83,7 @@ import java.util.List;
* An activity that holds a single torrents details fragment. It is used on devices (i.e. phones) where there is no room to show details in the {@link * An activity that holds a single torrents details fragment. It is used on devices (i.e. phones) where there is no room to show details in the {@link
* TorrentsActivity} directly. Task execution, such as loading of more details and updating file priorities, is performed in this activity via * TorrentsActivity} directly. Task execution, such as loading of more details and updating file priorities, is performed in this activity via
* background methods. * background methods.
*
* @author Eric Kok * @author Eric Kok
*/ */
@EActivity(R.layout.activity_details) @EActivity(R.layout.activity_details)
@ -104,13 +106,12 @@ public class DetailsActivity extends AppCompatActivity implements TorrentTasksEx
protected ConnectivityHelper connectivityHelper; protected ConnectivityHelper connectivityHelper;
@Bean @Bean
protected ApplicationSettings applicationSettings; protected ApplicationSettings applicationSettings;
private IDaemonAdapter currentConnection = null;
// Details view components // Details view components
@ViewById @ViewById
protected Toolbar selectionToolbar; protected Toolbar selectionToolbar;
@FragmentById(R.id.torrentdetails_fragment) @FragmentById(R.id.torrentdetails_fragment)
protected DetailsFragment fragmentDetails; protected DetailsFragment fragmentDetails;
private IDaemonAdapter currentConnection = null;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {

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

@ -17,15 +17,11 @@
package org.transdroid.core.gui; package org.transdroid.core.gui;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Fragment;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.ActionMenuView;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -35,6 +31,11 @@ import android.widget.ListView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.ActionMenuView;
import androidx.fragment.app.Fragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.nispok.snackbar.Snackbar; import com.nispok.snackbar.Snackbar;
import com.nispok.snackbar.SnackbarManager; import com.nispok.snackbar.SnackbarManager;
import com.nispok.snackbar.enums.SnackbarType; import com.nispok.snackbar.enums.SnackbarType;
@ -48,7 +49,6 @@ 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.ServerSetting; 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.Label; import org.transdroid.core.gui.navigation.Label;
@ -75,6 +75,7 @@ import java.util.List;
* Fragment that shows detailed statistics about some torrent. These come from some already fetched {@link Torrent} object, but it also retrieves * Fragment that shows detailed statistics about some torrent. These come from some already fetched {@link Torrent} object, but it also retrieves
* further detailed statistics. The actual execution of tasks is performed by the activity that contains this fragment, as per the {@link * further detailed statistics. The actual execution of tasks is performed by the activity that contains this fragment, as per the {@link
* TorrentTasksExecutor} interface. * TorrentTasksExecutor} interface.
*
* @author Eric Kok * @author Eric Kok
*/ */
@EFragment(R.layout.fragment_details) @EFragment(R.layout.fragment_details)
@ -95,8 +96,6 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
protected boolean isLoadingTorrent = false; protected boolean isLoadingTorrent = false;
@InstanceState @InstanceState
protected boolean hasCriticalError = false; protected boolean hasCriticalError = false;
private ServerSetting currentServerSettings = null;
// Views // Views
@ViewById @ViewById
protected View detailsContainer; protected View detailsContainer;
@ -112,6 +111,170 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
protected TextView emptyText, errorText; protected TextView emptyText, errorText;
@ViewById @ViewById
protected ProgressBar loadingProgress; protected ProgressBar loadingProgress;
private ServerSetting currentServerSettings = null;
private MultiChoiceModeListener onDetailsSelected = new MultiChoiceModeListener() {
SelectionManagerMode selectionManagerMode;
@Override
public boolean onCreateActionMode(final ActionMode mode, Menu menu) {
// Show contextual action bar to start/stop/remove/etc. torrents in batch mode
detailsMenu.setEnabled(false);
contextualMenu.setVisibility(View.VISIBLE);
contextualMenu.setOnMenuItemClickListener(menuItem -> onActionItemClicked(mode, menuItem));
contextualMenu.getMenu().clear();
getActivity().getMenuInflater().inflate(R.menu.fragment_details_cab_main, contextualMenu.getMenu());
Context themedContext = ((AppCompatActivity) getActivity()).getSupportActionBar().getThemedContext();
mode.getMenuInflater().inflate(R.menu.fragment_details_cab_secondary, menu);
selectionManagerMode = new SelectionManagerMode(themedContext, detailsList, R.plurals.navigation_filesselected);
selectionManagerMode.setOnlyCheckClass(TorrentFile.class);
selectionManagerMode.onCreateActionMode(mode, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
selectionManagerMode.onPrepareActionMode(mode, menu);
// Pause autorefresh
if (getActivity() != null && getActivity() instanceof TorrentsActivity) {
((TorrentsActivity) getActivity()).stopRefresh = true;
((TorrentsActivity) getActivity()).stopAutoRefresh();
}
boolean filePaths = currentServerSettings != null && Daemon.supportsFilePaths(currentServerSettings.getType());
contextualMenu.getMenu().findItem(R.id.action_download).setVisible(filePaths);
boolean filePriorities = currentServerSettings != null && Daemon.supportsFilePrioritySetting(currentServerSettings.getType());
contextualMenu.getMenu().findItem(R.id.action_priority_off).setVisible(filePriorities);
contextualMenu.getMenu().findItem(R.id.action_priority_low).setVisible(filePriorities);
contextualMenu.getMenu().findItem(R.id.action_priority_normal).setVisible(filePriorities);
contextualMenu.getMenu().findItem(R.id.action_priority_high).setVisible(filePriorities);
return true;
}
@SuppressLint("SdCardPath")
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// Get checked torrents
List<TorrentFile> checked = new ArrayList<>();
for (int i = 0; i < detailsList.getCheckedItemPositions().size(); i++) {
if (detailsList.getCheckedItemPositions().valueAt(i) && i < detailsList.getAdapter().getCount() &&
detailsList.getAdapter().getItem(detailsList.getCheckedItemPositions().keyAt(i)) instanceof TorrentFile) {
checked.add((TorrentFile) detailsList.getAdapter().getItem(detailsList.getCheckedItemPositions().keyAt(i)));
}
}
int itemId = item.getItemId();
if (itemId == R.id.action_download) {
if (checked.size() < 1 || currentServerSettings == null) {
return true;
}
String urlBase = currentServerSettings.getFtpUrl();
if (urlBase == null || urlBase.equals("")) {
urlBase = "ftp://" + currentServerSettings.getAddress() + "/";
}
// Try using AndFTP intents
Intent andftpStart = new Intent(Intent.ACTION_PICK);
andftpStart.setDataAndType(Uri.parse(urlBase), "vnd.android.cursor.dir/lysesoft.andftp.uri");
andftpStart.putExtra("command_type", "download");
andftpStart.putExtra("ftp_pasv", "true");
if (Uri.parse(urlBase).getUserInfo() != null) {
andftpStart.putExtra("ftp_username", Uri.parse(urlBase).getUserInfo());
} else {
andftpStart.putExtra("ftp_username", currentServerSettings.getUsername());
}
if (currentServerSettings.getFtpPassword() != null && !currentServerSettings.getFtpPassword().equals("")) {
andftpStart.putExtra("ftp_password", currentServerSettings.getFtpPassword());
} else {
andftpStart.putExtra("ftp_password", currentServerSettings.getPassword());
}
// Note: AndFTP doesn't understand the directory that Environment.getExternalStoragePublicDirectory()
// uses :(
andftpStart.putExtra("local_folder", "/sdcard/Download");
for (int f = 0; f < checked.size(); f++) {
String file = checked.get(f).getRelativePath();
if (file != null) {
// If the file is directly in the root, AndFTP fails if we supply the proper path (like
// /file.pdf)
// Work around this bug by removing the leading / if no further directories are used in the path
if (file.startsWith("/") && file.indexOf("/", 1) < 0) {
file = file.substring(1);
}
andftpStart.putExtra("remote_file" + (f + 1), file);
}
}
if (andftpStart.resolveActivity(getActivity().getPackageManager()) != null) {
startActivity(andftpStart);
mode.finish();
return true;
}
// Try using a VIEW intent given an ftp:// scheme URI
String url = urlBase + checked.get(0).getRelativePath();
Intent simpleStart = new Intent(Intent.ACTION_VIEW, Uri.parse(url)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (simpleStart.resolveActivity(getActivity().getPackageManager()) != null) {
startActivity(simpleStart);
mode.finish();
return true;
}
// No app is available that can handle FTP downloads
SnackbarManager.show(Snackbar.with(getActivity()).text(getString(R.string.error_noftpapp, url)).type(SnackbarType.MULTI_LINE)
.colorResource(R.color.red));
mode.finish();
return true;
} else if (itemId == R.id.action_copytoclipboard) {
StringBuilder names = new StringBuilder();
for (int f = 0; f < checked.size(); f++) {
if (f != 0) {
names.append("\n");
}
names.append(checked.get(f).getName());
}
ClipboardManager clipboardManager = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
clipboardManager.setPrimaryClip(ClipData.newPlainText("Transdroid", names.toString()));
mode.finish();
return true;
} else {
Priority priority = Priority.Off;
if (itemId == R.id.action_priority_low) {
priority = Priority.Low;
}
if (itemId == R.id.action_priority_normal) {
priority = Priority.Normal;
}
if (itemId == R.id.action_priority_high) {
priority = Priority.High;
}
if (getTasksExecutor() != null)
getTasksExecutor().updatePriority(torrent, checked, priority);
mode.finish();
return true;
}
}
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
selectionManagerMode.onItemCheckedStateChanged(mode, position, id, checked);
}
@Override
public void onDestroyActionMode(ActionMode mode) {
// Resume autorefresh
if (getActivity() != null && getActivity() instanceof TorrentsActivity) {
((TorrentsActivity) getActivity()).stopRefresh = false;
((TorrentsActivity) getActivity()).startAutoRefresh();
}
selectionManagerMode.onDestroyActionMode(mode);
contextualMenu.setVisibility(View.GONE);
detailsMenu.setEnabled(true);
}
};
@AfterViews @AfterViews
protected void init() { protected void init() {
@ -133,12 +296,9 @@ 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) {
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { swipeRefreshLayout.setOnRefreshListener(() -> {
@Override
public void onRefresh() {
((RefreshableActivity) getActivity()).refreshScreen(); ((RefreshableActivity) getActivity()).refreshScreen();
swipeRefreshLayout.setRefreshing(false); // Use our custom indicator swipeRefreshLayout.setRefreshing(false); // Use our custom indicator
}
}); });
} }
@ -161,6 +321,7 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
/** /**
* Updates the details adapter header to show the new torrent data. * Updates the details adapter header to show the new torrent data.
*
* @param newTorrent The new, non-null torrent object * @param newTorrent The new, non-null torrent object
*/ */
public void updateTorrent(Torrent newTorrent) { public void updateTorrent(Torrent newTorrent) {
@ -186,6 +347,7 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
/** /**
* Updates the details adapter to show the list of trackers and tracker errors. * Updates the details adapter to show the list of trackers and tracker errors.
*
* @param checkTorrent The torrent for which the details were retrieved * @param checkTorrent The torrent for which the details were retrieved
* @param newTorrentDetails The new fine details object of some torrent * @param newTorrentDetails The new fine details object of some torrent
*/ */
@ -205,6 +367,7 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
/** /**
* Updates the list adapter to show a new list of torrent files, replacing the old files list. * Updates the list adapter to show a new list of torrent files, replacing the old files list.
*
* @param checkTorrent The torrent for which the details were retrieved * @param checkTorrent The torrent for which the details were retrieved
* @param newTorrentFiles The new, updated list of torrent file objects * @param newTorrentFiles The new, updated list of torrent file objects
*/ */
@ -220,6 +383,7 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
/** /**
* Can be called if some outside activity returned new torrents, so we can perhaps piggyback on this by update our data as well. * Can be called if some outside activity returned new torrents, so we can perhaps piggyback on this by update our data as well.
*
* @param torrents The last of retrieved torrents * @param torrents The last of retrieved torrents
*/ */
public void perhapsUpdateTorrent(List<Torrent> torrents) { public void perhapsUpdateTorrent(List<Torrent> torrents) {
@ -239,6 +403,7 @@ 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 should be updated every time * Updates the locally maintained list of labels that are active on the server. Used in the label picking dialog and should be updated every time
* after the list of torrents was retrieved to keep it updated. * 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) {
@ -261,6 +426,7 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
/** /**
* Updates the shown screen depending on whether the torrent is loading * Updates the shown screen depending on whether the torrent is loading
*
* @param isLoading True if the torrent is (re)loading, false otherwise * @param isLoading True if the torrent is (re)loading, false otherwise
* @param connectionErrorMessage The error message text to show to the user, or null if there was no error * @param connectionErrorMessage The error message text to show to the user, or null if there was no error
*/ */
@ -280,9 +446,7 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
public void createMenuOptions() { public void createMenuOptions() {
getActivity().getMenuInflater().inflate(R.menu.fragment_details, detailsMenu.getMenu()); getActivity().getMenuInflater().inflate(R.menu.fragment_details, detailsMenu.getMenu());
detailsMenu.setOnMenuItemClickListener(new ActionMenuView.OnMenuItemClickListener() { detailsMenu.setOnMenuItemClickListener(menuItem -> {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
switch (menuItem.getItemId()) { switch (menuItem.getItemId()) {
case R.id.action_pause: case R.id.action_pause:
pauseTorrent(); pauseTorrent();
@ -328,7 +492,6 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
return true; return true;
} }
return false; return false;
}
}); });
} }
@ -509,177 +672,9 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
} }
} }
private MultiChoiceModeListener onDetailsSelected = new MultiChoiceModeListener() {
SelectionManagerMode selectionManagerMode;
@Override
public boolean onCreateActionMode(final ActionMode mode, Menu menu) {
// Show contextual action bar to start/stop/remove/etc. torrents in batch mode
detailsMenu.setEnabled(false);
contextualMenu.setVisibility(View.VISIBLE);
contextualMenu.setOnMenuItemClickListener(new ActionMenuView.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
return onActionItemClicked(mode, menuItem);
}
});
contextualMenu.getMenu().clear();
getActivity().getMenuInflater().inflate(R.menu.fragment_details_cab_main, contextualMenu.getMenu());
Context themedContext = ((AppCompatActivity) getActivity()).getSupportActionBar().getThemedContext();
mode.getMenuInflater().inflate(R.menu.fragment_details_cab_secondary, menu);
selectionManagerMode = new SelectionManagerMode(themedContext, detailsList, R.plurals.navigation_filesselected);
selectionManagerMode.setOnlyCheckClass(TorrentFile.class);
selectionManagerMode.onCreateActionMode(mode, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
selectionManagerMode.onPrepareActionMode(mode, menu);
// Pause autorefresh
if (getActivity() != null && getActivity() instanceof TorrentsActivity) {
((TorrentsActivity) getActivity()).stopRefresh = true;
((TorrentsActivity) getActivity()).stopAutoRefresh();
}
boolean filePaths = currentServerSettings != null && Daemon.supportsFilePaths(currentServerSettings.getType());
contextualMenu.getMenu().findItem(R.id.action_download).setVisible(filePaths);
boolean filePriorities = currentServerSettings != null && Daemon.supportsFilePrioritySetting(currentServerSettings.getType());
contextualMenu.getMenu().findItem(R.id.action_priority_off).setVisible(filePriorities);
contextualMenu.getMenu().findItem(R.id.action_priority_low).setVisible(filePriorities);
contextualMenu.getMenu().findItem(R.id.action_priority_normal).setVisible(filePriorities);
contextualMenu.getMenu().findItem(R.id.action_priority_high).setVisible(filePriorities);
return true;
}
@SuppressLint("SdCardPath")
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// Get checked torrents
List<TorrentFile> checked = new ArrayList<>();
for (int i = 0; i < detailsList.getCheckedItemPositions().size(); i++) {
if (detailsList.getCheckedItemPositions().valueAt(i) && i < detailsList.getAdapter().getCount() &&
detailsList.getAdapter().getItem(detailsList.getCheckedItemPositions().keyAt(i)) instanceof TorrentFile) {
checked.add((TorrentFile) detailsList.getAdapter().getItem(detailsList.getCheckedItemPositions().keyAt(i)));
}
}
int itemId = item.getItemId();
if (itemId == R.id.action_download) {
if (checked.size() < 1 || currentServerSettings == null) {
return true;
}
String urlBase = currentServerSettings.getFtpUrl();
if (urlBase == null || urlBase.equals("")) {
urlBase = "ftp://" + currentServerSettings.getAddress() + "/";
}
// Try using AndFTP intents
Intent andftpStart = new Intent(Intent.ACTION_PICK);
andftpStart.setDataAndType(Uri.parse(urlBase), "vnd.android.cursor.dir/lysesoft.andftp.uri");
andftpStart.putExtra("command_type", "download");
andftpStart.putExtra("ftp_pasv", "true");
if (Uri.parse(urlBase).getUserInfo() != null) {
andftpStart.putExtra("ftp_username", Uri.parse(urlBase).getUserInfo());
} else {
andftpStart.putExtra("ftp_username", currentServerSettings.getUsername());
}
if (currentServerSettings.getFtpPassword() != null && !currentServerSettings.getFtpPassword().equals("")) {
andftpStart.putExtra("ftp_password", currentServerSettings.getFtpPassword());
} else {
andftpStart.putExtra("ftp_password", currentServerSettings.getPassword());
}
// Note: AndFTP doesn't understand the directory that Environment.getExternalStoragePublicDirectory()
// uses :(
andftpStart.putExtra("local_folder", "/sdcard/Download");
for (int f = 0; f < checked.size(); f++) {
String file = checked.get(f).getRelativePath();
if (file != null) {
// If the file is directly in the root, AndFTP fails if we supply the proper path (like
// /file.pdf)
// Work around this bug by removing the leading / if no further directories are used in the path
if (file.startsWith("/") && file.indexOf("/", 1) < 0) {
file = file.substring(1);
}
andftpStart.putExtra("remote_file" + (f + 1), file);
}
}
if (andftpStart.resolveActivity(getActivity().getPackageManager()) != null) {
startActivity(andftpStart);
mode.finish();
return true;
}
// Try using a VIEW intent given an ftp:// scheme URI
String url = urlBase + checked.get(0).getRelativePath();
Intent simpleStart = new Intent(Intent.ACTION_VIEW, Uri.parse(url)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (simpleStart.resolveActivity(getActivity().getPackageManager()) != null) {
startActivity(simpleStart);
mode.finish();
return true;
}
// No app is available that can handle FTP downloads
SnackbarManager.show(Snackbar.with(getActivity()).text(getString(R.string.error_noftpapp, url)).type(SnackbarType.MULTI_LINE)
.colorResource(R.color.red));
mode.finish();
return true;
} else if (itemId == R.id.action_copytoclipboard) {
StringBuilder names = new StringBuilder();
for (int f = 0; f < checked.size(); f++) {
if (f != 0) {
names.append("\n");
}
names.append(checked.get(f).getName());
}
ClipboardManager clipboardManager = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
clipboardManager.setPrimaryClip(ClipData.newPlainText("Transdroid", names.toString()));
mode.finish();
return true;
} else {
Priority priority = Priority.Off;
if (itemId == R.id.action_priority_low) {
priority = Priority.Low;
}
if (itemId == R.id.action_priority_normal) {
priority = Priority.Normal;
}
if (itemId == R.id.action_priority_high) {
priority = Priority.High;
}
if (getTasksExecutor() != null)
getTasksExecutor().updatePriority(torrent, checked, priority);
mode.finish();
return true;
}
}
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
selectionManagerMode.onItemCheckedStateChanged(mode, position, id, checked);
}
@Override
public void onDestroyActionMode(ActionMode mode) {
// Resume autorefresh
if (getActivity() != null && getActivity() instanceof TorrentsActivity) {
((TorrentsActivity) getActivity()).stopRefresh = false;
((TorrentsActivity) getActivity()).startAutoRefresh();
}
selectionManagerMode.onDestroyActionMode(mode);
contextualMenu.setVisibility(View.GONE);
detailsMenu.setEnabled(true);
}
};
/** /**
* Returns the object responsible for executing torrent tasks against a connected server * Returns the object responsible for executing torrent tasks against a connected server
*
* @return The executor for tasks on some torrent * @return The executor for tasks on some torrent
*/ */
private TorrentTasksExecutor getTasksExecutor() { private TorrentTasksExecutor getTasksExecutor() {

37
app/src/main/java/org/transdroid/core/gui/ServerPickerDialog.java

@ -16,36 +16,23 @@
*/ */
package org.transdroid.core.gui; package org.transdroid.core.gui;
import java.util.List; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.app.DialogFragment;
import android.os.Bundle; import android.os.Bundle;
import androidx.fragment.app.DialogFragment;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.core.app.settings.ServerSetting; import org.transdroid.core.app.settings.ServerSetting;
import android.app.AlertDialog; import java.util.List;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
public class ServerPickerDialog extends DialogFragment { public class ServerPickerDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
String[] serverNames = getArguments().getStringArray("serverNames");
return new AlertDialog.Builder(getActivity()).setTitle(R.string.navigation_pickserver)
.setItems(serverNames, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (getActivity() != null && getActivity() instanceof TorrentsActivity)
((TorrentsActivity) getActivity()).switchServerAndAddFromIntent(which);
}
}).create();
}
/** /**
* Opens a dialog that allows the selection of a configured server (manual or seedbox). The calling activity will * Opens a dialog that allows the selection of a configured server (manual or seedbox). The calling activity will
* receive a callback on its switchServerAndAddFromIntent(int) method. * receive a callback on its switchServerAndAddFromIntent(int) method.
*
* @param activity The torrents activity from which the picker is started (and which received the callback) * @param activity The torrents activity from which the picker is started (and which received the callback)
* @param serverSettings The list of all available servers, of which their names will be offered to the user to pick * @param serverSettings The list of all available servers, of which their names will be offered to the user to pick
* from (and its position in the list is returned to the activity) * from (and its position in the list is returned to the activity)
@ -59,7 +46,17 @@ public class ServerPickerDialog extends DialogFragment {
Bundle arguments = new Bundle(); Bundle arguments = new Bundle();
arguments.putStringArray("serverNames", serverNames); arguments.putStringArray("serverNames", serverNames);
dialog.setArguments(arguments); dialog.setArguments(arguments);
dialog.show(activity.getFragmentManager(), "serverpicker"); dialog.show(activity.getSupportFragmentManager(), "serverpicker");
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
String[] serverNames = getArguments().getStringArray("serverNames");
return new AlertDialog.Builder(getActivity()).setTitle(R.string.navigation_pickserver)
.setItems(serverNames, (dialog, which) -> {
if (getActivity() != null && getActivity() instanceof TorrentsActivity)
((TorrentsActivity) getActivity()).switchServerAndAddFromIntent(which);
}).create();
} }
} }

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

@ -42,6 +42,7 @@ public class ServerSelectionView extends RelativeLayout {
/** /**
* Updates the name of the current connected server. * Updates the name of the current connected server.
*
* @param currentServer The server currently connected to * @param currentServer The server currently connected to
*/ */
public void updateCurrentServer(IDaemonAdapter currentServer) { public void updateCurrentServer(IDaemonAdapter currentServer) {
@ -50,6 +51,7 @@ public class ServerSelectionView extends RelativeLayout {
/** /**
* Updates the name of the selected filter. * Updates the name of the selected filter.
*
* @param currentFilter The filter that is currently selected * @param currentFilter The filter that is currently selected
*/ */
public void updateCurrentFilter(NavigationFilter currentFilter) { public void updateCurrentFilter(NavigationFilter currentFilter) {

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

@ -42,6 +42,8 @@ public class ServerStatusView extends RelativeLayout implements OnRatesPickedLis
@ViewById @ViewById
protected View speedswrapperLayout; protected View speedswrapperLayout;
private TorrentsActivity activity; private TorrentsActivity activity;
private OnClickListener onStartDownPickerClicked = v ->
SetTransferRatesDialog.show(getContext(), ServerStatusView.this);
public ServerStatusView(Context context) { public ServerStatusView(Context context) {
super(context); super(context);
@ -54,6 +56,7 @@ public class ServerStatusView extends RelativeLayout implements OnRatesPickedLis
/** /**
* 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
* @param dormantAsInactive Whether to treat dormant (0KB/s) torrent as inactive state torrents * @param dormantAsInactive Whether to treat dormant (0KB/s) torrent as inactive state torrents
* @param supportsSetTransferRates Whether the connected torrent client supports setting of max transfer speeds * @param supportsSetTransferRates Whether the connected torrent client supports setting of max transfer speeds
@ -99,12 +102,6 @@ public class ServerStatusView extends RelativeLayout implements OnRatesPickedLis
} }
private OnClickListener onStartDownPickerClicked = new OnClickListener() {
public void onClick(View v) {
SetTransferRatesDialog.show(getContext(), ServerStatusView.this);
}
};
@Override @Override
public void onRatesPicked(int maxDownloadSpeed, int maxUploadSpeed) { public void onRatesPicked(int maxDownloadSpeed, int maxUploadSpeed) {
activity.updateMaxSpeeds(maxDownloadSpeed, maxUploadSpeed); activity.updateMaxSpeeds(maxDownloadSpeed, maxUploadSpeed);

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

@ -16,9 +16,6 @@
*/ */
package org.transdroid.core.gui; package org.transdroid.core.gui;
import androidx.appcompat.widget.ActionMenuView;
import androidx.appcompat.widget.Toolbar;
import org.transdroid.daemon.Priority; import org.transdroid.daemon.Priority;
import org.transdroid.daemon.Torrent; import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentFile; import org.transdroid.daemon.TorrentFile;
@ -27,6 +24,7 @@ import java.util.List;
/** /**
* Interface to be implemented by any activity that wants containing fragments to be able to load data and execute commands against a torrent server. * Interface to be implemented by any activity that wants containing fragments to be able to load data and execute commands against a torrent server.
*
* @author Eric Kok * @author Eric Kok
*/ */
public interface TorrentTasksExecutor { public interface TorrentTasksExecutor {

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

@ -24,23 +24,22 @@ import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.core.view.MenuItemCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.ActionMenuView;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
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 androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.ActionMenuView;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.drawerlayout.widget.DrawerLayout;
import com.getbase.floatingactionbutton.FloatingActionButton; import com.getbase.floatingactionbutton.FloatingActionButton;
import com.getbase.floatingactionbutton.FloatingActionsMenu; import com.getbase.floatingactionbutton.FloatingActionsMenu;
import com.nispok.snackbar.Snackbar; import com.nispok.snackbar.Snackbar;
@ -86,7 +85,10 @@ import org.transdroid.core.gui.rss.RssFeedsActivity_;
import org.transdroid.core.gui.search.FilePickerHelper; import org.transdroid.core.gui.search.FilePickerHelper;
import org.transdroid.core.gui.search.UrlEntryDialog; import org.transdroid.core.gui.search.UrlEntryDialog;
import org.transdroid.core.gui.settings.MainSettingsActivity_; import org.transdroid.core.gui.settings.MainSettingsActivity_;
import org.transdroid.core.service.*; import org.transdroid.core.service.AppUpdateJob;
import org.transdroid.core.service.ConnectivityHelper;
import org.transdroid.core.service.RssCheckerJob;
import org.transdroid.core.service.ServerCheckerJob;
import org.transdroid.core.widget.ListWidgetProvider; import org.transdroid.core.widget.ListWidgetProvider;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
@ -102,8 +104,6 @@ import org.transdroid.daemon.task.AddByUrlTask;
import org.transdroid.daemon.task.DaemonTaskFailureResult; import org.transdroid.daemon.task.DaemonTaskFailureResult;
import org.transdroid.daemon.task.DaemonTaskResult; import org.transdroid.daemon.task.DaemonTaskResult;
import org.transdroid.daemon.task.DaemonTaskSuccessResult; import org.transdroid.daemon.task.DaemonTaskSuccessResult;
import org.transdroid.daemon.task.ToggleSequentialDownloadTask;
import org.transdroid.daemon.task.ToggleFirstLastPieceDownloadTask;
import org.transdroid.daemon.task.ForceRecheckTask; import org.transdroid.daemon.task.ForceRecheckTask;
import org.transdroid.daemon.task.GetFileListTask; import org.transdroid.daemon.task.GetFileListTask;
import org.transdroid.daemon.task.GetFileListTaskSuccessResult; import org.transdroid.daemon.task.GetFileListTaskSuccessResult;
@ -124,6 +124,8 @@ import org.transdroid.daemon.task.SetTrackersTask;
import org.transdroid.daemon.task.SetTransferRatesTask; import org.transdroid.daemon.task.SetTransferRatesTask;
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 org.transdroid.daemon.task.ToggleFirstLastPieceDownloadTask;
import org.transdroid.daemon.task.ToggleSequentialDownloadTask;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import java.io.File; import java.io.File;
@ -142,6 +144,7 @@ import java.util.Map.Entry;
* Main activity that holds the fragment that shows the torrents list, presents a way to filter the list (via an action bar spinner or list side list) * Main activity that holds the fragment that shows the torrents list, presents a way to filter the list (via an action bar spinner or list side list)
* and potentially shows a torrent details fragment too, if there is room. Task execution such as loading of and adding torrents is performs in this * and potentially shows a torrent details fragment too, if there is room. Task execution such as loading of and adding torrents is performs in this
* activity, using background methods. Finally, the activity offers navigation elements such as access to settings and showing connection issues. * activity, using background methods. Finally, the activity offers navigation elements such as access to settings and showing connection issues.
*
* @author Eric Kok * @author Eric Kok
*/ */
@EActivity(R.layout.activity_torrents) @EActivity(R.layout.activity_torrents)
@ -183,12 +186,6 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
protected ListView filtersList; protected ListView filtersList;
@ViewById @ViewById
protected SearchView filterSearch; protected SearchView filterSearch;
private ListView navigationList;
private FilterListAdapter navigationListAdapter;
private ServerSelectionView serverSelectionView;
private ServerStatusView serverStatusView;
private ActionBarDrawerToggle drawerToggle;
// Settings // Settings
@Bean @Bean
protected ApplicationSettings applicationSettings; protected ApplicationSettings applicationSettings;
@ -209,6 +206,11 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
protected DetailsFragment fragmentDetails; protected DetailsFragment fragmentDetails;
@InstanceState @InstanceState
boolean firstStart = true; boolean firstStart = true;
private ListView navigationList;
private FilterListAdapter navigationListAdapter;
private ServerSelectionView serverSelectionView;
private ServerStatusView serverStatusView;
private ActionBarDrawerToggle drawerToggle;
private MenuItem searchMenu = null; private MenuItem searchMenu = null;
private IDaemonAdapter currentConnection = null; private IDaemonAdapter currentConnection = null;
@ -217,6 +219,34 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
private String awaitingAddLocalFile; private String awaitingAddLocalFile;
private String awaitingAddTitle; private String awaitingAddTitle;
/**
* Handles item selections on the dedicated list of filter items
*/
private OnItemClickListener onFilterListItemClicked = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
navigationList.setItemChecked(position, true);
Object item = navigationList.getAdapter().getItem(position);
if (item instanceof SimpleListItem) {
filterSelected((SimpleListItem) item, false);
}
if (drawerLayout != null)
drawerLayout.closeDrawer(drawerContainer);
}
};
private SearchView.OnQueryTextListener filterQueryTextChanged = new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
// Redirect to filter method which will directly apply it
filterTorrents(newText);
return true;
}
};
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -239,13 +269,8 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
torrentsToolbar.addView(serverSelectionView); torrentsToolbar.addView(serverSelectionView);
} }
actionsToolbar.addView(serverStatusView); actionsToolbar.addView(serverStatusView);
actionsToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
// Redirect to the classic activity implementation so we can use @OptionsItem methods // Redirect to the classic activity implementation so we can use @OptionsItem methods
return onOptionsItemSelected(menuItem); actionsToolbar.setOnMenuItemClickListener(this::onOptionsItemSelected);
}
});
setSupportActionBar(torrentsToolbar); // For direct menu item inflation by the contained fragments setSupportActionBar(torrentsToolbar); // For direct menu item inflation by the contained fragments
getSupportActionBar().setDisplayShowTitleEnabled(false); getSupportActionBar().setDisplayShowTitleEnabled(false);
@ -254,7 +279,7 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
navigationListAdapter.updateServers(applicationSettings.getAllServerSettings()); navigationListAdapter.updateServers(applicationSettings.getAllServerSettings());
navigationListAdapter.updateStatusTypes(StatusType.getAllStatusTypes(this)); navigationListAdapter.updateStatusTypes(StatusType.getAllStatusTypes(this));
// Add an empty labels list (which will be updated later, but the adapter needs to be created now) // Add an empty labels list (which will be updated later, but the adapter needs to be created now)
navigationListAdapter.updateLabels(new ArrayList<Label>()); navigationListAdapter.updateLabels(new ArrayList<>());
// Apply the filters list to the navigation drawer (on phones) or the dedicated side bar (i.e. on tablets) // Apply the filters list to the navigation drawer (on phones) or the dedicated side bar (i.e. on tablets)
if (filtersList != null) { if (filtersList != null) {
@ -264,7 +289,7 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
drawerToggle = drawerToggle =
new ActionBarDrawerToggle(this, drawerLayout, torrentsToolbar, R.string.navigation_opendrawer, 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.addDrawerListener(drawerToggle);
} }
navigationList.setAdapter(navigationListAdapter); navigationList.setAdapter(navigationListAdapter);
navigationList.setOnItemClickListener(onFilterListItemClicked); navigationList.setOnItemClickListener(onFilterListItemClicked);
@ -427,15 +452,12 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
SearchView searchView = new SearchView(torrentsToolbar.getContext()); SearchView searchView = new SearchView(torrentsToolbar.getContext());
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
searchView.setQueryRefinementEnabled(true); searchView.setQueryRefinementEnabled(true);
searchView.setOnSearchClickListener(new OnClickListener() { searchView.setOnSearchClickListener(v -> {
@Override
public void onClick(View v) {
// Pause autorefresh // Pause autorefresh
stopRefresh = true; stopRefresh = true;
stopAutoRefresh(); stopAutoRefresh();
}
}); });
MenuItemCompat.setOnActionExpandListener(item, new MenuItemCompat.OnActionExpandListener() { item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
@Override @Override
public boolean onMenuItemActionExpand(MenuItem item) { public boolean onMenuItemActionExpand(MenuItem item) {
return true; return true;
@ -448,7 +470,7 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
return true; return true;
} }
}); });
MenuItemCompat.setActionView(item, searchView); item.setActionView(searchView);
searchMenu = item; searchMenu = item;
} }
return true; return true;
@ -519,24 +541,9 @@ public class TorrentsActivity extends AppCompatActivity 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);
}
if (drawerLayout != null)
drawerLayout.closeDrawer(drawerContainer);
}
};
/** /**
* 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
* @param forceNewConnection Whether a new connection should be initialised regardless of the old server selection * @param forceNewConnection Whether a new connection should be initialised regardless of the old server selection
*/ */
@ -596,14 +603,15 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
/** /**
* Hides the filter list and details fragment's full view if there is no configured connection * Hides the filter list and details fragment's full view if there is no configured connection
*
* @param hasServerSettings Whether there are server settings available, so we can continue to connect * @param hasServerSettings Whether there are server settings available, so we can continue to connect
*/ */
private void updateFragmentVisibility(boolean hasServerSettings) { private void updateFragmentVisibility(boolean hasServerSettings) {
if (fragmentDetails != null && fragmentDetails.isResumed()) { if (fragmentDetails != null && fragmentDetails.isResumed()) {
if (hasServerSettings) { if (hasServerSettings) {
getFragmentManager().beginTransaction().show(fragmentDetails).commit(); getSupportFragmentManager().beginTransaction().show(fragmentDetails).commit();
} else { } else {
getFragmentManager().beginTransaction().hide(fragmentDetails).commit(); getSupportFragmentManager().beginTransaction().hide(fragmentDetails).commit();
} }
} }
invalidateOptionsMenu(); invalidateOptionsMenu();
@ -611,6 +619,7 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
@Override @Override
protected void onNewIntent(Intent intent) { protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent); setIntent(intent);
handleStartIntent(); handleStartIntent();
} }
@ -869,22 +878,9 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
fragmentTorrents.sortBy(TorrentsSortBy.Size); fragmentTorrents.sortBy(TorrentsSortBy.Size);
} }
private SearchView.OnQueryTextListener filterQueryTextChanged = new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
// Redirect to filter method which will directly apply it
filterTorrents(newText);
return true;
}
};
/** /**
* Redirect the newly entered list filter to the torrents fragment. * Redirect the newly entered list filter to the torrents fragment.
*
* @param newFilterText The newly entered filter (or empty to clear the current filter). * @param newFilterText The newly entered filter (or empty to clear the current filter).
*/ */
public void filterTorrents(String newFilterText) { public void filterTorrents(String newFilterText) {
@ -894,6 +890,7 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
/** /**
* 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 * Shows the a details fragment for the given torrent, either in the dedicated details fragment pane, in the same pane as the torrent list was
* displayed or by starting a details activity. * 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) {
@ -1116,8 +1113,7 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
try { try {
// Write a temporary file with the torrent contents // Write a temporary file with the torrent contents
tempFile = File.createTempFile("transdroid_", ".torrent", getCacheDir()); tempFile = File.createTempFile("transdroid_", ".torrent", getCacheDir());
FileOutputStream output = new FileOutputStream(tempFile); try (FileOutputStream output = new FileOutputStream(tempFile)) {
try {
final byte[] buffer = new byte[1024]; final byte[] buffer = new byte[1024];
int read; int read;
while ((read = input.read(buffer)) != -1) { while ((read = input.read(buffer)) != -1) {
@ -1126,8 +1122,6 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
output.flush(); output.flush();
String fileName = Uri.fromFile(tempFile).toString(); String fileName = Uri.fromFile(tempFile).toString();
addTorrentByFile(fileName, title); addTorrentByFile(fileName, title);
} finally {
output.close();
} }
} catch (IOException e) { } catch (IOException e) {
log.e(this, "Can't write input stream to " + tempFile.toString() + ": " + e.toString()); log.e(this, "Can't write input stream to " + tempFile.toString() + ": " + e.toString());
@ -1305,7 +1299,6 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
@UiThread @UiThread
protected void onCommunicationError(DaemonTaskFailureResult result, boolean isCritical) { protected void onCommunicationError(DaemonTaskFailureResult result, boolean isCritical) {
//noinspection ThrowableResultOfMethodCallIgnored
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()));
SnackbarManager.show(Snackbar.with(this).text(error).colorResource(R.color.red).type(SnackbarType.MULTI_LINE)); SnackbarManager.show(Snackbar.with(this).text(error).colorResource(R.color.red).type(SnackbarType.MULTI_LINE));

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

@ -16,12 +16,7 @@
*/ */
package org.transdroid.core.gui; package org.transdroid.core.gui;
import android.app.Fragment;
import android.content.Context; import android.content.Context;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.ActionMenuView;
import androidx.appcompat.widget.Toolbar;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -31,6 +26,12 @@ import android.widget.ListView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.ActionMenuView;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.getbase.floatingactionbutton.FloatingActionsMenu; import com.getbase.floatingactionbutton.FloatingActionsMenu;
import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.AfterViews;
@ -64,18 +65,19 @@ import java.util.Locale;
/** /**
* Fragment that shows a list of torrents that are active on the server. It supports sorting and filtering and can show connection progress and * Fragment that shows a list of torrents that are active on the server. It supports sorting and filtering and can show connection progress and
* issues. However, actual task starting and execution and overall navigation elements are part of the containing activity, not this fragment. * issues. However, actual task starting and execution and overall navigation elements are part of the containing activity, not this fragment.
*
* @author Eric Kok * @author Eric Kok
*/ */
@EFragment(R.layout.fragment_torrents) @EFragment(R.layout.fragment_torrents)
public class TorrentsFragment extends Fragment implements OnLabelPickedListener { public class TorrentsFragment extends Fragment implements OnLabelPickedListener {
// HACK Working around #391 while hopefully we rework the UI in the future to persist the list in db or something
protected static ArrayList<Torrent> torrents = null;
// Local data // Local data
@Bean @Bean
protected ApplicationSettings applicationSettings; protected ApplicationSettings applicationSettings;
@Bean @Bean
protected SystemSettings systemSettings; protected SystemSettings systemSettings;
// HACK Working around #391 while hopefully we rework the UI in the future to persist the list in db or something
protected static ArrayList<Torrent> torrents = null;
@InstanceState @InstanceState
protected ArrayList<Torrent> lastMultiSelectedTorrents; protected ArrayList<Torrent> lastMultiSelectedTorrents;
@InstanceState @InstanceState
@ -110,6 +112,128 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
protected TextView errorText; protected TextView errorText;
@ViewById @ViewById
protected ProgressBar loadingProgress; protected ProgressBar loadingProgress;
private MultiChoiceModeListener onTorrentsSelected = new MultiChoiceModeListener() {
private SelectionManagerMode selectionManagerMode;
private ActionMenuView actionsMenu;
private Toolbar actionsToolbar;
private FloatingActionsMenu addmenuButton;
@Override
public boolean onCreateActionMode(final ActionMode mode, Menu menu) {
// Show contextual action bars to start/stop/remove/etc. torrents in batch mode
if (actionsMenu == null) {
actionsMenu = ((TorrentsActivity) getActivity()).contextualMenu;
actionsToolbar = ((TorrentsActivity) getActivity()).actionsToolbar;
addmenuButton = ((TorrentsActivity) getActivity()).addmenuButton;
}
actionsToolbar.setEnabled(false);
actionsMenu.setVisibility(View.VISIBLE);
addmenuButton.setVisibility(View.GONE);
actionsMenu.setOnMenuItemClickListener(menuItem -> onActionItemClicked(mode, menuItem));
actionsMenu.getMenu().clear();
getActivity().getMenuInflater().inflate(R.menu.fragment_torrents_cab, actionsMenu.getMenu());
Context themedContext = ((AppCompatActivity) getActivity()).getSupportActionBar().getThemedContext();
selectionManagerMode = new SelectionManagerMode(themedContext, torrentsList, R.plurals.navigation_torrentsselected);
selectionManagerMode.onCreateActionMode(mode, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
selectionManagerMode.onPrepareActionMode(mode, menu);
// Hide/show options depending on the type of server we are connected to
if (daemonType != null) {
actionsMenu.getMenu().findItem(R.id.action_start).setVisible(Daemon.supportsStoppingStarting(daemonType));
actionsMenu.getMenu().findItem(R.id.action_stop).setVisible(Daemon.supportsStoppingStarting(daemonType));
actionsMenu.getMenu().findItem(R.id.action_setlabel).setVisible(Daemon.supportsSetLabel(daemonType));
}
// Pause autorefresh
if (getActivity() != null && getActivity() instanceof TorrentsActivity) {
((TorrentsActivity) getActivity()).stopRefresh = true;
((TorrentsActivity) getActivity()).stopAutoRefresh();
}
return true;
}
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// Get checked torrents
ArrayList<Torrent> checked = new ArrayList<>();
for (int i = 0; i < torrentsList.getCheckedItemPositions().size(); i++) {
if (torrentsList.getCheckedItemPositions().valueAt(i) && i < torrentsList.getAdapter().getCount()) {
checked.add((Torrent) torrentsList.getAdapter().getItem(torrentsList.getCheckedItemPositions().keyAt(i)));
}
}
int itemId = item.getItemId();
if (itemId == R.id.action_resume) {
for (Torrent torrent : checked) {
getTasksExecutor().resumeTorrent(torrent);
}
mode.finish();
return true;
} else if (itemId == R.id.action_pause) {
for (Torrent torrent : checked) {
getTasksExecutor().pauseTorrent(torrent);
}
mode.finish();
return true;
} else if (itemId == R.id.action_start) {
for (Torrent torrent : checked) {
getTasksExecutor().startTorrent(torrent, false);
}
mode.finish();
return true;
} else if (itemId == R.id.action_stop) {
for (Torrent torrent : checked) {
getTasksExecutor().stopTorrent(torrent);
}
mode.finish();
return true;
} else if (itemId == R.id.action_remove_default) {
for (Torrent torrent : checked) {
getTasksExecutor().removeTorrent(torrent, false);
}
mode.finish();
return true;
} else if (itemId == R.id.action_remove_withdata) {
for (Torrent torrent : checked) {
getTasksExecutor().removeTorrent(torrent, true);
}
mode.finish();
return true;
} else if (itemId == R.id.action_setlabel) {
lastMultiSelectedTorrents = checked;
if (currentLabels != null) {
SetLabelDialog.show(getActivity(), TorrentsFragment.this, currentLabels);
}
mode.finish();
return true;
} else {
return false;
}
}
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
selectionManagerMode.onItemCheckedStateChanged(mode, position, id, checked);
}
@Override
public void onDestroyActionMode(ActionMode mode) {
// Resume autorefresh
if (getActivity() != null && getActivity() instanceof TorrentsActivity) {
((TorrentsActivity) getActivity()).stopRefresh = false;
((TorrentsActivity) getActivity()).startAutoRefresh();
}
selectionManagerMode.onDestroyActionMode(mode);
actionsMenu.setVisibility(View.GONE);
actionsToolbar.setEnabled(true);
addmenuButton.setVisibility(View.VISIBLE);
}
};
@AfterViews @AfterViews
protected void init() { protected void init() {
@ -127,12 +251,9 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
} }
// 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) {
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { swipeRefreshLayout.setOnRefreshListener(() -> {
@Override
public void onRefresh() {
((RefreshableActivity) getActivity()).refreshScreen(); ((RefreshableActivity) getActivity()).refreshScreen();
swipeRefreshLayout.setRefreshing(false); // Use our custom indicator swipeRefreshLayout.setRefreshing(false); // Use our custom indicator
}
}); });
} }
nosettingsText.setText(getString(R.string.navigation_nosettings, getString(R.string.app_name))); nosettingsText.setText(getString(R.string.navigation_nosettings, getString(R.string.app_name)));
@ -141,6 +262,7 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
/** /**
* Updates the list adapter to show a new list of torrent objects, replacing the old torrents completely * Updates the list adapter to show a new list of torrent objects, replacing the old torrents completely
*
* @param newTorrents The new, updated list of torrents * @param newTorrents The new, updated list of torrents
*/ */
public void updateTorrents(ArrayList<Torrent> newTorrents, ArrayList<Label> currentLabels) { public void updateTorrents(ArrayList<Torrent> newTorrents, ArrayList<Label> currentLabels) {
@ -155,6 +277,7 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
/** /**
* Just look for a specific torrent in the currently shown list (by its unique id) and update only this * Just look for a specific torrent in the currently shown list (by its unique id) and update only this
*
* @param affected The affected torrent to update * @param affected The affected torrent to update
* @param wasRemoved Whether the affected torrent was indeed removed; otherwise it was updated somehow * @param wasRemoved Whether the affected torrent was indeed removed; otherwise it was updated somehow
*/ */
@ -182,6 +305,7 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
/** /**
* Clears the currently visible list of torrents. * Clears the currently visible list of torrents.
*
* @param clearError Also clear any error message * @param clearError Also clear any error message
* @param clearFilter Also clear any selected filter * @param clearFilter Also clear any selected filter
*/ */
@ -200,6 +324,7 @@ 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 property equals the existing * Stores the new sort order (for future refreshes) and sorts the current visible list. If the given new sort property equals the existing
* property, the list sort order is reversed instead. * 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) {
@ -222,6 +347,7 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
/** /**
* Apply a filter on the current list of all torrents, showing the appropriate sublist of torrents only * Apply a filter on the current list of all torrents, showing the appropriate sublist of torrents only
*
* @param newFilter The new filter to apply to the local list of torrents * @param newFilter The new filter to apply to the local list of torrents
*/ */
public void applyNavigationFilter(NavigationFilter newFilter) { public void applyNavigationFilter(NavigationFilter newFilter) {
@ -265,134 +391,6 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
updateViewVisibility(); updateViewVisibility();
} }
private MultiChoiceModeListener onTorrentsSelected = new MultiChoiceModeListener() {
private SelectionManagerMode selectionManagerMode;
private ActionMenuView actionsMenu;
private Toolbar actionsToolbar;
private FloatingActionsMenu addmenuButton;
@Override
public boolean onCreateActionMode(final ActionMode mode, Menu menu) {
// Show contextual action bars to start/stop/remove/etc. torrents in batch mode
if (actionsMenu == null) {
actionsMenu = ((TorrentsActivity) getActivity()).contextualMenu;
actionsToolbar = ((TorrentsActivity) getActivity()).actionsToolbar;
addmenuButton = ((TorrentsActivity) getActivity()).addmenuButton;
}
actionsToolbar.setEnabled(false);
actionsMenu.setVisibility(View.VISIBLE);
addmenuButton.setVisibility(View.GONE);
actionsMenu.setOnMenuItemClickListener(new ActionMenuView.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
return onActionItemClicked(mode, menuItem);
}
});
actionsMenu.getMenu().clear();
getActivity().getMenuInflater().inflate(R.menu.fragment_torrents_cab, actionsMenu.getMenu());
Context themedContext = ((AppCompatActivity) getActivity()).getSupportActionBar().getThemedContext();
selectionManagerMode = new SelectionManagerMode(themedContext, torrentsList, R.plurals.navigation_torrentsselected);
selectionManagerMode.onCreateActionMode(mode, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
selectionManagerMode.onPrepareActionMode(mode, menu);
// Hide/show options depending on the type of server we are connected to
if (daemonType != null) {
actionsMenu.getMenu().findItem(R.id.action_start).setVisible(Daemon.supportsStoppingStarting(daemonType));
actionsMenu.getMenu().findItem(R.id.action_stop).setVisible(Daemon.supportsStoppingStarting(daemonType));
actionsMenu.getMenu().findItem(R.id.action_setlabel).setVisible(Daemon.supportsSetLabel(daemonType));
}
// Pause autorefresh
if (getActivity() != null && getActivity() instanceof TorrentsActivity) {
((TorrentsActivity) getActivity()).stopRefresh = true;
((TorrentsActivity) getActivity()).stopAutoRefresh();
}
return true;
}
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// Get checked torrents
ArrayList<Torrent> checked = new ArrayList<>();
for (int i = 0; i < torrentsList.getCheckedItemPositions().size(); i++) {
if (torrentsList.getCheckedItemPositions().valueAt(i) && i < torrentsList.getAdapter().getCount()) {
checked.add((Torrent) torrentsList.getAdapter().getItem(torrentsList.getCheckedItemPositions().keyAt(i)));
}
}
int itemId = item.getItemId();
if (itemId == R.id.action_resume) {
for (Torrent torrent : checked) {
getTasksExecutor().resumeTorrent(torrent);
}
mode.finish();
return true;
} else if (itemId == R.id.action_pause) {
for (Torrent torrent : checked) {
getTasksExecutor().pauseTorrent(torrent);
}
mode.finish();
return true;
} else if (itemId == R.id.action_start) {
for (Torrent torrent : checked) {
getTasksExecutor().startTorrent(torrent, false);
}
mode.finish();
return true;
} else if (itemId == R.id.action_stop) {
for (Torrent torrent : checked) {
getTasksExecutor().stopTorrent(torrent);
}
mode.finish();
return true;
} else if (itemId == R.id.action_remove_default) {
for (Torrent torrent : checked) {
getTasksExecutor().removeTorrent(torrent, false);
}
mode.finish();
return true;
} else if (itemId == R.id.action_remove_withdata) {
for (Torrent torrent : checked) {
getTasksExecutor().removeTorrent(torrent, true);
}
mode.finish();
return true;
} else if (itemId == R.id.action_setlabel) {
lastMultiSelectedTorrents = checked;
if (currentLabels != null) {
SetLabelDialog.show(getActivity(), TorrentsFragment.this, currentLabels);
}
mode.finish();
return true;
} else {
return false;
}
}
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
selectionManagerMode.onItemCheckedStateChanged(mode, position, id, checked);
}
@Override
public void onDestroyActionMode(ActionMode mode) {
// Resume autorefresh
if (getActivity() != null && getActivity() instanceof TorrentsActivity) {
((TorrentsActivity) getActivity()).stopRefresh = false;
((TorrentsActivity) getActivity()).startAutoRefresh();
}
selectionManagerMode.onDestroyActionMode(mode);
actionsMenu.setVisibility(View.GONE);
actionsToolbar.setEnabled(true);
addmenuButton.setVisibility(View.VISIBLE);
}
};
@Click @Click
protected void emptyTextClicked() { protected void emptyTextClicked() {
// Refresh the activity (that contains this fragment) when the empty view gear is clicked // Refresh the activity (that contains this fragment) when the empty view gear is clicked
@ -425,6 +423,7 @@ 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 need to show a message * Updates the shown screen depending on whether we have a connection (so torrents can be shown) or not (in case we need to show a message
* suggesting help). This should only ever be called on the UI thread. * 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) {
@ -446,6 +445,7 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
/** /**
* Updates the shown screen depending on whether the torrents are loading. This should only ever be called on the UI thread. * Updates the shown screen depending on whether the torrents are loading. This should only ever be called on the UI thread.
*
* @param isLoading True if the list of torrents is (re)loading, false otherwise * @param isLoading True if the list of torrents is (re)loading, false otherwise
*/ */
public void updateIsLoading(boolean isLoading) { public void updateIsLoading(boolean isLoading) {
@ -460,6 +460,7 @@ 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 UI thread. * Updates the shown screen depending on whether a connection error occurred. This should only ever be called on the UI thread.
*
* @param connectionErrorMessage The error message from the last failed connection attempt, or null to clear the visible error text * @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) {
@ -489,6 +490,7 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
/** /**
* Returns the object responsible for executing torrent tasks against a connected server * Returns the object responsible for executing torrent tasks against a connected server
*
* @return The executor for tasks on some torrent * @return The executor for tasks on some torrent
*/ */
private TorrentTasksExecutor getTasksExecutor() { private TorrentTasksExecutor getTasksExecutor() {

12
app/src/main/java/org/transdroid/core/gui/TransdroidApp.java

@ -17,11 +17,10 @@
package org.transdroid.core.gui; package org.transdroid.core.gui;
import android.app.Application; import android.app.Application;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.evernote.android.job.JobConfig; import com.evernote.android.job.JobConfig;
import com.evernote.android.job.JobManager; import com.evernote.android.job.JobManager;
import com.evernote.android.job.util.JobLogger;
import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EApplication; import org.androidannotations.annotations.EApplication;
import org.transdroid.core.gui.log.Log; import org.transdroid.core.gui.log.Log;
@ -38,12 +37,7 @@ public class TransdroidApp extends Application {
super.onCreate(); super.onCreate();
// Configure Android-Job // Configure Android-Job
JobConfig.addLogger(new JobLogger() { JobConfig.addLogger((priority, tag, message, t) -> log.d(tag, message));
@Override
public void log(int priority, @NonNull String tag, @NonNull String message, @Nullable Throwable t) {
log.d(tag, message);
}
});
JobManager.create(this).addJobCreator(new ScheduledJobCreator()); JobManager.create(this).addJobCreator(new ScheduledJobCreator());
} }

31
app/src/main/java/org/transdroid/core/gui/lists/DetailsAdapter.java

@ -16,23 +16,23 @@
*/ */
package org.transdroid.core.gui.lists; package org.transdroid.core.gui.lists;
import java.util.ArrayList;
import java.util.List;
import org.transdroid.R;
import org.transdroid.core.gui.navigation.*;
import org.transdroid.core.gui.lists.PiecesMapView;
import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentFile;
import android.content.Context; import android.content.Context;
import android.text.util.Linkify; import android.text.util.Linkify;
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.R;
import org.transdroid.core.gui.navigation.FilterSeparatorView_;
import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentFile;
import java.util.ArrayList;
import java.util.List;
/** /**
* List adapter that holds a header view showing torrent details and show the list list contained by the torrent. * List adapter that holds a header view showing torrent details and show the list list contained by the torrent.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class DetailsAdapter extends MergeAdapter { public class DetailsAdapter extends MergeAdapter {
@ -78,7 +78,7 @@ public class DetailsAdapter extends MergeAdapter {
errorsSeparatorAdapter.setViewEnabled(false); errorsSeparatorAdapter.setViewEnabled(false);
errorsSeparatorAdapter.setViewVisibility(View.GONE); errorsSeparatorAdapter.setViewVisibility(View.GONE);
addAdapter(errorsSeparatorAdapter); addAdapter(errorsSeparatorAdapter);
this.errorsAdapter = new SimpleListItemAdapter(context, new ArrayList<SimpleListItem>()); this.errorsAdapter = new SimpleListItemAdapter(context, new ArrayList<>());
this.errorsAdapter.setAutoLinkMask(Linkify.WEB_URLS); this.errorsAdapter.setAutoLinkMask(Linkify.WEB_URLS);
addAdapter(errorsAdapter); addAdapter(errorsAdapter);
@ -88,7 +88,7 @@ public class DetailsAdapter extends MergeAdapter {
trackersSeparatorAdapter.setViewEnabled(false); trackersSeparatorAdapter.setViewEnabled(false);
trackersSeparatorAdapter.setViewVisibility(View.GONE); trackersSeparatorAdapter.setViewVisibility(View.GONE);
addAdapter(trackersSeparatorAdapter); addAdapter(trackersSeparatorAdapter);
this.trackersAdapter = new SimpleListItemAdapter(context, new ArrayList<SimpleListItem>()); this.trackersAdapter = new SimpleListItemAdapter(context, new ArrayList<>());
addAdapter(trackersAdapter); addAdapter(trackersAdapter);
// Torrent files // Torrent files
@ -97,13 +97,14 @@ public class DetailsAdapter extends MergeAdapter {
torrentFilesSeparatorAdapter.setViewEnabled(false); torrentFilesSeparatorAdapter.setViewEnabled(false);
torrentFilesSeparatorAdapter.setViewVisibility(View.GONE); torrentFilesSeparatorAdapter.setViewVisibility(View.GONE);
addAdapter(torrentFilesSeparatorAdapter); addAdapter(torrentFilesSeparatorAdapter);
this.torrentFilesAdapter = new TorrentFilesAdapter(context, new ArrayList<TorrentFile>()); this.torrentFilesAdapter = new TorrentFilesAdapter(context, new ArrayList<>());
addAdapter(torrentFilesAdapter); addAdapter(torrentFilesAdapter);
} }
/** /**
* Update the torrent data in the details header of this merge adapter * Update the torrent data in the details header of this merge adapter
*
* @param torrent The torrent for which detailed data is shown * @param torrent The torrent for which detailed data is shown
*/ */
public void updateTorrent(Torrent torrent) { public void updateTorrent(Torrent torrent) {
@ -113,11 +114,12 @@ public class DetailsAdapter extends MergeAdapter {
/** /**
* Update the list of files contained in this torrent * Update the list of files contained in this torrent
*
* @param torrentFiles The new list of files, or null if the list and header should be hidden * @param torrentFiles The new list of files, or null if the list and header should be hidden
*/ */
public void updateTorrentFiles(List<TorrentFile> torrentFiles) { public void updateTorrentFiles(List<TorrentFile> torrentFiles) {
if (torrentFiles == null) { if (torrentFiles == null) {
torrentFilesAdapter.update(new ArrayList<TorrentFile>()); torrentFilesAdapter.update(new ArrayList<>());
torrentFilesSeparatorAdapter.setViewVisibility(View.GONE); torrentFilesSeparatorAdapter.setViewVisibility(View.GONE);
} else { } else {
torrentFilesAdapter.update(torrentFiles); torrentFilesAdapter.update(torrentFiles);
@ -127,6 +129,7 @@ public class DetailsAdapter extends MergeAdapter {
/** /**
* Update the list of trackers * Update the list of trackers
*
* @param trackers The new list of trackers known for this torrent, or null if the list and header should be hidden * @param trackers The new list of trackers known for this torrent, or null if the list and header should be hidden
*/ */
public void updateTrackers(List<? extends SimpleListItem> trackers) { public void updateTrackers(List<? extends SimpleListItem> trackers) {
@ -141,6 +144,7 @@ public class DetailsAdapter extends MergeAdapter {
/** /**
* Update the list of errors * Update the list of errors
*
* @param errors The new list of errors known for this torrent, or null if the list and header should be hidden * @param errors The new list of errors known for this torrent, or null if the list and header should be hidden
*/ */
public void updateErrors(List<? extends SimpleListItem> errors) { public void updateErrors(List<? extends SimpleListItem> errors) {
@ -191,6 +195,7 @@ public class DetailsAdapter extends MergeAdapter {
/** /**
* Allows updating of the full data list underlying this adapter, replacing all items * Allows updating of the full data list underlying this adapter, replacing all items
*
* @param newItems The new list of files to display * @param newItems The new list of files to display
*/ */
public void update(List<TorrentFile> newItems) { public void update(List<TorrentFile> newItems) {

86
app/src/main/java/org/transdroid/core/gui/lists/LocalTorrent.java

@ -16,7 +16,7 @@
*/ */
package org.transdroid.core.gui.lists; package org.transdroid.core.gui.lists;
import java.util.Locale; import android.content.res.Resources;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
@ -25,17 +25,27 @@ import org.transdroid.daemon.TorrentStatus;
import org.transdroid.daemon.util.FileSizeConverter; import org.transdroid.daemon.util.FileSizeConverter;
import org.transdroid.daemon.util.TimespanConverter; import org.transdroid.daemon.util.TimespanConverter;
import android.content.res.Resources; import java.util.Locale;
/** /**
* Wrapper around Torrent to provide some addition getters that give translatable or otherwise formatted Strings of * Wrapper around Torrent to provide some addition getters that give translatable or otherwise formatted Strings of
* torrent statistics. * torrent statistics.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class LocalTorrent { public class LocalTorrent {
private static final String DECIMAL_FORMATTER = "%.1f";
private static final String DECIMAL_FORMATTER_2 = "%.2f";
private final Torrent t;
private LocalTorrent(Torrent torrent) {
this.t = torrent;
}
/** /**
* Creates the LocalTorrent object so that the translatable/formattable version of a Torrent can be used. * Creates the LocalTorrent object so that the translatable/formattable version of a Torrent can be used.
*
* @param torrent The Torrent object * @param torrent The Torrent object
* @return The torrent wrapped as LocalTorrent object * @return The torrent wrapped as LocalTorrent object
*/ */
@ -43,19 +53,37 @@ public class LocalTorrent {
return new LocalTorrent(torrent); return new LocalTorrent(torrent);
} }
private final Torrent t; /**
* Convert a DaemonException to a translatable human-readable error message
private LocalTorrent(Torrent torrent) { *
this.t = torrent; * @param e The exception that was thrown by the server
* @return A string resource ID to show to the user
*/
public static int getResourceForDaemonException(DaemonException e) {
switch (e.getType()) {
case MethodUnsupported:
return R.string.error_unsupported;
case UnexpectedResponse:
return R.string.error_jsonresponseerror;
case ParsingFailed:
return R.string.error_jsonrequesterror;
case NotConnected:
return R.string.error_daemonnotconnected;
case AuthenticationFailure:
return R.string.error_401;
case FileAccessError:
return R.string.error_torrentfile;
case ConnectionError:
default:
return R.string.error_httperror;
}
} }
private static final String DECIMAL_FORMATTER = "%.1f";
private static final String DECIMAL_FORMATTER_2 = "%.2f";
/** /**
* Builds a string showing the upload/download seed ratio. If not downloading, it will base the ratio on the total * Builds a string showing the upload/download seed ratio. If not downloading, it will base the ratio on the total
* size; so if you created the torrent yourself you will have downloaded 0 bytes, but the ratio will pretend you * size; so if you created the torrent yourself you will have downloaded 0 bytes, but the ratio will pretend you
* have 100%. * have 100%.
*
* @return A nicely formatted string containing the upload/download seed ratio * @return A nicely formatted string containing the upload/download seed ratio
*/ */
public String getRatioString() { public String getRatioString() {
@ -74,6 +102,7 @@ public class LocalTorrent {
/** /**
* Returns a formatted string indicating the current progress in terms of transferred bytes * Returns a formatted string indicating the current progress in terms of transferred bytes
*
* @param r The context resources, to access translations * @param r The context resources, to access translations
* @param withAvailability Whether to show file availability in-line * @param withAvailability Whether to show file availability in-line
* @return A nicely formatted string indicating torrent status and, if applicable, progress in bytes * @return A nicely formatted string indicating torrent status and, if applicable, progress in bytes
@ -111,6 +140,7 @@ public class LocalTorrent {
/** /**
* Returns a formatted string indicating either the expected time to download (ETA) or, when seeding, the ratio * Returns a formatted string indicating either the expected time to download (ETA) or, when seeding, the ratio
*
* @param r The context resources, to access translations * @param r The context resources, to access translations
* @return A string like '~ 34 seconds', or 'RATIO 8.2' or an empty string * @return A string like '~ 34 seconds', or 'RATIO 8.2' or an empty string
*/ */
@ -134,6 +164,7 @@ public class LocalTorrent {
/** /**
* Returns a formatted string indicating the torrent status and connected peers * Returns a formatted string indicating the torrent status and connected peers
*
* @param r The context resources, to access translations * @param r The context resources, to access translations
* @return A string like 'Queued' or, when seeding or leeching, '2 OF 28 PEERS' * @return A string like 'Queued' or, when seeding or leeching, '2 OF 28 PEERS'
*/ */
@ -162,22 +193,22 @@ public class LocalTorrent {
/** /**
* Returns a formatted string indicating current transfer speeds for the torrent * Returns a formatted string indicating current transfer speeds for the torrent
*
* @param r The context resources, to access translations * @param r The context resources, to access translations
* @return A string like ' 28KB/s 1.8MB/s', or an empty string when not transferrring * @return A string like ' 28KB/s 1.8MB/s', or an empty string when not transferrring
*/ */
public String getProgressSpeedText(Resources r) { public String getProgressSpeedText(Resources r) {
switch (t.getStatusCode()) { switch (t.getStatusCode()) {
case Waiting:
case Checking:
case Paused:
case Queued:
return "";
case Downloading: case Downloading:
return r.getString(R.string.status_speed_down, FileSizeConverter.getSize(t.getRateDownload()) + "/s") + " " return r.getString(R.string.status_speed_down, FileSizeConverter.getSize(t.getRateDownload()) + "/s") + " "
+ r.getString(R.string.status_speed_up, FileSizeConverter.getSize(t.getRateUpload()) + "/s"); + r.getString(R.string.status_speed_up, FileSizeConverter.getSize(t.getRateUpload()) + "/s");
case Seeding: case Seeding:
return r.getString(R.string.status_speed_up, FileSizeConverter.getSize(t.getRateUpload()) + "/s"); return r.getString(R.string.status_speed_up, FileSizeConverter.getSize(t.getRateUpload()) + "/s");
case Waiting:
case Checking:
case Paused:
case Queued:
default: default:
return ""; return "";
} }
@ -210,6 +241,7 @@ public class LocalTorrent {
/** /**
* Returns a formatted string indicating the remaining download time * Returns a formatted string indicating the remaining download time
*
* @param r The context resources, to access translations * @param r The context resources, to access translations
* @param inDays Whether to show days or use hours for > 24 hours left instead * @param inDays Whether to show days or use hours for > 24 hours left instead
* @return A string like '4d 8h 34m 5s' or '2m 3s' * @return A string like '4d 8h 34m 5s' or '2m 3s'
@ -222,30 +254,4 @@ public class LocalTorrent {
TimespanConverter.getTime(t.getEta(), inDays)); TimespanConverter.getTime(t.getEta(), inDays));
} }
/**
* Convert a DaemonException to a translatable human-readable error message
* @param e The exception that was thrown by the server
* @return A string resource ID to show to the user
*/
public static int getResourceForDaemonException(DaemonException e) {
switch (e.getType()) {
case MethodUnsupported:
return R.string.error_unsupported;
case ConnectionError:
return R.string.error_httperror;
case UnexpectedResponse:
return R.string.error_jsonresponseerror;
case ParsingFailed:
return R.string.error_jsonrequesterror;
case NotConnected:
return R.string.error_daemonnotconnected;
case AuthenticationFailure:
return R.string.error_401;
case FileAccessError:
return R.string.error_torrentfile;
default:
return R.string.error_httperror;
}
}
} }

21
app/src/main/java/org/transdroid/core/gui/lists/MergeAdapter.java

@ -16,8 +16,6 @@
*/ */
package org.transdroid.core.gui.lists; package org.transdroid.core.gui.lists;
import java.util.ArrayList;
import android.database.DataSetObserver; import android.database.DataSetObserver;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -27,17 +25,21 @@ import android.widget.ListAdapter;
import android.widget.SectionIndexer; import android.widget.SectionIndexer;
import android.widget.TextView; import android.widget.TextView;
import java.util.ArrayList;
import java.util.Arrays;
/** /**
* An adapter that can contain many other adapters and shows them in sequence. Taken from * An adapter that can contain many other adapters and shows them in sequence. Taken from
* http://stackoverflow.com/questions/7964259/android-attaching-multiple-adapters-to-one-adapter and based on the Apache * http://stackoverflow.com/questions/7964259/android-attaching-multiple-adapters-to-one-adapter and based on the Apache
* 2-licensed CommonsWare MergeAdapter. * 2-licensed CommonsWare MergeAdapter.
*
* @author Eric Kok * @author Eric Kok
* @author Alex Amiryan * @author Alex Amiryan
* @author Mark Murphy * @author Mark Murphy
*/ */
public class MergeAdapter extends BaseAdapter implements SectionIndexer { public class MergeAdapter extends BaseAdapter implements SectionIndexer {
protected ArrayList<ListAdapter> pieces = new ArrayList<ListAdapter>(); protected ArrayList<ListAdapter> pieces = new ArrayList<>();
protected String noItemsText; protected String noItemsText;
/** /**
@ -49,6 +51,7 @@ public class MergeAdapter extends BaseAdapter implements SectionIndexer {
/** /**
* Adds a new adapter to the roster of things to appear in the aggregate list. * Adds a new adapter to the roster of things to appear in the aggregate list.
*
* @param adapter Source for row views for this section * @param adapter Source for row views for this section
*/ */
public void addAdapter(ListAdapter adapter) { public void addAdapter(ListAdapter adapter) {
@ -58,6 +61,7 @@ public class MergeAdapter extends BaseAdapter implements SectionIndexer {
/** /**
* Get the data item associated with the specified position in the data set. * Get the data item associated with the specified position in the data set.
*
* @param position Position of the item whose data we want * @param position Position of the item whose data we want
*/ */
public Object getItem(int position) { public Object getItem(int position) {
@ -80,6 +84,7 @@ public class MergeAdapter extends BaseAdapter implements SectionIndexer {
/** /**
* Get the adapter associated with the specified position in the data set. * Get the adapter associated with the specified position in the data set.
*
* @param position Position of the item whose adapter we want * @param position Position of the item whose adapter we want
*/ */
public ListAdapter getAdapter(int position) { public ListAdapter getAdapter(int position) {
@ -130,6 +135,7 @@ public class MergeAdapter extends BaseAdapter implements SectionIndexer {
/** /**
* Get the type of {@link View} that will be created by {@link #getView(int, View, ViewGroup)} for the specified item. * Get the type of {@link View} that will be created by {@link #getView(int, View, ViewGroup)} for the specified item.
*
* @param position Position of the item whose data we want * @param position Position of the item whose data we want
*/ */
@Override @Override
@ -162,6 +168,7 @@ public class MergeAdapter extends BaseAdapter implements SectionIndexer {
/** /**
* Returns true if the item at the specified position is not a separator. * Returns true if the item at the specified position is not a separator.
*
* @param position Position of the item whose data we want * @param position Position of the item whose data we want
*/ */
@Override @Override
@ -181,6 +188,7 @@ public class MergeAdapter extends BaseAdapter implements SectionIndexer {
/** /**
* Get a {@link View} that displays the data at the specified position in the data set. * Get a {@link View} that displays the data at the specified position in the data set.
*
* @param position Position of the item whose data we want * @param position Position of the item whose data we want
* @param convertView View to recycle, if not null * @param convertView View to recycle, if not null
* @param parent ViewGroup containing the returned View * @param parent ViewGroup containing the returned View
@ -208,6 +216,7 @@ public class MergeAdapter extends BaseAdapter implements SectionIndexer {
/** /**
* Get the row id associated with the specified position in the list. * Get the row id associated with the specified position in the list.
*
* @param position Position of the item whose data we want * @param position Position of the item whose data we want
*/ */
public long getItemId(int position) { public long getItemId(int position) {
@ -278,16 +287,14 @@ public class MergeAdapter extends BaseAdapter implements SectionIndexer {
} }
public final Object[] getSections() { public final Object[] getSections() {
ArrayList<Object> sections = new ArrayList<Object>(); ArrayList<Object> sections = new ArrayList<>();
for (ListAdapter piece : pieces) { for (ListAdapter piece : pieces) {
if (piece instanceof SectionIndexer) { if (piece instanceof SectionIndexer) {
Object[] curSections = ((SectionIndexer) piece).getSections(); Object[] curSections = ((SectionIndexer) piece).getSections();
if (curSections != null) { if (curSections != null) {
for (Object section : curSections) { sections.addAll(Arrays.asList(curSections));
sections.add(section);
}
} }
} }
} }

30
app/src/main/java/org/transdroid/core/gui/lists/PiecesMapView.java

@ -1,27 +1,24 @@
package org.transdroid.core.gui.lists; package org.transdroid.core.gui.lists;
import org.transdroid.R;
import android.content.Context; import android.content.Context;
import android.view.View;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.view.View;
import org.transdroid.R;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.lang.Math;
class PiecesMapView extends View { class PiecesMapView extends View {
private final float scale = getContext().getResources().getDisplayMetrics().density; private final float scale = getContext().getResources().getDisplayMetrics().density;
private final int MINIMUM_HEIGHT = (int) (25 * scale); private final int MINIMUM_HEIGHT = (int) (25 * scale);
private final int MINIMUM_PIECE_WIDTH = (int) (2 * scale); private final int MINIMUM_PIECE_WIDTH = (int) (2 * scale);
private ArrayList<Integer> pieces = null;
private final Paint downloadingPaint = new Paint(); private final Paint downloadingPaint = new Paint();
private final Paint donePaint = new Paint(); private final Paint donePaint = new Paint();
private final Paint partialDonePaint = new Paint(); private final Paint partialDonePaint = new Paint();
private ArrayList<Integer> pieces = null;
public PiecesMapView(Context context) { public PiecesMapView(Context context) {
super(context); super(context);
@ -35,7 +32,7 @@ class PiecesMapView extends View {
} }
public void setPieces(List<Integer> pieces) { public void setPieces(List<Integer> pieces) {
this.pieces = new ArrayList<Integer>(pieces); this.pieces = new ArrayList<>(pieces);
invalidate(); invalidate();
} }
@ -62,10 +59,10 @@ class PiecesMapView extends View {
int pieceWidth; int pieceWidth;
pieceWidth = MINIMUM_PIECE_WIDTH; pieceWidth = MINIMUM_PIECE_WIDTH;
piecesScaled = new ArrayList<Integer>(); piecesScaled = new ArrayList<>();
int bucketCount = (int) Math.ceil((double) width / (double) pieceWidth); int bucketCount = (int) Math.ceil((double) width / (double) pieceWidth);
int bucketSize = (int) Math.floor((double)this.pieces.size() / (double) bucketCount); int bucketSize = (int) Math.floor((double) this.pieces.size() / (double) bucketCount);
// loop buckets // loop buckets
for (int i = 0; i < bucketCount; i++) { for (int i = 0; i < bucketCount; i++) {
@ -74,15 +71,15 @@ class PiecesMapView extends View {
int start = i * bucketSize; int start = i * bucketSize;
// If this is the last bucket, throw the remainder of the pieces array into it // If this is the last bucket, throw the remainder of the pieces array into it
int end = (i == bucketCount-1) ? this.pieces.size() : (i+1) * bucketSize; int end = (i == bucketCount - 1) ? this.pieces.size() : (i + 1) * bucketSize;
ArrayList<Integer> bucket = new ArrayList<Integer>(this.pieces.subList(start, end)); ArrayList<Integer> bucket = new ArrayList<>(this.pieces.subList(start, end));
int doneCount = 0; int doneCount = 0;
int downloadingCount = 0; int downloadingCount = 0;
// loop pieces in bucket // loop pieces in bucket
for(int j = 0; j < bucket.size(); j++) { for (int j = 0; j < bucket.size(); j++) {
// Count downloading pieces // Count downloading pieces
if (bucket.get(j) == 1) { if (bucket.get(j) == 1) {
downloadingCount++; downloadingCount++;
@ -114,10 +111,9 @@ class PiecesMapView extends View {
piecesScaled.add(state); piecesScaled.add(state);
} }
String scaledPiecesString = ""; StringBuilder scaledPiecesString = new StringBuilder();
for (int s : piecesScaled) for (int s : piecesScaled) {
{ scaledPiecesString.append(s);
scaledPiecesString += s;
} }
// Draw downscaled peices // Draw downscaled peices

3
app/src/main/java/org/transdroid/core/gui/lists/SimpleListItem.java

@ -19,10 +19,11 @@ package org.transdroid.core.gui.lists;
/** /**
* Represents a filter item as shown in the navigation list or spinner. * Represents a filter item as shown in the navigation list or spinner.
*
* @author Eric Kok * @author Eric Kok
*/ */
public interface SimpleListItem { public interface SimpleListItem {
public String getName(); String getName();
} }

25
app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemAdapter.java

@ -16,14 +16,14 @@
*/ */
package org.transdroid.core.gui.lists; package org.transdroid.core.gui.lists;
import java.util.ArrayList;
import java.util.List;
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 java.util.ArrayList;
import java.util.List;
public class SimpleListItemAdapter extends BaseAdapter { public class SimpleListItemAdapter extends BaseAdapter {
private final Context context; private final Context context;
@ -37,6 +37,7 @@ public class SimpleListItemAdapter extends BaseAdapter {
/** /**
* Allows updating of the full data list underlying this adapter, replacing all items * Allows updating of the full data list underlying this adapter, replacing all items
*
* @param newItems The new list of simple list items to display * @param newItems The new list of simple list items to display
*/ */
public void update(List<? extends SimpleListItem> newItems) { public void update(List<? extends SimpleListItem> newItems) {
@ -66,7 +67,7 @@ public class SimpleListItemAdapter extends BaseAdapter {
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
SimpleListItemView filterItemView; SimpleListItemView filterItemView;
if (convertView == null || !(convertView instanceof SimpleListItemView)) { if (!(convertView instanceof SimpleListItemView)) {
filterItemView = SimpleListItemView_.build(context); filterItemView = SimpleListItemView_.build(context);
} else { } else {
filterItemView = (SimpleListItemView) convertView; filterItemView = (SimpleListItemView) convertView;
@ -78,18 +79,26 @@ public class SimpleListItemAdapter extends BaseAdapter {
/** /**
* Represents a very simple list item that only contains a single string to show in the list. Use wrapStringsList to * Represents a very simple list item that only contains a single string to show in the list. Use wrapStringsList to
* wrap an existing list of strings into a list of {@link SimpleListItem}s. * wrap an existing list of strings into a list of {@link SimpleListItem}s.
*
* @author Eric Kok * @author Eric Kok
*/ */
public static class SimpleStringItem implements SimpleListItem { public static class SimpleStringItem implements SimpleListItem {
private final String string;
public SimpleStringItem(String string) {
this.string = string;
}
/** /**
* Wraps a simple string of strings into a list of SimpleStringItem to add as data to a * Wraps a simple string of strings into a list of SimpleStringItem to add as data to a
* {@link SimpleListItemAdapter} * {@link SimpleListItemAdapter}
*
* @param strings A list of string * @param strings A list of string
* @return A list of SimpleStringItem objects representing the input strings * @return A list of SimpleStringItem objects representing the input strings
*/ */
public static List<SimpleStringItem> wrapStringsList(List<String> strings) { public static List<SimpleStringItem> wrapStringsList(List<String> strings) {
ArrayList<SimpleStringItem> errors = new ArrayList<SimpleStringItem>(); ArrayList<SimpleStringItem> errors = new ArrayList<>();
if (strings != null) { if (strings != null) {
for (String string : strings) { for (String string : strings) {
errors.add(new SimpleStringItem(string)); errors.add(new SimpleStringItem(string));
@ -98,12 +107,6 @@ public class SimpleListItemAdapter extends BaseAdapter {
return errors; return errors;
} }
private final String string;
public SimpleStringItem(String string) {
this.string = string;
}
@Override @Override
public String getName() { public String getName() {
return this.string; return this.string;

6
app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemSpinnerAdapter.java

@ -16,17 +16,18 @@
*/ */
package org.transdroid.core.gui.lists; package org.transdroid.core.gui.lists;
import java.util.List;
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.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.TextView; import android.widget.TextView;
import java.util.List;
/** /**
* A wrapper around {@link ArrayAdapter} that contains {@link SimpleListItem}s which simply show their name in the * A wrapper around {@link ArrayAdapter} that contains {@link SimpleListItem}s which simply show their name in the
* Spinner. The standard Android spinner resources are used for the layout. * Spinner. The standard Android spinner resources are used for the layout.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class SimpleListItemSpinnerAdapter<T extends SimpleListItem> extends ArrayAdapter<T> { public class SimpleListItemSpinnerAdapter<T extends SimpleListItem> extends ArrayAdapter<T> {
@ -34,6 +35,7 @@ public class SimpleListItemSpinnerAdapter<T extends SimpleListItem> extends Arra
/** /**
* Constructs the adapter, supplying the {@link SimpleListItem}s to show in the spinner. The given resource will be * Constructs the adapter, supplying the {@link SimpleListItem}s to show in the spinner. The given resource will be
* ignored as the standard Android Spinner layout is used instead. * ignored as the standard Android Spinner layout is used instead.
*
* @param context The UI context to inflate the layout in * @param context The UI context to inflate the layout in
* @param resource This is ignored; android.R.layout.simple_spinner_item is always used instead * @param resource This is ignored; android.R.layout.simple_spinner_item is always used instead
* @param objects The items to show in the spinner, which can simply display some name * @param objects The items to show in the spinner, which can simply display some name

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

@ -26,6 +26,7 @@ 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(R.layout.list_item_simple) @EViewGroup(R.layout.list_item_simple)

3
app/src/main/java/org/transdroid/core/gui/lists/SortByListItem.java

@ -17,11 +17,13 @@
package org.transdroid.core.gui.lists; package org.transdroid.core.gui.lists;
import android.content.Context; import android.content.Context;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.daemon.TorrentsSortBy; import org.transdroid.daemon.TorrentsSortBy;
/** /**
* Represents a way in which a torrents list can be sorted. * Represents a way in which a torrents list can be sorted.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class SortByListItem implements SimpleListItem { public class SortByListItem implements SimpleListItem {
@ -64,6 +66,7 @@ public class SortByListItem implements SimpleListItem {
/** /**
* Returns the contained represented sort order. * Returns the contained represented sort order.
*
* @return The sort by order as its enumeration value * @return The sort by order as its enumeration value
*/ */
public TorrentsSortBy getSortBy() { public TorrentsSortBy getSortBy() {

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

@ -32,6 +32,7 @@ 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(R.layout.fragment_details_header) @EViewGroup(R.layout.fragment_details_header)
@ -49,6 +50,7 @@ public class TorrentDetailsView extends RelativeLayout {
/** /**
* Update the text fields with new/updated torrent details * Update the text fields with new/updated torrent details
*
* @param torrent The torrent for which to show details * @param torrent The torrent for which to show details
*/ */
public void update(Torrent torrent) { public void update(Torrent torrent) {

14
app/src/main/java/org/transdroid/core/gui/lists/TorrentFilePriorityLayout.java

@ -16,9 +16,6 @@
*/ */
package org.transdroid.core.gui.lists; package org.transdroid.core.gui.lists;
import org.transdroid.R;
import org.transdroid.daemon.Priority;
import android.content.Context; import android.content.Context;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
@ -26,23 +23,26 @@ import android.graphics.RectF;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import org.transdroid.R;
import org.transdroid.daemon.Priority;
/** /**
* A relative layout that that is checkable (to be used in a contextual action bar) and shows a coloured bar in the far * A relative layout that that is checkable (to be used in a contextual action bar) and shows a coloured bar in the far
* left indicating the priority of the represented file. The darker the green, the higher the priority, while grey means * left indicating the priority of the represented file. The darker the green, the higher the priority, while grey means
* the file isn't downloaded at all. * the file isn't downloaded at all.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class TorrentFilePriorityLayout extends RelativeLayout { public class TorrentFilePriorityLayout extends RelativeLayout {
private final float scale = getContext().getResources().getDisplayMetrics().density; private final float scale = getContext().getResources().getDisplayMetrics().density;
private final int WIDTH = (int) (6 * scale + 0.5f); private final int WIDTH = (int) (6 * scale + 0.5f);
private Priority priority = null;
private final Paint offPaint = new Paint(); private final Paint offPaint = new Paint();
private final Paint lowPaint = new Paint(); private final Paint lowPaint = new Paint();
private final Paint highPaint = new Paint(); private final Paint highPaint = new Paint();
private final Paint normalPaint = new Paint(); private final Paint normalPaint = new Paint();
private final RectF fullRect = new RectF(); private final RectF fullRect = new RectF();
private Priority priority = null;
public TorrentFilePriorityLayout(Context context) { public TorrentFilePriorityLayout(Context context) {
super(context); super(context);
@ -72,9 +72,7 @@ public class TorrentFilePriorityLayout extends RelativeLayout {
protected void onDraw(Canvas canvas) { protected void onDraw(Canvas canvas) {
super.onDraw(canvas); super.onDraw(canvas);
int height = getHeight(); fullRect.set(0, 0, WIDTH, getHeight());
int width = WIDTH;
fullRect.set(0, 0, width, height);
if (priority == null) { if (priority == null) {
return; return;

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

@ -26,6 +26,7 @@ import org.transdroid.daemon.TorrentFile;
/** /**
* 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(R.layout.list_item_torrentfile) @EViewGroup(R.layout.list_item_torrentfile)

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

@ -16,8 +16,6 @@
*/ */
package org.transdroid.core.gui.lists; package org.transdroid.core.gui.lists;
import org.transdroid.R;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.Canvas; import android.graphics.Canvas;
@ -26,6 +24,8 @@ import android.graphics.RectF;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
import org.transdroid.R;
/** /**
* Draws a progress bar indicating the download progress as well as the torrent status. * Draws a progress bar indicating the download progress as well as the torrent status.
* *
@ -35,10 +35,6 @@ public class TorrentProgressBar extends View {
private final float scale = getContext().getResources().getDisplayMetrics().density; private final float scale = getContext().getResources().getDisplayMetrics().density;
private final int MINIMUM_HEIGHT = (int) (3 * scale + 0.5f); private final int MINIMUM_HEIGHT = (int) (3 * scale + 0.5f);
private int progress;
private boolean isActive;
private boolean isError;
private final Paint notdonePaint = new Paint(); private final Paint notdonePaint = new Paint();
private final Paint inactiveDonePaint = new Paint(); private final Paint inactiveDonePaint = new Paint();
private final Paint inactivePaint = new Paint(); private final Paint inactivePaint = new Paint();
@ -47,21 +43,9 @@ public class TorrentProgressBar extends View {
private final Paint errorPaint = new Paint(); private final Paint errorPaint = new Paint();
private final RectF fullRect = new RectF(); private final RectF fullRect = new RectF();
private final RectF progressRect = new RectF(); private final RectF progressRect = new RectF();
private int progress;
public void setProgress(int progress) { private boolean isActive;
this.progress = progress; private boolean isError;
this.invalidate();
}
public void setActive(boolean isActive) {
this.isActive = isActive;
this.invalidate();
}
public void setError(boolean isError) {
this.isError = isError;
this.invalidate();
}
public TorrentProgressBar(Context context) { public TorrentProgressBar(Context context) {
super(context); super(context);
@ -81,6 +65,21 @@ public class TorrentProgressBar extends View {
a.recycle(); a.recycle();
} }
public void setProgress(int progress) {
this.progress = progress;
this.invalidate();
}
public void setActive(boolean isActive) {
this.isActive = isActive;
this.invalidate();
}
public void setError(boolean isError) {
this.isError = isError;
this.invalidate();
}
private void initPaints() { private void initPaints() {
notdonePaint.setColor(getResources().getColor(R.color.torrent_background)); notdonePaint.setColor(getResources().getColor(R.color.torrent_background));
inactiveDonePaint.setColor(getResources().getColor(R.color.torrent_paused)); inactiveDonePaint.setColor(getResources().getColor(R.color.torrent_paused));

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

@ -16,9 +16,6 @@
*/ */
package org.transdroid.core.gui.lists; package org.transdroid.core.gui.lists;
import org.transdroid.R;
import org.transdroid.daemon.TorrentStatus;
import android.content.Context; import android.content.Context;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
@ -26,24 +23,27 @@ import android.graphics.RectF;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import org.transdroid.R;
import org.transdroid.daemon.TorrentStatus;
/** /**
* A relative layout that is checkable (to be used in a contextual action bar) and shows a coloured bar in the far left * A relative layout that is checkable (to be used in a contextual action bar) and shows a coloured bar in the far left
* indicating the status of the represented torrent. Active downloads are blue, seeding torrents are green, errors are * indicating the status of the represented torrent. Active downloads are blue, seeding torrents are green, errors are
* red, etc. * red, etc.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class TorrentStatusLayout extends RelativeLayout { public class TorrentStatusLayout extends RelativeLayout {
private final float scale = getContext().getResources().getDisplayMetrics().density; private final float scale = getContext().getResources().getDisplayMetrics().density;
private final int WIDTH = (int) (6 * scale + 0.5f); private final int WIDTH = (int) (6 * scale + 0.5f);
private TorrentStatus status = null;
private final Paint pausedPaint = new Paint(); private final Paint pausedPaint = new Paint();
private final Paint otherPaint = new Paint(); private final Paint otherPaint = new Paint();
private final Paint downloadingPaint = new Paint(); private final Paint downloadingPaint = new Paint();
private final Paint seedingPaint = new Paint(); private final Paint seedingPaint = new Paint();
private final Paint errorPaint = new Paint(); private final Paint errorPaint = new Paint();
private final RectF fullRect = new RectF(); private final RectF fullRect = new RectF();
private TorrentStatus status = null;
public TorrentStatusLayout(Context context) { public TorrentStatusLayout(Context context) {
super(context); super(context);
@ -68,6 +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 The updated torrent status to show * @param status The updated torrent status to show
*/ */
public void setStatus(TorrentStatus status) { public void setStatus(TorrentStatus status) {
@ -79,9 +80,7 @@ public class TorrentStatusLayout extends RelativeLayout {
protected void onDraw(Canvas canvas) { protected void onDraw(Canvas canvas) {
super.onDraw(canvas); super.onDraw(canvas);
int height = getHeight(); fullRect.set(0, 0, WIDTH, getHeight());
int width = WIDTH;
fullRect.set(0, 0, width, height);
if (status == null) { if (status == null) {
return; return;

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

@ -29,6 +29,7 @@ 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(R.layout.list_item_torrent) @EViewGroup(R.layout.list_item_torrent)

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

@ -29,18 +29,19 @@ import java.util.ArrayList;
/** /**
* Adapter that contains a list of torrent objects to show. * Adapter that contains a list of torrent objects to show.
*
* @author Eric Kok * @author Eric Kok
*/ */
@EBean @EBean
public class TorrentsAdapter extends BaseAdapter { public class TorrentsAdapter extends BaseAdapter {
private ArrayList<Torrent> torrents = null;
@RootContext @RootContext
protected Context context; protected Context context;
private ArrayList<Torrent> torrents = null;
/** /**
* Allows updating the full internal list of torrents at once, replacing the old list * Allows updating the full internal list of torrents at once, replacing the old list
*
* @param newTorrents The new list of torrent objects * @param newTorrents The new list of torrent objects
*/ */
public void update(ArrayList<Torrent> newTorrents) { public void update(ArrayList<Torrent> newTorrents) {

4
app/src/main/java/org/transdroid/core/gui/lists/ViewHolderAdapter.java

@ -28,6 +28,7 @@ import android.widget.ListView;
* the view object. This is required since otherwise the adapter's consumer (i.e. a {@link ListView}) does not update * the view object. This is required since otherwise the adapter's consumer (i.e. a {@link ListView}) does not update
* the list row accordingly. Use {@link #setViewEnabled(boolean)} to enable or disable this contained view for user * the list row accordingly. Use {@link #setViewEnabled(boolean)} to enable or disable this contained view for user
* interaction. * interaction.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class ViewHolderAdapter extends BaseAdapter { public class ViewHolderAdapter extends BaseAdapter {
@ -38,6 +39,7 @@ public class ViewHolderAdapter extends BaseAdapter {
* Instantiates this wrapper adapter with the one and only view to show. It can not be updated and view visibility * Instantiates this wrapper adapter with the one and only view to show. It can not be updated and view visibility
* should be set directly on this adapter using {@link #setViewVisibility(int)}. Use * should be set directly on this adapter using {@link #setViewVisibility(int)}. Use
* {@link #setViewEnabled(boolean)} to enable or disable this contained view for user interaction. * {@link #setViewEnabled(boolean)} to enable or disable this contained view for user interaction.
*
* @param view The view that will be wrapper in an adapter to show in a list view * @param view The view that will be wrapper in an adapter to show in a list view
*/ */
public ViewHolderAdapter(View view) { public ViewHolderAdapter(View view) {
@ -47,6 +49,7 @@ public class ViewHolderAdapter extends BaseAdapter {
/** /**
* Sets the visibility on the contained view and notifies consumers of this adapter (i.e. a {@link ListView}) * Sets the visibility on the contained view and notifies consumers of this adapter (i.e. a {@link ListView})
* accordingly. Use {@link View#GONE} to hide this adapter's view altogether. * accordingly. Use {@link View#GONE} to hide this adapter's view altogether.
*
* @param visibility The visibility to set on the contained view * @param visibility The visibility to set on the contained view
*/ */
public void setViewVisibility(int visibility) { public void setViewVisibility(int visibility) {
@ -57,6 +60,7 @@ public class ViewHolderAdapter extends BaseAdapter {
/** /**
* Sets whether the contained view should be enabled and notifies consumers of this adapter (i.e. a {@link ListView} * Sets whether the contained view should be enabled and notifies consumers of this adapter (i.e. a {@link ListView}
* ) accordingly. A contained enabled view allows user interaction (clicks, focus), while a disabled view does not. * ) accordingly. A contained enabled view allows user interaction (clicks, focus), while a disabled view does not.
*
* @param enabled Whether the contained view should be enabled * @param enabled Whether the contained view should be enabled
*/ */
public void setViewEnabled(boolean enabled) { public void setViewEnabled(boolean enabled) {

10
app/src/main/java/org/transdroid/core/gui/log/DatabaseHelper.java

@ -16,19 +16,21 @@
*/ */
package org.transdroid.core.gui.log; package org.transdroid.core.gui.log;
import java.sql.SQLException;
import android.content.Context; import android.content.Context;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import androidx.annotation.Keep;
import android.util.Log; import android.util.Log;
import androidx.annotation.Keep;
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper; import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.support.ConnectionSource; import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils; import com.j256.ormlite.table.TableUtils;
import java.sql.SQLException;
/** /**
* Helper to access the database to access persisting objects. * Helper to access the database to access persisting objects.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class DatabaseHelper extends OrmLiteSqliteOpenHelper { public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
@ -57,7 +59,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
switch (oldVersion) { switch (oldVersion) {
case 1: case 1:
TableUtils.createTable(connectionSource, ErrorLogEntry.class); TableUtils.createTable(connectionSource, ErrorLogEntry.class);
/*case 1: /*case 2:
etc...*/ etc...*/
} }

39
app/src/main/java/org/transdroid/core/gui/log/ErrorLogEntry.java

@ -16,16 +16,17 @@
*/ */
package org.transdroid.core.gui.log; package org.transdroid.core.gui.log;
import java.util.Date;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import com.j256.ormlite.field.DatabaseField; import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable; import com.j256.ormlite.table.DatabaseTable;
import java.util.Date;
/** /**
* Represents an error log entry to be registered in the database. * Represents an error log entry to be registered in the database.
*
* @author Eric Kok * @author Eric Kok
*/ */
@DatabaseTable(tableName = "ErrorLogEntry") @DatabaseTable(tableName = "ErrorLogEntry")
@ -33,7 +34,15 @@ public class ErrorLogEntry implements Parcelable {
public static final String ID = "logId"; public static final String ID = "logId";
public static final String DATEANDTIME = "dateAndTime"; public static final String DATEANDTIME = "dateAndTime";
public static final Parcelable.Creator<ErrorLogEntry> CREATOR = new Parcelable.Creator<ErrorLogEntry>() {
public ErrorLogEntry createFromParcel(Parcel in) {
return new ErrorLogEntry(in);
}
public ErrorLogEntry[] newArray(int size) {
return new ErrorLogEntry[size];
}
};
@DatabaseField(id = true, columnName = ID) @DatabaseField(id = true, columnName = ID)
private Integer logId; private Integer logId;
@DatabaseField(columnName = DATEANDTIME) @DatabaseField(columnName = DATEANDTIME)
@ -55,6 +64,14 @@ public class ErrorLogEntry implements Parcelable {
this.message = message; this.message = message;
} }
private ErrorLogEntry(Parcel in) {
logId = in.readInt();
dateAndTime = new Date(in.readLong());
priority = in.readInt();
tag = in.readString();
message = in.readString();
}
public Integer getLogId() { public Integer getLogId() {
return logId; return logId;
} }
@ -87,22 +104,4 @@ public class ErrorLogEntry implements Parcelable {
out.writeString(message); out.writeString(message);
} }
public static final Parcelable.Creator<ErrorLogEntry> CREATOR = new Parcelable.Creator<ErrorLogEntry>() {
public ErrorLogEntry createFromParcel(Parcel in) {
return new ErrorLogEntry(in);
}
public ErrorLogEntry[] newArray(int size) {
return new ErrorLogEntry[size];
}
};
private ErrorLogEntry(Parcel in) {
logId = in.readInt();
dateAndTime = new Date(in.readLong());
priority = in.readInt();
tag = in.readString();
message = in.readString();
}
} }

16
app/src/main/java/org/transdroid/core/gui/log/ErrorLogSender.java

@ -16,8 +16,11 @@
*/ */
package org.transdroid.core.gui.log; package org.transdroid.core.gui.log;
import java.sql.SQLException; import android.app.Activity;
import java.util.List; import android.content.ActivityNotFoundException;
import android.content.Intent;
import com.j256.ormlite.dao.Dao;
import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EBean; import org.androidannotations.annotations.EBean;
@ -26,11 +29,8 @@ import org.transdroid.R;
import org.transdroid.core.app.settings.ServerSetting; import org.transdroid.core.app.settings.ServerSetting;
import org.transdroid.core.gui.navigation.NavigationHelper; import org.transdroid.core.gui.navigation.NavigationHelper;
import android.app.Activity; import java.sql.SQLException;
import android.content.ActivityNotFoundException; import java.util.List;
import android.content.Intent;
import com.j256.ormlite.dao.Dao;
@EBean @EBean
public class ErrorLogSender { public class ErrorLogSender {
@ -76,7 +76,7 @@ public class ErrorLogSender {
Intent target = new Intent(Intent.ACTION_SEND); Intent target = new Intent(Intent.ACTION_SEND);
target.setType("message/rfc822"); target.setType("message/rfc822");
target.putExtra(Intent.EXTRA_EMAIL, new String[] { "transdroid@2312.nl" }); target.putExtra(Intent.EXTRA_EMAIL, new String[]{"transdroid@2312.nl"});
target.putExtra(Intent.EXTRA_SUBJECT, "Transdroid error report"); target.putExtra(Intent.EXTRA_SUBJECT, "Transdroid error report");
target.putExtra(Intent.EXTRA_TEXT, body.toString()); target.putExtra(Intent.EXTRA_TEXT, body.toString());
try { try {

1
app/src/main/java/org/transdroid/core/gui/log/Log.java

@ -28,6 +28,7 @@ import java.util.Date;
/** /**
* Application-wide logging class that registers entries in the database (for a certain time). * Application-wide logging class that registers entries in the database (for a certain time).
*
* @author Eric Kok * @author Eric Kok
*/ */
@EBean(scope = Scope.Singleton) @EBean(scope = Scope.Singleton)

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

@ -16,12 +16,6 @@
*/ */
package org.transdroid.core.gui.navigation; package org.transdroid.core.gui.navigation;
import java.io.Serializable;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.Extra;
import org.transdroid.core.gui.*;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
@ -32,10 +26,17 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.Window; import android.view.Window;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.Extra;
import org.transdroid.core.gui.TorrentsActivity_;
import java.io.Serializable;
/** /**
* Helper class that show a dialog either as pop-up or as full screen activity. Should be used by calling * Helper class that show a dialog either as pop-up or as full screen activity. Should be used by calling
* {@link #showDialog(Context, DialogSpecification)} with in instance of the dialog specification that should be shown, * {@link #showDialog(Context, DialogSpecification)} with in instance of the dialog specification that should be shown,
* from the calling activity's {@link Activity#onCreateDialog(int)}. * from the calling activity's {@link Activity#onCreateDialog(int)}.
*
* @author Eric Kok * @author Eric Kok
*/ */
@EActivity @EActivity
@ -44,6 +45,27 @@ public class DialogHelper extends Activity {
@Extra @Extra
protected DialogSpecification dialog; protected DialogSpecification dialog;
/**
* Call this from {@link Activity#onCreateDialog(int)}, supplying an instance of the {@link DialogSpecification}
* that should be shown to the user.
*
* @param context The activity that calls this method and which will own the constructed dialog
* @param dialog An instance of the specification for the dialog that needs to be shown
* @return Either an instance of a {@link Dialog} that the activity should further control or null if the dialog
* will instead be opened as a full screen activity
*/
public static Dialog showDialog(Context context, DialogSpecification dialog) {
// If the device is large (i.e. a tablet) then return a dialog to show
if (!NavigationHelper_.getInstance_(context).isSmallScreen())
return new PopupDialog(context, dialog);
// This is a small device; create a full screen dialog (which is just an activity)
DialogHelper_.intent(context).dialog(dialog).start();
return null;
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -69,23 +91,16 @@ public class DialogHelper extends Activity {
} }
/** /**
* Call this from {@link Activity#onCreateDialog(int)}, supplying an instance of the {@link DialogSpecification} * Specification for some dialog that can be show to the user, consisting of a custom layout and possibly an action
* that should be shown to the user. * bar menu. Warning: the action bar, and thus the menu options, is only shown when the dialog is presented as full
* @param context The activity that calls this method and which will own the constructed dialog * screen activity. Use only for unimportant actions.
* @param dialog An instance of the specification for the dialog that needs to be shown
* @return Either an instance of a {@link Dialog} that the activity should further control or null if the dialog
* will instead be opened as a full screen activity
*/ */
public static Dialog showDialog(Context context, DialogSpecification dialog) { public interface DialogSpecification extends Serializable {
int getDialogLayoutId();
// If the device is large (i.e. a tablet) then return a dialog to show
if (!NavigationHelper_.getInstance_(context).isSmallScreen())
return new PopupDialog(context, dialog);
// This is a small device; create a full screen dialog (which is just an activity) int getDialogMenuId();
DialogHelper_.intent(context).dialog(dialog).start();
return null;
boolean onMenuItemSelected(Activity ownerActivity, int selectedItemId);
} }
/** /**
@ -99,15 +114,4 @@ public class DialogHelper extends Activity {
} }
} }
/**
* Specification for some dialog that can be show to the user, consisting of a custom layout and possibly an action
* bar menu. Warning: the action bar, and thus the menu options, is only shown when the dialog is presented as full
* screen activity. Use only for unimportant actions.
*/
public interface DialogSpecification extends Serializable {
int getDialogLayoutId();
int getDialogMenuId();
boolean onMenuItemSelected(Activity ownerActivity, int selectedItemId);
}
} }

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

@ -24,7 +24,6 @@ import org.androidannotations.annotations.RootContext;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.core.app.settings.ServerSetting; import org.transdroid.core.app.settings.ServerSetting;
import org.transdroid.core.gui.lists.MergeAdapter; import org.transdroid.core.gui.lists.MergeAdapter;
import org.transdroid.core.gui.lists.SimpleListItem;
import org.transdroid.core.gui.lists.ViewHolderAdapter; import org.transdroid.core.gui.lists.ViewHolderAdapter;
import org.transdroid.core.gui.navigation.StatusType.StatusTypeFilter; import org.transdroid.core.gui.navigation.StatusType.StatusTypeFilter;
@ -33,6 +32,7 @@ import java.util.List;
/** /**
* List adapter that holds filter items, that is, servers, view types and labels. A header item is inserted where appropriate. * List adapter that holds filter items, that is, servers, view types and labels. A header item is inserted where appropriate.
*
* @author Eric Kok * @author Eric Kok
*/ */
@EBean @EBean
@ -40,15 +40,16 @@ public class FilterListAdapter extends MergeAdapter {
@RootContext @RootContext
protected Context context; protected Context context;
private FilterListItemAdapter serverItems = null;
private FilterListItemAdapter statusTypeItems = null;
private FilterListItemAdapter labelItems = null;
protected ViewHolderAdapter statusTypeSeparator; protected ViewHolderAdapter statusTypeSeparator;
protected ViewHolderAdapter labelSeperator; protected ViewHolderAdapter labelSeperator;
protected ViewHolderAdapter serverSeparator; protected ViewHolderAdapter serverSeparator;
private FilterListItemAdapter serverItems = null;
private FilterListItemAdapter statusTypeItems = null;
private FilterListItemAdapter labelItems = null;
/** /**
* Update the list of available servers * Update the list of available servers
*
* @param servers The new list of available servers * @param servers The new list of available servers
*/ */
public void updateServers(List<ServerSetting> servers) { public void updateServers(List<ServerSetting> servers) {
@ -63,13 +64,14 @@ public class FilterListAdapter extends MergeAdapter {
this.serverItems.update(servers); this.serverItems.update(servers);
} else { } else {
serverSeparator.setViewVisibility(View.GONE); serverSeparator.setViewVisibility(View.GONE);
this.serverItems.update(new ArrayList<SimpleListItem>()); this.serverItems.update(new ArrayList<>());
} }
notifyDataSetChanged(); notifyDataSetChanged();
} }
/** /**
* Update the list of available status types * Update the list of available status types
*
* @param statusTypes The new list of available status types * @param statusTypes The new list of available status types
*/ */
public void updateStatusTypes(List<StatusTypeFilter> statusTypes) { public void updateStatusTypes(List<StatusTypeFilter> statusTypes) {
@ -84,13 +86,14 @@ public class FilterListAdapter extends MergeAdapter {
this.statusTypeItems.update(statusTypes); this.statusTypeItems.update(statusTypes);
} else { } else {
statusTypeSeparator.setViewVisibility(View.GONE); statusTypeSeparator.setViewVisibility(View.GONE);
this.statusTypeItems.update(new ArrayList<SimpleListItem>()); this.statusTypeItems.update(new ArrayList<>());
} }
notifyDataSetChanged(); notifyDataSetChanged();
} }
/** /**
* Update the list of available labels * Update the list of available labels
*
* @param labels The new list of available labels * @param labels The new list of available labels
*/ */
public void updateLabels(List<Label> labels) { public void updateLabels(List<Label> labels) {
@ -105,7 +108,7 @@ public class FilterListAdapter extends MergeAdapter {
this.labelItems.update(labels); this.labelItems.update(labels);
} else { } else {
labelSeperator.setViewVisibility(View.GONE); labelSeperator.setViewVisibility(View.GONE);
this.labelItems.update(new ArrayList<SimpleListItem>()); this.labelItems.update(new ArrayList<>());
} }
notifyDataSetChanged(); notifyDataSetChanged();
} }

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

@ -38,6 +38,7 @@ public class FilterListItemAdapter extends BaseAdapter {
/** /**
* Allows updating of the full data list underlying this adapter, replacing all items * Allows updating of the full data list underlying this adapter, replacing all items
*
* @param newItems The new list of filter items to display * @param newItems The new list of filter items to display
*/ */
public void update(List<? extends SimpleListItem> newItems) { public void update(List<? extends SimpleListItem> newItems) {
@ -63,7 +64,7 @@ public class FilterListItemAdapter extends BaseAdapter {
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
FilterListItemView filterItemView; FilterListItemView filterItemView;
if (convertView == null || !(convertView instanceof SimpleListItemView)) { if (!(convertView instanceof SimpleListItemView)) {
filterItemView = FilterListItemView_.build(context); filterItemView = FilterListItemView_.build(context);
} else { } else {
filterItemView = (FilterListItemView) convertView; filterItemView = (FilterListItemView) convertView;

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

@ -27,6 +27,7 @@ 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(R.layout.list_item_filter) @EViewGroup(R.layout.list_item_filter)

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

@ -27,6 +27,7 @@ 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(R.layout.list_item_separator) @EViewGroup(R.layout.list_item_separator)
@ -43,6 +44,7 @@ public class FilterSeparatorView extends FrameLayout {
/** /**
* Sets the text that will be shown in this separator (sub header) * Sets the text that will be shown in this separator (sub header)
*
* @param text The new text to show * @param text The new text to show
* @return Itself, for convenience of method chaining * @return Itself, for convenience of method chaining
*/ */

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

@ -29,12 +29,21 @@ 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.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class Label implements SimpleListItem, NavigationFilter, Comparable<Label> { public class Label implements SimpleListItem, NavigationFilter, Comparable<Label> {
private static String unnamedLabelText = null; public static final Parcelable.Creator<Label> CREATOR = new Parcelable.Creator<Label>() {
public Label createFromParcel(Parcel in) {
return new Label(in);
}
public Label[] newArray(int size) {
return new Label[size];
}
};
private static String unnamedLabelText = null;
private final boolean isEmptyLabel; private final boolean isEmptyLabel;
private final String name; private final String name;
private final int count; private final int count;
@ -49,6 +58,39 @@ public class Label implements SimpleListItem, NavigationFilter, Comparable<Label
this(daemonLabel.getName(), daemonLabel.getCount(), false); this(daemonLabel.getName(), daemonLabel.getCount(), false);
} }
private Label(Parcel in) {
this.name = in.readString();
this.count = in.readInt();
this.isEmptyLabel = in.readInt() == 1;
}
/**
* Converts a list of labels as retrieved from a server daemon into a list of labels that can be used in the UI as navigation filters.
*
* @param daemonLabels The raw list of labels as received from the server daemon adapter
* @param unnamedLabel The text to show for the empty label (i.e. the unnamed label)
* @return A label items that can be used in a filter list such as the action bar spinner
*/
public static ArrayList<Label> convertToNavigationLabels(List<org.transdroid.daemon.Label> daemonLabels, String unnamedLabel) {
if (daemonLabels == null) {
return null;
}
ArrayList<Label> localLabels = new ArrayList<>();
unnamedLabelText = unnamedLabel;
for (org.transdroid.daemon.Label label : daemonLabels) {
if (label != null && !TextUtils.isEmpty(label.getName())) {
localLabels.add(new Label(label));
}
}
Collections.sort(localLabels);
// force unlabelled to be at the top
localLabels.add(0, new Label(unnamedLabel, -1, true));
return localLabels;
}
@Override @Override
public String getName() { public String getName() {
if (TextUtils.isEmpty(this.name)) { if (TextUtils.isEmpty(this.name)) {
@ -73,6 +115,7 @@ public class Label implements SimpleListItem, NavigationFilter, Comparable<Label
/** /**
* Returns true if the torrent label's name matches this (selected) label's name, false otherwise * Returns true if the torrent label's name matches this (selected) label's name, false otherwise
*
* @param torrent The torrent to match against this label * @param torrent The torrent to match against this label
* @param dormantAsInactive This property is ignored for label comparisons * @param dormantAsInactive This property is ignored for label comparisons
*/ */
@ -89,48 +132,6 @@ public class Label implements SimpleListItem, NavigationFilter, Comparable<Label
return this.name.compareTo(another.getName()); return this.name.compareTo(another.getName());
} }
/**
* Converts a list of labels as retrieved from a server daemon into a list of labels that can be used in the UI as navigation filters.
* @param daemonLabels The raw list of labels as received from the server daemon adapter
* @param unnamedLabel The text to show for the empty label (i.e. the unnamed label)
* @return A label items that can be used in a filter list such as the action bar spinner
*/
public static ArrayList<Label> convertToNavigationLabels(List<org.transdroid.daemon.Label> daemonLabels, String unnamedLabel) {
if (daemonLabels == null) {
return null;
}
ArrayList<Label> localLabels = new ArrayList<>();
unnamedLabelText = unnamedLabel;
for (org.transdroid.daemon.Label label : daemonLabels) {
if (label != null && !TextUtils.isEmpty(label.getName())) {
localLabels.add(new Label(label));
}
}
Collections.sort(localLabels);
// force unlabelled to be at the top
localLabels.add(0, new Label(unnamedLabel, -1, true));
return localLabels;
}
private Label(Parcel in) {
this.name = in.readString();
this.count = in.readInt();
this.isEmptyLabel = in.readInt() == 1;
}
public static final Parcelable.Creator<Label> CREATOR = new Parcelable.Creator<Label>() {
public Label createFromParcel(Parcel in) {
return new Label(in);
}
public Label[] newArray(int size) {
return new Label[size];
}
};
@Override @Override
public int describeContents() { public int describeContents() {
return 0; return 0;

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

@ -22,6 +22,7 @@ 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
*/ */
public interface NavigationFilter extends Parcelable { public interface NavigationFilter extends Parcelable {
@ -29,6 +30,7 @@ public interface NavigationFilter extends Parcelable {
/** /**
* Implementations should check if the supplied torrent matches the filter; for example a label filter should return true if the torrent's label * Implementations should check if the supplied torrent matches the filter; for example a label filter should return true if the torrent's label
* equals this items label name. * 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 seeding * @param dormantAsInactive If true, dormant (0KB/s, so no data transfer) torrents are never actively downloading or seeding
* @return True if the torrent matches the filter and should be shown in the current screen, false otherwise * @return True if the torrent matches the filter and should be shown in the current screen, false otherwise
@ -37,12 +39,14 @@ public interface NavigationFilter extends Parcelable {
/** /**
* Implementations should return a name that can be shown to indicate the active filter * Implementations should return a name that can be shown to indicate the active filter
*
* @return The name of the filter item as string * @return The name of the filter item as string
*/ */
String getName(); String getName();
/** /**
* Implementations should return a code that (within reasonable expectations) uniquely identifies it in the list of navigation filters * Implementations should return a code that (within reasonable expectations) uniquely identifies it in the list of navigation filters
*
* @return The code to uniquely identify this specific navigation filter, such as the name with a class name prefix * @return The code to uniquely identify this specific navigation filter, such as the name with a class name prefix
*/ */
String getCode(); String getCode();

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

@ -26,14 +26,13 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.text.Spannable; import android.text.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.style.TypefaceSpan; import android.text.style.TypefaceSpan;
import com.afollestad.materialdialogs.DialogAction; import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.MaterialDialog;
import com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache; import com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache;
import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator; import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator;
@ -54,6 +53,7 @@ import java.util.List;
/** /**
* Helper for activities to make navigation-related decisions, such as when a device can display a larger, tablet style layout or how to display * Helper for activities to make navigation-related decisions, such as when a device can display a larger, tablet style layout or how to display
* errors. * errors.
*
* @author Eric Kok * @author Eric Kok
*/ */
@SuppressLint("ResourceAsColor") @SuppressLint("ResourceAsColor")
@ -68,78 +68,9 @@ public class NavigationHelper {
@RootContext @RootContext
protected Context context; protected Context context;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public boolean checkTorrentReadPermission(final Activity activity) {
return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT ||
checkPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE, REQUEST_TORRENT_READ_PERMISSION);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public boolean checkSettingsReadPermission(final Activity activity) {
return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT ||
checkPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE, REQUEST_SETTINGS_READ_PERMISSION);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public boolean checkSettingsWritePermission(final Activity activity) {
return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT ||
checkPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE, REQUEST_SETTINGS_WRITE_PERMISSION);
}
private boolean checkPermission(final Activity activity, final String permission, final int requestCode) {
if (hasPermission(permission))
// Permission already granted
return true;
if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
// Never asked again: show a dialog with an explanation
activity.runOnUiThread(new Runnable() {
public void run() {
new MaterialDialog.Builder(context).content(R.string.permission_readtorrent).positiveText(android.R.string.ok)
.onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode);
}
}).show();
}
});
return false;
}
// Permission not granted (and we asked for it already before)
ActivityCompat.requestPermissions(activity, new String[]{permission}, REQUEST_TORRENT_READ_PERMISSION);
return false;
}
private boolean hasPermission(String requiredPermission) {
return ContextCompat.checkSelfPermission(context, requiredPermission) == PackageManager.PERMISSION_GRANTED;
}
public Boolean handleTorrentReadPermissionResult(int requestCode, int[] grantResults) {
if (requestCode == REQUEST_TORRENT_READ_PERMISSION) {
// Return permission granting result
return grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
}
return null;
}
public Boolean handleSettingsReadPermissionResult(int requestCode, int[] grantResults) {
if (requestCode == REQUEST_SETTINGS_READ_PERMISSION) {
// Return permission granting result
return grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
}
return null;
}
public Boolean handleSettingsWritePermissionResult(int requestCode, int[] grantResults) {
if (requestCode == REQUEST_SETTINGS_WRITE_PERMISSION) {
// Return permission granting result
return grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
}
return null;
}
/** /**
* Converts a string into a {@link Spannable} that displays the string in the Roboto Condensed font * 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 input string will be displayed * @return A {@link Spannable} that can be applied to supporting views (such as the action bar title) so that the input string will be displayed
* using the Roboto Condensed font (if the OS has this) * using the Roboto Condensed font (if the OS has this)
@ -155,6 +86,7 @@ public class NavigationHelper {
/** /**
* Analyses a torrent http or magnet URI and tries to come up with a reasonable human-readable name. * Analyses a torrent http or magnet URI and tries to come up with a reasonable human-readable name.
*
* @param rawTorrentUri The raw http:// or magnet: link to the torrent * @param rawTorrentUri The raw http:// or magnet: link to the torrent
* @return A best-guess, reasonably long name for the linked torrent * @return A best-guess, reasonably long name for the linked torrent
*/ */
@ -202,8 +134,74 @@ public class NavigationHelper {
return null; return null;
} }
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public boolean checkTorrentReadPermission(final Activity activity) {
return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT ||
checkPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE, REQUEST_TORRENT_READ_PERMISSION);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public boolean checkSettingsReadPermission(final Activity activity) {
return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT ||
checkPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE, REQUEST_SETTINGS_READ_PERMISSION);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public boolean checkSettingsWritePermission(final Activity activity) {
return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT ||
checkPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE, REQUEST_SETTINGS_WRITE_PERMISSION);
}
private boolean checkPermission(final Activity activity, final String permission, final int requestCode) {
if (hasPermission(permission))
// Permission already granted
return true;
if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
// Never asked again: show a dialog with an explanation
activity.runOnUiThread(() ->
new MaterialDialog.Builder(context)
.content(R.string.permission_readtorrent)
.positiveText(android.R.string.ok)
.onPositive((dialog, which) ->
ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode)).show());
return false;
}
// Permission not granted (and we asked for it already before)
ActivityCompat.requestPermissions(activity, new String[]{permission}, REQUEST_TORRENT_READ_PERMISSION);
return false;
}
private boolean hasPermission(String requiredPermission) {
return ContextCompat.checkSelfPermission(context, requiredPermission) == PackageManager.PERMISSION_GRANTED;
}
public Boolean handleTorrentReadPermissionResult(int requestCode, int[] grantResults) {
if (requestCode == REQUEST_TORRENT_READ_PERMISSION) {
// Return permission granting result
return grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
}
return null;
}
public Boolean handleSettingsReadPermissionResult(int requestCode, int[] grantResults) {
if (requestCode == REQUEST_SETTINGS_READ_PERMISSION) {
// Return permission granting result
return grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
}
return null;
}
public Boolean handleSettingsWritePermissionResult(int requestCode, int[] grantResults) {
if (requestCode == REQUEST_SETTINGS_WRITE_PERMISSION) {
// Return permission granting result
return grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
}
return null;
}
/** /**
* Returns (and initialises, if needed) an image cache that uses memory and (1MB) local storage. * Returns (and initialises, if needed) an image cache that uses memory and (1MB) local storage.
*
* @return An image cache that loads web images synchronously and transparently * @return An image cache that loads web images synchronously and transparently
*/ */
public ImageLoader getImageCache() { public ImageLoader getImageCache() {
@ -250,15 +248,17 @@ public class NavigationHelper {
/** /**
* Returns the application name (like Transdroid) and version name (like 1.5.0), appended by the version code (like 180). * Returns the application name (like Transdroid) and version name (like 1.5.0), appended by the version code (like 180).
*
* @return The app name and version, such as 'Transdroid 1.5.0 (180)' * @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 + " (" + Integer.toString(BuildConfig.VERSION_CODE) + ")"; return context.getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")";
} }
/** /**
* Returns whether the device is considered small (i.e. a phone) rather than large (i.e. a tablet). Can, for example, be used to determine if a * Returns whether the device is considered small (i.e. a phone) rather than large (i.e. a tablet). Can, for example, be used to determine if a
* dialog should be shown full screen. Currently is true if the device's smallest dimension is 500 dip. * dialog should be shown full screen. Currently is true if the device's smallest dimension is 500 dip.
*
* @return True if the app runs on a small device, false otherwise * @return True if the app runs on a small device, false otherwise
*/ */
public boolean isSmallScreen() { public boolean isSmallScreen() {
@ -268,6 +268,7 @@ public class NavigationHelper {
/** /**
* Whether any search-related UI components should be shown in the interface. At the moment returns false only if we run as Transdroid Lite * Whether any search-related UI components should be shown in the interface. At the moment returns false only if we run as Transdroid Lite
* version. * version.
*
* @return True if search is enabled, false otherwise * @return True if search is enabled, false otherwise
*/ */
public boolean enableSearchUi() { public boolean enableSearchUi() {
@ -276,6 +277,7 @@ public class NavigationHelper {
/** /**
* 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. * Whether any RSS-related UI components should be shown in the interface. At the moment returns false only if we run as Transdroid Lite version.
*
* @return True if search is enabled, false otherwise * @return True if search is enabled, false otherwise
*/ */
public boolean enableRssUi() { public boolean enableRssUi() {
@ -285,6 +287,7 @@ public class NavigationHelper {
/** /**
* Returns whether any seedbox-related components should be shown in the interface; specifically the option to add server settings via easy * Returns whether any seedbox-related components should be shown in the interface; specifically the option to add 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() {
@ -293,6 +296,7 @@ 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 Play Store for updates, for * @return True if it should be checked against transdroid.org if there are app updates (as opposed to using the Play Store for updates, for
* example), false otherwise * example), false otherwise
*/ */

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

@ -18,6 +18,7 @@ package org.transdroid.core.gui.navigation;
/** /**
* Interface to be implemented by any activity that allows its content to be refreshed; fragments can ask for user-initiated refreshes. * Interface to be implemented by any activity that allows its content to be refreshed; fragments can ask for user-initiated refreshes.
*
* @author Eric Kok * @author Eric Kok
*/ */
public interface RefreshableActivity { public interface RefreshableActivity {

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

@ -16,9 +16,6 @@
*/ */
package org.transdroid.core.gui.navigation; package org.transdroid.core.gui.navigation;
import org.transdroid.core.gui.navigation.SelectionModificationSpinner.OnModificationActionSelectedListener;
import org.transdroid.daemon.Finishable;
import android.content.Context; import android.content.Context;
import android.util.SparseBooleanArray; import android.util.SparseBooleanArray;
import android.view.ActionMode; import android.view.ActionMode;
@ -28,10 +25,14 @@ import android.view.ViewGroup;
import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.ListView; import android.widget.ListView;
import org.transdroid.core.gui.navigation.SelectionModificationSpinner.OnModificationActionSelectedListener;
import org.transdroid.daemon.Finishable;
/** /**
* A helper to implement {@link ListView} selection modification behaviour with the {@link SelectionModificationSpinner} * A helper to implement {@link ListView} selection modification behaviour with the {@link SelectionModificationSpinner}
* by implementing the specific actions and providing a title based on the number of currently selected items. It is * by implementing the specific actions and providing a title based on the number of currently selected items. It is
* important that the provided list was instantiated already. * important that the provided list was instantiated already.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class SelectionManagerMode implements MultiChoiceModeListener, OnModificationActionSelectedListener { public class SelectionManagerMode implements MultiChoiceModeListener, OnModificationActionSelectedListener {
@ -44,6 +45,7 @@ public class SelectionManagerMode implements MultiChoiceModeListener, OnModifica
/** /**
* Instantiates the helper by binding it to a specific {@link ListView} and providing the text resource to display * Instantiates the helper by binding it to a specific {@link ListView} and providing the text resource to display
* as title in the spinner. * as title in the spinner.
*
* @param themedContext The context which is associated with the correct theme to apply when inflating views, i.e. the toolbar context * @param themedContext The context which is associated with the correct theme to apply when inflating views, i.e. the toolbar context
* @param managedList The list to manage the selection for and execute selection action to * @param managedList The list to manage the selection for and execute selection action to
* @param titleTemplateResource The string resource id to show as the spinners title; the number of selected items * @param titleTemplateResource The string resource id to show as the spinners title; the number of selected items
@ -58,6 +60,7 @@ public class SelectionManagerMode implements MultiChoiceModeListener, OnModifica
/** /**
* Set the class type of items that are allowed to be checked in the {@link ListView}. Defaults to null, which means * Set the class type of items that are allowed to be checked in the {@link ListView}. Defaults to null, which means
* every list view row can be checked. * every list view row can be checked.
*
* @param onlyCheckClass The {@link Class} instance to use to check list item types against * @param onlyCheckClass The {@link Class} instance to use to check list item types against
*/ */
public void setOnlyCheckClass(Class<?> onlyCheckClass) { public void setOnlyCheckClass(Class<?> onlyCheckClass) {

39
app/src/main/java/org/transdroid/core/gui/navigation/SelectionModificationSpinner.java

@ -16,27 +16,30 @@
*/ */
package org.transdroid.core.gui.navigation; package org.transdroid.core.gui.navigation;
import org.transdroid.R;
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.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import androidx.appcompat.widget.AppCompatSpinner;
import org.transdroid.R;
/** /**
* Spinner that holds actions that can be performed on list selections. The spinner itself has some title, which can for * Spinner that holds actions that can be performed on list selections. The spinner itself has some title, which can for
* example be used to show the number of selected items. * example be used to show the number of selected items.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class SelectionModificationSpinner extends Spinner { public class SelectionModificationSpinner extends AppCompatSpinner {
private SelectionDropDownAdapter selectionAdapter; private SelectionDropDownAdapter selectionAdapter;
private OnModificationActionSelectedListener onModificationActionSelected = null; private OnModificationActionSelectedListener onModificationActionSelected = null;
/** /**
* Instantiates a spinner that contains some fixed actions for a user to modify selections. * Instantiates a spinner that contains some fixed actions for a user to modify selections.
*
* @param context The interface context where the spinner will be shown in * @param context The interface context where the spinner will be shown in
*/ */
public SelectionModificationSpinner(Context context) { public SelectionModificationSpinner(Context context) {
@ -47,6 +50,7 @@ public class SelectionModificationSpinner extends Spinner {
/** /**
* Updates the fixed title text shown in the spinner, regardless of spinner item action selection. * Updates the fixed title text shown in the spinner, regardless of spinner item action selection.
*
* @param title The new static string to show, such as the number of selected items * @param title The new static string to show, such as the number of selected items
*/ */
public void updateTitle(String title) { public void updateTitle(String title) {
@ -56,6 +60,7 @@ public class SelectionModificationSpinner extends Spinner {
/** /**
* Sets the listener for action selection events. * Sets the listener for action selection events.
*
* @param onModificationActionSelected The listener that handles performing of the actions as selected in this * @param onModificationActionSelected The listener that handles performing of the actions as selected in this
* spinner by the user * spinner by the user
*/ */
@ -75,19 +80,30 @@ public class SelectionModificationSpinner extends Spinner {
super.setSelection(position); super.setSelection(position);
} }
/**
* Interface to implement if an interface want to respond to selection modification actions.
*/
public interface OnModificationActionSelectedListener {
void selectAll();
void selectFinished();
void invertSelection();
}
/** /**
* Local adapter that holds the actions which can be performed and a title text view that always shows instead of a * Local adapter that holds the actions which can be performed and a title text view that always shows instead of a
* list item as in a normal spinner. * list item as in a normal spinner.
*/ */
private class SelectionDropDownAdapter extends ArrayAdapter<String> { private static class SelectionDropDownAdapter extends ArrayAdapter<String> {
protected TextView titleView = null; protected TextView titleView = null;
public SelectionDropDownAdapter(Context context) { public SelectionDropDownAdapter(Context context) {
super(context, android.R.layout.simple_list_item_1, new String[] { super(context, android.R.layout.simple_list_item_1, new String[]{
context.getString(R.string.navigation_selectall), context.getString(R.string.navigation_selectall),
context.getString(R.string.navigation_selectfinished), context.getString(R.string.navigation_selectfinished),
context.getString(R.string.navigation_invertselection) }); context.getString(R.string.navigation_invertselection)});
titleView = new TextView(getContext()); titleView = new TextView(getContext());
} }
@ -105,13 +121,4 @@ public class SelectionModificationSpinner extends Spinner {
} }
/**
* Interface to implement if an interface want to respond to selection modification actions.
*/
public interface OnModificationActionSelectedListener {
public void selectAll();
public void selectFinished();
public void invertSelection();
}
} }

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

@ -20,8 +20,6 @@ import android.content.Context;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ListView; import android.widget.ListView;
@ -39,6 +37,7 @@ public class SetLabelDialog {
/** /**
* A dialog fragment that allows picking a label or entering a new label to set this new label to the torrent. * A dialog fragment that allows picking a label or entering a new label to set this new label to the torrent.
*
* @param context The activity context that opens (and owns) this dialog * @param context The activity context that opens (and owns) this dialog
* @param onLabelPickedListener The callback when a new label has been entered or picked by the user * @param onLabelPickedListener The callback when a new label has been entered or picked by the user
* @param currentLabels The list of labels as currently exist on the server, to present as list for easy selection * @param currentLabels The list of labels as currently exist on the server, to present as list for easy selection
@ -61,22 +60,17 @@ public class SetLabelDialog {
.positiveText(R.string.status_update) .positiveText(R.string.status_update)
.neutralText(R.string.status_label_remove) .neutralText(R.string.status_label_remove)
.negativeText(android.R.string.cancel) .negativeText(android.R.string.cancel)
.callback(new MaterialDialog.ButtonCallback() { .onPositive((dialog, which) -> {
@Override
public void onPositive(MaterialDialog dialog) {
// User should have provided a new label // User should have provided a new label
if (TextUtils.isEmpty(newLabelEdit.getText())) { if (TextUtils.isEmpty(newLabelEdit.getText())) {
SnackbarManager.show(Snackbar.with(context).text(R.string.error_notalabel).colorResource(R.color.red)); SnackbarManager.show(Snackbar.with(context).text(R.string.error_notalabel).colorResource(R.color.red));
return; return;
} }
onLabelPickedListener.onLabelPicked(newLabelEdit.getText().toString()); onLabelPickedListener.onLabelPicked(newLabelEdit.getText().toString());
} })
.onNeutral((dialog, which) ->
onLabelPickedListener.onLabelPicked(null));
@Override
public void onNeutral(MaterialDialog dialog) {
onLabelPickedListener.onLabelPicked(null);
}
});
final MaterialDialog dialog = SettingsUtils final MaterialDialog dialog = SettingsUtils
.applyDialogTheme(builder) .applyDialogTheme(builder)
.build(); .build();
@ -87,12 +81,9 @@ public class SetLabelDialog {
labelsList.setVisibility(View.GONE); labelsList.setVisibility(View.GONE);
} else { } else {
labelsList.setAdapter(new FilterListItemAdapter(context, currentLabels)); labelsList.setAdapter(new FilterListItemAdapter(context, currentLabels));
labelsList.setOnItemClickListener(new OnItemClickListener() { labelsList.setOnItemClickListener((parent, view, position, id) -> {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
onLabelPickedListener.onLabelPicked(((Label) labelsList.getItemAtPosition(position)).getName()); onLabelPickedListener.onLabelPicked(((Label) labelsList.getItemAtPosition(position)).getName());
dialog.dismiss(); dialog.dismiss();
}
}); });
} }

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

@ -30,6 +30,7 @@ public class SetStorageLocationDialog {
/** /**
* A dialog fragment that allows changing of the storage location by editing the path text directly. * A dialog fragment that allows changing of the storage location by editing the path text directly.
*
* @param context The activity context that opens (and owns) this dialog * @param context The activity context that opens (and owns) this dialog
* @param onStorageLocationUpdatedListener The callback for when the user is done updating the storage location * @param onStorageLocationUpdatedListener The callback for when the user is done updating the storage location
* @param currentLocation The current storage location that will be available to the user to edit * @param currentLocation The current storage location that will be available to the user to edit
@ -42,12 +43,9 @@ public class SetStorageLocationDialog {
.customView(locationLayout, false) .customView(locationLayout, false)
.positiveText(R.string.status_update) .positiveText(R.string.status_update)
.negativeText(android.R.string.cancel) .negativeText(android.R.string.cancel)
.callback(new MaterialDialog.ButtonCallback() { .onPositive((dialog, which) -> {
@Override
public void onPositive(MaterialDialog dialog) {
// User is done editing and requested to update given the text input // User is done editing and requested to update given the text input
onStorageLocationUpdatedListener.onStorageLocationUpdated(locationText.getText().toString()); onStorageLocationUpdatedListener.onStorageLocationUpdated(locationText.getText().toString());
}
}); });
SettingsUtils SettingsUtils

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

@ -16,12 +16,13 @@
*/ */
package org.transdroid.core.gui.navigation; package org.transdroid.core.gui.navigation;
import android.app.DialogFragment;
import android.content.Context; import android.content.Context;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.EditText; import android.widget.EditText;
import androidx.fragment.app.DialogFragment;
import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.MaterialDialog;
import org.transdroid.R; import org.transdroid.R;
@ -34,6 +35,7 @@ public class SetTrackersDialog extends DialogFragment {
/** /**
* A dialog fragment that allows changing the trackers of a torrent by editing the text directly. * A dialog fragment that allows changing the trackers of a torrent by editing the text directly.
*
* @param context The activity context that opens (and owns) this dialog * @param context The activity context that opens (and owns) this dialog
* @param onTrackersUpdatedListener The callback for when the user is done updating the trackers list * @param onTrackersUpdatedListener The callback for when the user is done updating the trackers list
* @param currentTrackers The current trackers text/list that will be available to the user to edit * @param currentTrackers The current trackers text/list that will be available to the user to edit
@ -46,12 +48,9 @@ public class SetTrackersDialog extends DialogFragment {
.customView(trackersLayout, false) .customView(trackersLayout, false)
.positiveText(R.string.status_update) .positiveText(R.string.status_update)
.negativeText(android.R.string.cancel) .negativeText(android.R.string.cancel)
.callback(new MaterialDialog.ButtonCallback() { .onPositive((dialog, which) -> {
@Override
public void onPositive(MaterialDialog dialog) {
// User is done editing and requested to update given the text input // User is done editing and requested to update given the text input
onTrackersUpdatedListener.onTrackersUpdated(Arrays.asList(trackersText.getText().toString().split("\n"))); onTrackersUpdatedListener.onTrackersUpdated(Arrays.asList(trackersText.getText().toString().split("\n")));
}
}); });
SettingsUtils.applyDialogTheme(builder).show(); SettingsUtils.applyDialogTheme(builder).show();
} }

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

@ -30,8 +30,19 @@ import org.transdroid.core.app.settings.SettingsUtils;
public class SetTransferRatesDialog { public class SetTransferRatesDialog {
private static OnClickListener onNumberClicked = v -> {
// Append the text contents of the button itself as text to the current number (as reference in the view's
// tag)
TextView numberView = (TextView) v.getTag();
if (numberView.getText().toString().equals(v.getContext().getString(R.string.status_maxspeed_novalue))) {
numberView.setText("");
}
numberView.setText(numberView.getText().toString() + ((Button) v).getText().toString());
};
/** /**
* A dialog fragment that allow picking of maximum download and upload transfer rates as well as the resetting of these values. * A dialog fragment that allow picking of maximum download and upload transfer rates as well as the resetting of these values.
*
* @param context The activity context that opens (and owns) this dialog * @param context The activity context that opens (and owns) this dialog
* @param onRatesPickedListener The callback for results in this dialog (with newly selected values or a reset) * @param onRatesPickedListener The callback for results in this dialog (with newly selected values or a reset)
*/ */
@ -46,9 +57,7 @@ public class SetTransferRatesDialog {
.positiveText(R.string.status_update) .positiveText(R.string.status_update)
.neutralText(R.string.status_maxspeed_reset) .neutralText(R.string.status_maxspeed_reset)
.negativeText(android.R.string.cancel) .negativeText(android.R.string.cancel)
.callback(new MaterialDialog.ButtonCallback() { .onPositive((dialog, which) -> {
@Override
public void onPositive(MaterialDialog dialog) {
int maxDown = -1, maxUp = -1; int maxDown = -1, maxUp = -1;
try { try {
maxDown = Integer.parseInt(maxSpeedDown.getText().toString()); maxDown = Integer.parseInt(maxSpeedDown.getText().toString());
@ -61,13 +70,10 @@ public class SetTransferRatesDialog {
return; return;
} }
onRatesPickedListener.onRatesPicked(maxDown, maxUp); onRatesPickedListener.onRatesPicked(maxDown, maxUp);
} })
.onNeutral((dialog, which) ->
onRatesPickedListener.resetRates());
@Override
public void onNeutral(MaterialDialog dialog) {
onRatesPickedListener.resetRates();
}
});
MaterialDialog dialog = SettingsUtils.applyDialogTheme(builder).build(); MaterialDialog dialog = SettingsUtils.applyDialogTheme(builder).build();
bindButtons(dialog.getCustomView(), maxSpeedDown, R.id.down1Button, R.id.down2Button, R.id.down3Button, R.id.down4Button, R.id.down5Button, bindButtons(dialog.getCustomView(), maxSpeedDown, R.id.down1Button, R.id.down2Button, R.id.down3Button, R.id.down4Button, R.id.down5Button,
@ -87,19 +93,6 @@ public class SetTransferRatesDialog {
} }
} }
private static OnClickListener onNumberClicked = new OnClickListener() {
@Override
public void onClick(View v) {
// Append the text contents of the button itself as text to the current number (as reference in the view's
// tag)
TextView numberView = (TextView) v.getTag();
if (numberView.getText().toString().equals(v.getContext().getString(R.string.status_maxspeed_novalue))) {
numberView.setText("");
}
numberView.setText(numberView.getText().toString() + ((Button) v).getText().toString());
}
};
/** /**
* Listener interface to the user having picked or wanting to resets the current maximum transfer speeds; * Listener interface to the user having picked or wanting to resets the current maximum transfer speeds;
*/ */

44
app/src/main/java/org/transdroid/core/gui/navigation/StatusType.java

@ -16,19 +16,20 @@
*/ */
package org.transdroid.core.gui.navigation; package org.transdroid.core.gui.navigation;
import java.util.Arrays; import android.content.Context;
import java.util.List; import android.os.Parcel;
import android.os.Parcelable;
import org.transdroid.R; import org.transdroid.R;
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.content.Context; import java.util.Arrays;
import android.os.Parcel; import java.util.List;
import android.os.Parcelable;
/** /**
* Enumeration of all status types, which filter the list of shown torrents based on transfer activity. * Enumeration of all status types, which filter the list of shown torrents based on transfer activity.
*
* @author Eric Kok * @author Eric Kok
*/ */
public enum StatusType { public enum StatusType {
@ -61,6 +62,7 @@ public enum StatusType {
/** /**
* Returns the status type to show all torrents, represented as filter item to show in the navigation list. * Returns the status type to show all torrents, represented as filter item to show in the navigation list.
*
* @param context The Android UI context, to access translations * @param context The Android UI context, to access translations
* @return The show ShowAll status type filter item * @return The show ShowAll status type filter item
*/ */
@ -70,6 +72,7 @@ public enum StatusType {
/** /**
* Returns a list with all status types, represented as filter item that can be shown in the GUI. * Returns a list with all status types, represented as filter item that can be shown in the GUI.
*
* @param context The Android UI context, to access translations * @param context The Android UI context, to access translations
* @return A list of filter items for all available status types * @return A list of filter items for all available status types
*/ */
@ -81,6 +84,7 @@ public enum StatusType {
/** /**
* Every status type can return a filter item that represents it in the navigation * Every status type can return a filter item that represents it in the navigation
*
* @param context The Android UI context, to access translations * @param context The Android UI context, to access translations
* @return A filter item object to show in the GUI * @return A filter item object to show in the GUI
*/ */
@ -88,6 +92,15 @@ public enum StatusType {
public static class StatusTypeFilter implements SimpleListItem, NavigationFilter { public static class StatusTypeFilter implements SimpleListItem, NavigationFilter {
public static final Parcelable.Creator<StatusTypeFilter> CREATOR = new Parcelable.Creator<StatusTypeFilter>() {
public StatusTypeFilter createFromParcel(Parcel in) {
return new StatusTypeFilter(in);
}
public StatusTypeFilter[] newArray(int size) {
return new StatusTypeFilter[size];
}
};
private final StatusType statusType; private final StatusType statusType;
private final String name; private final String name;
@ -96,6 +109,11 @@ public enum StatusType {
this.name = name; this.name = name;
} }
private StatusTypeFilter(Parcel in) {
this.statusType = StatusType.valueOf(in.readString());
this.name = in.readString();
}
public StatusType getStatusType() { public StatusType getStatusType() {
return statusType; return statusType;
} }
@ -113,6 +131,7 @@ public enum StatusType {
/** /**
* Returns true if the torrent status matches this (selected) status type, false otherwise * Returns true if the torrent status matches this (selected) status type, false otherwise
*
* @param torrent The torrent to match against this status type * @param torrent The torrent to match against this status type
* @param dormantAsInactive If true, dormant (0KB/s, so no data transfer) torrents are never actively * @param dormantAsInactive If true, dormant (0KB/s, so no data transfer) torrents are never actively
* downloading or seeding * downloading or seeding
@ -134,21 +153,6 @@ public enum StatusType {
} }
} }
private StatusTypeFilter(Parcel in) {
this.statusType = StatusType.valueOf(in.readString());
this.name = in.readString();
}
public static final Parcelable.Creator<StatusTypeFilter> CREATOR = new Parcelable.Creator<StatusTypeFilter>() {
public StatusTypeFilter createFromParcel(Parcel in) {
return new StatusTypeFilter(in);
}
public StatusTypeFilter[] newArray(int size) {
return new StatusTypeFilter[size];
}
};
@Override @Override
public int describeContents() { public int describeContents() {
return 0; return 0;

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

@ -17,13 +17,14 @@
package org.transdroid.core.gui.remoterss; package org.transdroid.core.gui.remoterss;
import androidx.fragment.app.Fragment;
import android.view.View; import android.view.View;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.ListView; import android.widget.ListView;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import androidx.fragment.app.Fragment;
import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EFragment; import org.androidannotations.annotations.EFragment;
@ -44,6 +45,7 @@ import java.util.List;
/** /**
* Fragment that shows a list of RSS items from the server and allows the user * Fragment that shows a list of RSS items from the server and allows the user
* to download remotely, without having to set up RSS feeds on the Android device. * to download remotely, without having to set up RSS feeds on the Android device.
*
* @author Twig * @author Twig
*/ */
@EFragment(R.layout.fragment_remoterss) @EFragment(R.layout.fragment_remoterss)
@ -108,8 +110,7 @@ public class RemoteRssFragment extends Fragment {
// Show/hide a nice message if there are no items to show // Show/hide a nice message if there are no items to show
if (remoteRssItems.size() > 0) { if (remoteRssItems.size() > 0) {
remoteRssStatusMessage.setVisibility(View.GONE); remoteRssStatusMessage.setVisibility(View.GONE);
} } else {
else {
remoteRssStatusMessage.setVisibility(View.VISIBLE); remoteRssStatusMessage.setVisibility(View.VISIBLE);
remoteRssStatusMessage.setText(R.string.remoterss_no_files); remoteRssStatusMessage.setText(R.string.remoterss_no_files);
} }

1
app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssItemView.java

@ -28,6 +28,7 @@ import org.transdroid.core.gui.remoterss.data.RemoteRssItem;
/** /**
* View that represents some {@link RemoteRssItem} object. * View that represents some {@link RemoteRssItem} object.
*
* @author Twig * @author Twig
*/ */
@EViewGroup(R.layout.list_item_remoterssitem) @EViewGroup(R.layout.list_item_remoterssitem)

3
app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssItemsAdapter.java

@ -40,8 +40,7 @@ public class RemoteRssItemsAdapter extends BaseAdapter {
if (convertView == null) { if (convertView == null) {
itemView = RemoteRssItemView_.build(context); itemView = RemoteRssItemView_.build(context);
} } else {
else {
itemView = (RemoteRssItemView) convertView; itemView = (RemoteRssItemView) convertView;
} }

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

@ -22,17 +22,18 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcel; import android.os.Parcel;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.material.tabs.TabLayout;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import android.text.TextUtils; import androidx.viewpager.widget.PagerAdapter;
import android.view.View; import androidx.viewpager.widget.ViewPager;
import android.view.ViewGroup;
import com.google.android.material.tabs.TabLayout;
import com.nispok.snackbar.Snackbar; import com.nispok.snackbar.Snackbar;
import com.nispok.snackbar.SnackbarManager; import com.nispok.snackbar.SnackbarManager;
import com.nispok.snackbar.enums.SnackbarType; import com.nispok.snackbar.enums.SnackbarType;
@ -71,22 +72,19 @@ import org.transdroid.daemon.task.DaemonTaskSuccessResult;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@EActivity(R.layout.activity_rssfeeds) @EActivity(R.layout.activity_rssfeeds)
public class RssFeedsActivity extends AppCompatActivity { public class RssFeedsActivity extends AppCompatActivity {
protected static final int RSS_FEEDS_LOCAL = 0;
protected static final int RSS_FEEDS_REMOTE = 1;
// Settings and local data // Settings and local data
@Bean @Bean
protected Log log; protected Log log;
@Bean @Bean
protected ApplicationSettings applicationSettings; protected ApplicationSettings applicationSettings;
protected static final int RSS_FEEDS_LOCAL = 0;
protected static final int RSS_FEEDS_REMOTE = 1;
@FragmentById(R.id.rssfeeds_fragment) @FragmentById(R.id.rssfeeds_fragment)
protected RssFeedsFragment fragmentLocalFeeds; protected RssFeedsFragment fragmentLocalFeeds;
@FragmentById(R.id.rssitems_fragment) @FragmentById(R.id.rssitems_fragment)
@ -111,62 +109,6 @@ public class RssFeedsActivity extends AppCompatActivity {
@Bean @Bean
protected ConnectivityHelper connectivityHelper; protected ConnectivityHelper connectivityHelper;
protected class LayoutPagerAdapter extends PagerAdapter {
boolean hasRemoteRss;
String serverName;
public LayoutPagerAdapter(boolean hasRemoteRss, String name) {
super();
this.hasRemoteRss = hasRemoteRss;
this.serverName = (name.length() > 0 ? name : getString(R.string.navigation_rss_tabs_remote));
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
int resId = 0;
if (position == RSS_FEEDS_LOCAL) {
resId = R.id.layout_rssfeeds_local;
}
else if (position == RSS_FEEDS_REMOTE) {
resId = R.id.layout_rss_feeds_remote;
}
return findViewById(resId);
}
@Override
public int getCount() {
return (this.hasRemoteRss ? 2 : 1);
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
return (view == o);
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View) object);
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case RSS_FEEDS_LOCAL:
return getString(R.string.navigation_rss_tabs_local);
case RSS_FEEDS_REMOTE:
return this.serverName;
}
return super.getPageTitle(position);
}
}
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
SettingsUtils.applyDayNightTheme(this); SettingsUtils.applyDayNightTheme(this);
@ -196,7 +138,8 @@ public class RssFeedsActivity extends AppCompatActivity {
try { try {
hasRemoteFeeds = remoteConnection.getRemoteRssChannels(log).size() > 0; hasRemoteFeeds = remoteConnection.getRemoteRssChannels(log).size() > 0;
} catch (DaemonException e) {} } catch (DaemonException ignored) {
}
if (hasRemoteFeeds) { if (hasRemoteFeeds) {
defaultTab = RSS_FEEDS_REMOTE; defaultTab = RSS_FEEDS_REMOTE;
@ -234,6 +177,7 @@ public class RssFeedsActivity extends AppCompatActivity {
/** /**
* Performs the loading of the RSS feed content and parsing of items, in a background thread. * Performs the loading of the RSS feed content and parsing of items, in a background thread.
*
* @param loader The RSS feed loader for which to retrieve the contents * @param loader The RSS feed loader for which to retrieve the contents
*/ */
@Background @Background
@ -253,6 +197,7 @@ public class RssFeedsActivity extends AppCompatActivity {
/** /**
* Stores the retrieved RSS feed content channel into the loader and updates the RSS feed in the feeds list fragment. * Stores the retrieved RSS feed content channel into the loader and updates the RSS feed in the feeds list fragment.
*
* @param loader The RSS feed loader that was executed * @param 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
@ -267,6 +212,7 @@ public class RssFeedsActivity extends AppCompatActivity {
/** /**
* Opens an RSS feed in the dedicated fragment (if there was space in the UI) or a new {@link RssItemsActivity}. Optionally this also registers in * Opens an RSS feed in the dedicated fragment (if there was space in the UI) or a new {@link RssItemsActivity}. Optionally this also registers in
* the user preferences that the feed was now viewed, so that in the future the new items can be properly marked. * the user preferences that the feed was now viewed, so that in the future the new items can be properly marked.
*
* @param loader The RSS feed loader (with settings and the loaded content channel) to show * @param loader The RSS feed loader (with settings and the loaded content channel) to show
* @param markAsViewedNow True if the user settings should be updated to reflect this feed's last viewed date; false otherwise * @param markAsViewedNow True if the user settings should be updated to reflect this feed's last viewed date; false otherwise
*/ */
@ -332,7 +278,7 @@ public class RssFeedsActivity extends AppCompatActivity {
IDaemonAdapter currentConnection = this.getCurrentConnection(); IDaemonAdapter currentConnection = this.getCurrentConnection();
// remote rss not supported for this connection type // remote rss not supported for this connection type
if (currentConnection instanceof RemoteRssSupplier == false) { if (!(currentConnection instanceof RemoteRssSupplier)) {
return; return;
} }
@ -354,12 +300,8 @@ public class RssFeedsActivity extends AppCompatActivity {
} }
// Sort by -newest // Sort by -newest
Collections.sort(recentItems, new Comparator<RemoteRssItem>() { Collections.sort(recentItems, (lhs, rhs) ->
@Override rhs.getTimestamp().compareTo(lhs.getTimestamp()));
public int compare(RemoteRssItem lhs, RemoteRssItem rhs) {
return rhs.getTimestamp().compareTo(lhs.getTimestamp());
}
});
} catch (DaemonException e) { } catch (DaemonException e) {
onCommunicationError(e); onCommunicationError(e);
return; return;
@ -367,28 +309,25 @@ public class RssFeedsActivity extends AppCompatActivity {
// @UIThread // @UIThread
fragmentRemoteFeeds.updateRemoteItems( fragmentRemoteFeeds.updateRemoteItems(
selectedFilter == 0 ? recentItems : feeds.get(selectedFilter -1).getItems(), selectedFilter == 0 ? recentItems : feeds.get(selectedFilter - 1).getItems(),
false /* allow android to restore scroll position */ ); false /* allow android to restore scroll position */);
showRemoteChannelFilters(); showRemoteChannelFilters();
} }
@UiThread @UiThread
protected void onCommunicationError(DaemonException daemonException) { protected void onCommunicationError(DaemonException daemonException) {
//noinspection ThrowableResultOfMethodCallIgnored
log.i(this, daemonException.toString()); log.i(this, daemonException.toString());
String error = getString(LocalTorrent.getResourceForDaemonException(daemonException)); String error = getString(LocalTorrent.getResourceForDaemonException(daemonException));
SnackbarManager.show(Snackbar.with(this).text(error).colorResource(R.color.red).type(SnackbarType.MULTI_LINE)); SnackbarManager.show(Snackbar.with(this).text(error).colorResource(R.color.red).type(SnackbarType.MULTI_LINE));
} }
public void onFeedSelected(int position) { public void onFeedSelected(int position) {
selectedFilter = position; selectedFilter = position;
if (position == 0) { if (position == 0) {
fragmentRemoteFeeds.updateRemoteItems(recentItems, true); fragmentRemoteFeeds.updateRemoteItems(recentItems, true);
} } else {
else { RemoteRssChannel channel = feeds.get(selectedFilter - 1);
RemoteRssChannel channel = feeds.get(selectedFilter -1);
fragmentRemoteFeeds.updateRemoteItems(channel.getItems(), true); fragmentRemoteFeeds.updateRemoteItems(channel.getItems(), true);
} }
} }
@ -424,7 +363,7 @@ public class RssFeedsActivity extends AppCompatActivity {
} }
private void showRemoteChannelFilters() { private void showRemoteChannelFilters() {
List<RemoteRssChannel> feedLabels = new ArrayList<>(feeds.size() +1); List<RemoteRssChannel> feedLabels = new ArrayList<>(feeds.size() + 1);
feedLabels.add(new RemoteRssChannel() { feedLabels.add(new RemoteRssChannel() {
@Override @Override
public String getName() { public String getName() {
@ -439,4 +378,58 @@ public class RssFeedsActivity extends AppCompatActivity {
fragmentRemoteFeeds.updateChannelFilters(feedLabels); fragmentRemoteFeeds.updateChannelFilters(feedLabels);
} }
protected class LayoutPagerAdapter extends PagerAdapter {
boolean hasRemoteRss;
String serverName;
public LayoutPagerAdapter(boolean hasRemoteRss, String name) {
super();
this.hasRemoteRss = hasRemoteRss;
this.serverName = (name.length() > 0 ? name : getString(R.string.navigation_rss_tabs_remote));
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
int resId = 0;
if (position == RSS_FEEDS_LOCAL) {
resId = R.id.layout_rssfeeds_local;
} else if (position == RSS_FEEDS_REMOTE) {
resId = R.id.layout_rss_feeds_remote;
}
return findViewById(resId);
}
@Override
public int getCount() {
return (this.hasRemoteRss ? 2 : 1);
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
return (view == o);
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View) object);
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case RSS_FEEDS_LOCAL:
return getString(R.string.navigation_rss_tabs_local);
case RSS_FEEDS_REMOTE:
return this.serverName;
}
return super.getPageTitle(position);
}
}
} }

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

@ -16,13 +16,14 @@
*/ */
package org.transdroid.core.gui.rss; package org.transdroid.core.gui.rss;
import androidx.fragment.app.Fragment;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.ListView; import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import androidx.fragment.app.Fragment;
import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EFragment; import org.androidannotations.annotations.EFragment;
@ -37,6 +38,7 @@ import java.util.List;
/** /**
* Fragment lists the RSS feeds the user wants to monitor and, if room, the list of items in a feed in a right pane. * 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(R.layout.fragment_rssfeeds) @EFragment(R.layout.fragment_rssfeeds)

1
app/src/main/java/org/transdroid/core/gui/rss/RssItemsActivity.java

@ -20,6 +20,7 @@ import android.annotation.TargetApi;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;

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

@ -23,8 +23,6 @@ import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import androidx.fragment.app.Fragment;
import androidx.appcompat.app.AppCompatActivity;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.Menu; import android.view.Menu;
@ -35,6 +33,9 @@ import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import com.nispok.snackbar.Snackbar; import com.nispok.snackbar.Snackbar;
import com.nispok.snackbar.SnackbarManager; import com.nispok.snackbar.SnackbarManager;
@ -57,6 +58,7 @@ import java.util.List;
/** /**
* 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(R.layout.fragment_rssitems) @EFragment(R.layout.fragment_rssitems)
@ -75,6 +77,10 @@ public class RssItemsFragment extends Fragment {
// Views // Views
@ViewById(R.id.rssitems_list) @ViewById(R.id.rssitems_list)
protected ListView rssItemsList; protected ListView rssItemsList;
@Bean
protected RssitemsAdapter rssitemsAdapter;
@ViewById
protected TextView emptyText;
private MultiChoiceModeListener onItemsSelected = new MultiChoiceModeListener() { private MultiChoiceModeListener onItemsSelected = new MultiChoiceModeListener() {
SelectionManagerMode selectionManagerMode; SelectionManagerMode selectionManagerMode;
@ -180,10 +186,6 @@ public class RssItemsFragment extends Fragment {
} }
}; };
@Bean
protected RssitemsAdapter rssitemsAdapter;
@ViewById
protected TextView emptyText;
@AfterViews @AfterViews
protected void init() { protected void init() {
@ -197,6 +199,7 @@ public class RssItemsFragment extends Fragment {
/** /**
* Update the shown RSS items in the list. * Update the shown RSS items in the list.
*
* @param channel The loaded RSS content channel object * @param channel The loaded RSS content channel object
* @param hasError True if there were errors in loading the channel, in which case an error text is shown; false otherwise * @param hasError True if there were errors in loading the channel, in which case an error text is shown; false otherwise
* @param requiresExternalAuthentication Whether this RSS feed requires external authentication and should thus be redirected to a browser * @param requiresExternalAuthentication Whether this RSS feed requires external authentication and should thus be redirected to a browser

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

@ -21,13 +21,13 @@ import org.transdroid.core.rssparser.Channel;
import org.transdroid.core.rssparser.Item; import org.transdroid.core.rssparser.Item;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
/** /**
* A container class that holds RSS feed settings and, after they have been retrieved, the contents as {@link Channel}, the number of new items and an * A container class that holds RSS feed settings and, after they have been retrieved, the contents as {@link Channel}, the number of new items and an
* indication of a connection error. * indication of a connection error.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class RssfeedLoader { public class RssfeedLoader {
@ -55,17 +55,13 @@ public class RssfeedLoader {
Date pubDate = channel.getItems().get(0).getPubdate(); Date pubDate = channel.getItems().get(0).getPubdate();
usePublishDate = pubDate != null && pubDate.getTime() > 0; usePublishDate = pubDate != null && pubDate.getTime() > 0;
} }
newCount = 0;
if (usePublishDate) { if (usePublishDate) {
// Count the number of new items, based on the date that this RSS feed was last viewed by the user // Count the number of new items, based on the date that this RSS feed was last viewed by the user
newCount = 0;
List<Item> items = channel.getItems(); List<Item> items = channel.getItems();
// Reverse-order sort the items on their published date // Reverse-order sort the items on their published date
Collections.sort(items, new Comparator<Item>() { Collections.sort(items, (lhs, rhs) ->
@Override -lhs.getPubdate().compareTo(rhs.getPubdate()));
public int compare(Item lhs, Item rhs) {
return 0 - lhs.getPubdate().compareTo(rhs.getPubdate());
}
});
for (Item item : items) { for (Item item : items) {
if (item.getPubdate() == null || setting.getLastViewed() == null || item.getPubdate().after(setting.getLastViewed())) { if (item.getPubdate() == null || setting.getLastViewed() == null || item.getPubdate().after(setting.getLastViewed())) {
newCount++; newCount++;
@ -76,7 +72,6 @@ public class RssfeedLoader {
} }
} else { } else {
// 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
newCount = 0;
boolean isNew = true; boolean isNew = true;
for (Item item : channel.getItems()) { for (Item item : channel.getItems()) {
if (item.getTheLink() != null && setting.getLastViewedItemUrl() != null && item.getTheLink().equals(setting.getLastViewedItemUrl())) { if (item.getTheLink() != null && setting.getLastViewedItemUrl() != null && item.getTheLink().equals(setting.getLastViewedItemUrl())) {

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

@ -33,6 +33,7 @@ import org.transdroid.core.gui.navigation.NavigationHelper;
/** /**
* View that represents some {@link RssfeedSetting} object and displays name as well as loads a favicon for the feed's site and can load how many new * View that represents some {@link RssfeedSetting} object and displays name as well as loads a favicon for the feed's site and can load how many new
* items are available. * items are available.
*
* @author Eric Kok * @author Eric Kok
*/ */
@EViewGroup(R.layout.list_item_rssfeed) @EViewGroup(R.layout.list_item_rssfeed)

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

@ -29,18 +29,19 @@ import java.util.List;
/** /**
* Adapter that contains a list of {@link RssfeedSetting}s, each with associated loaded RSS feed {@link org.transdroid.core.rssparser.Channel}. * Adapter that contains a list of {@link RssfeedSetting}s, each with associated loaded RSS feed {@link org.transdroid.core.rssparser.Channel}.
*
* @author Eric Kok * @author Eric Kok
*/ */
@EBean @EBean
public class RssfeedsAdapter extends BaseAdapter { public class RssfeedsAdapter extends BaseAdapter {
private List<RssfeedLoader> loaders = null;
@RootContext @RootContext
protected Context context; protected Context context;
private List<RssfeedLoader> loaders = null;
/** /**
* Allows updating the full internal list of feed loaders at once, replacing the old list * Allows updating the full internal list of feed loaders at once, replacing the old list
*
* @param loaders The new list of RSS feed loader objects, which pair settings and a loaded channel * @param loaders The new list of RSS feed loader objects, which pair settings and a loaded channel
*/ */
public void update(List<RssfeedLoader> loaders) { public void update(List<RssfeedLoader> loaders) {

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

@ -28,6 +28,7 @@ import org.transdroid.R;
/** /**
* A relative layout that that is checkable (to be used in a contextual action bar) and shows a coloured bar in the far left indicating the view * A relative layout that that is checkable (to be used in a contextual action bar) and shows a coloured bar in the far left indicating the view
* status, that is, if the item is new to the user or was viewed earlier. * 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 {
@ -65,9 +66,7 @@ public class RssitemStatusLayout extends RelativeLayout {
protected void onDraw(Canvas canvas) { protected void onDraw(Canvas canvas) {
super.onDraw(canvas); super.onDraw(canvas);
int height = getHeight(); fullRect.set(0, 0, WIDTH, getHeight());
int width = WIDTH;
fullRect.set(0, 0, width, height);
if (isNew == null) { if (isNew == null) {
return; return;

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

@ -27,6 +27,7 @@ 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(R.layout.list_item_rssitem) @EViewGroup(R.layout.list_item_rssitem)

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

@ -28,18 +28,19 @@ 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
*/ */
@EBean @EBean
public class RssitemsAdapter extends BaseAdapter { public class RssitemsAdapter extends BaseAdapter {
private Channel rssfeed = null;
@RootContext @RootContext
protected Context context; protected Context context;
private Channel rssfeed = null;
/** /**
* Allows updating the full RSS feed (channel and contained items), replacing the old data * Allows updating the full RSS feed (channel and contained items), replacing the old data
*
* @param rssfeed The new RSS feed contents * @param rssfeed The new RSS feed contents
*/ */
public void update(Channel rssfeed) { public void update(Channel rssfeed) {

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

@ -20,8 +20,6 @@ import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
@ -38,6 +36,7 @@ public class BarcodeHelper {
/** /**
* Call this to start a bar code scanner intent. The calling activity will receive an Intent result with the given * Call this to start a bar code scanner intent. The calling activity will receive an Intent result with the given
* request code. * request code.
*
* @param activity The calling activity, to which the result is returned or a dialog is bound that asks to install * @param activity The calling activity, to which the result is returned or a dialog is bound that asks to install
* the bar code scanner * the bar code scanner
* @param requestCode {@link #ACTIVITY_BARCODE_QRSETTINGS} * @param requestCode {@link #ACTIVITY_BARCODE_QRSETTINGS}
@ -51,6 +50,7 @@ public class BarcodeHelper {
* Call this to share content encoded in a QR code, specially used to share settings. The calling activity will * Call this to share content encoded in a QR code, specially used to share settings. The calling activity will
* receive an Intent result with ID {@link #ACTIVITY_BARCODE_QRSETTINGS}. From there the returned intent will * receive an Intent result with ID {@link #ACTIVITY_BARCODE_QRSETTINGS}. From there the returned intent will
* contain the data as SCAN_RESULT String extra. * contain the data as SCAN_RESULT String extra.
*
* @param activity The calling activity, to which the result is returned or a dialog is bound that asks to install * @param activity The calling activity, to which the result is returned or a dialog is bound that asks to install
* the bar code scanner * the bar code scanner
* @param content The content to share, that is, the raw data (Transdroid settings encoded as JSON data structure) * @param content The content to share, that is, the raw data (Transdroid settings encoded as JSON data structure)
@ -71,15 +71,12 @@ public class BarcodeHelper {
activity.startActivityForResult(intent, requestCode); activity.startActivityForResult(intent, requestCode);
} catch (Exception e) { } catch (Exception e) {
// Can't start the bar code scanner, for example with a SecurityException or when ZXing is not present // Can't start the bar code scanner, for example with a SecurityException or when ZXing is not present
final WeakReference<Context> intentStartContext = new WeakReference<Context>(activity); final WeakReference<Context> intentStartContext = new WeakReference<>(activity);
new AlertDialog.Builder(activity).setIcon(android.R.drawable.ic_dialog_alert) new AlertDialog.Builder(activity).setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(activity.getString(R.string.search_barcodescannernotfound)) .setMessage(activity.getString(R.string.search_barcodescannernotfound))
.setPositiveButton(android.R.string.yes, new OnClickListener() { .setPositiveButton(android.R.string.yes, (dialog, which) -> {
@Override
public void onClick(DialogInterface dialog, int which) {
if (intentStartContext.get() != null) if (intentStartContext.get() != null)
intentStartContext.get().startActivity(new Intent(Intent.ACTION_VIEW, SCANNER_MARKET_URI)); intentStartContext.get().startActivity(new Intent(Intent.ACTION_VIEW, SCANNER_MARKET_URI));
}
}).setNegativeButton(android.R.string.no, null).show(); }).setNegativeButton(android.R.string.no, null).show();
} }
} }

14
app/src/main/java/org/transdroid/core/gui/search/FilePickerHelper.java

@ -16,17 +16,15 @@
*/ */
package org.transdroid.core.gui.search; package org.transdroid.core.gui.search;
import org.transdroid.R;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import org.transdroid.R;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
public class FilePickerHelper { public class FilePickerHelper {
@ -37,6 +35,7 @@ public class FilePickerHelper {
/** /**
* Call this to start a file picker intent. The calling activity will receive an Intent result with ID * Call this to start a file picker intent. The calling activity will receive an Intent result with ID
* {@link #ACTIVITY_FILEPICKER} with an Intent that contains the selected local file as data Intent. * {@link #ACTIVITY_FILEPICKER} with an Intent that contains the selected local file as data Intent.
*
* @param activity The calling activity, to which the result is returned or a dialog is bound that asks to install * @param activity The calling activity, to which the result is returned or a dialog is bound that asks to install
* the file picker * the file picker
*/ */
@ -52,15 +51,12 @@ public class FilePickerHelper {
activity.startActivityForResult(new Intent("org.openintents.action.PICK_FILE"), ACTIVITY_FILEPICKER); activity.startActivityForResult(new Intent("org.openintents.action.PICK_FILE"), ACTIVITY_FILEPICKER);
} catch (Exception e2) { } catch (Exception e2) {
// Can't start the file manager, for example with a SecurityException or when IO File Manager is not present // Can't start the file manager, for example with a SecurityException or when IO File Manager is not present
final WeakReference<Context> intentStartContext = new WeakReference<Context>(activity); final WeakReference<Context> intentStartContext = new WeakReference<>(activity);
new AlertDialog.Builder(activity).setIcon(android.R.drawable.ic_dialog_alert) new AlertDialog.Builder(activity).setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(activity.getString(R.string.search_filemanagernotfound)) .setMessage(activity.getString(R.string.search_filemanagernotfound))
.setPositiveButton(android.R.string.yes, new OnClickListener() { .setPositiveButton(android.R.string.yes, (dialog, which) -> {
@Override
public void onClick(DialogInterface dialog, int which) {
if (intentStartContext.get() != null) if (intentStartContext.get() != null)
intentStartContext.get().startActivity(new Intent(Intent.ACTION_VIEW, FILEMANAGER_MARKET_URI)); intentStartContext.get().startActivity(new Intent(Intent.ACTION_VIEW, FILEMANAGER_MARKET_URI));
}
}).setNegativeButton(android.R.string.no, null).show(); }).setNegativeButton(android.R.string.no, null).show();
} }
} }

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

@ -23,9 +23,6 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.provider.SearchRecentSuggestions; import android.provider.SearchRecentSuggestions;
import androidx.core.view.MenuItemCompat;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -36,6 +33,9 @@ import android.widget.SearchView;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.EActivity;
@ -58,6 +58,7 @@ import java.util.List;
/** /**
* An activity that shows search results to the user (after a query was supplied by the standard Android search manager) and either shows the list of * An activity that shows search results to the user (after a query was supplied by the standard Android search manager) and either shows the list of
* search sites on the left (e.g. on tablets) or allows switching between search sites via the action bar spinner. * search sites on the left (e.g. on tablets) or allows switching between search sites via the action bar spinner.
*
* @author Eric Kok * @author Eric Kok
*/ */
@EActivity(R.layout.activity_search) @EActivity(R.layout.activity_search)
@ -85,6 +86,25 @@ public class SearchActivity extends AppCompatActivity {
private List<SearchSetting> searchSites; private List<SearchSetting> searchSites;
private SearchSetting lastUsedSite; private SearchSetting lastUsedSite;
private String lastUsedQuery; private String lastUsedQuery;
private OnItemClickListener onSearchSiteClicked = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
lastUsedSite = searchSites.get(position);
refreshSearch();
}
};
private AdapterView.OnItemSelectedListener onSearchSiteSelected = new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
lastUsedSite = searchSites.get(position);
refreshSearch();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
};
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -96,12 +116,8 @@ public class SearchActivity extends AppCompatActivity {
protected void init() { protected void init() {
searchToolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_material); searchToolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_material);
searchToolbar.setNavigationOnClickListener(new View.OnClickListener() { searchToolbar.setNavigationOnClickListener(v ->
@Override TorrentsActivity_.intent(SearchActivity.this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start());
public void onClick(View v) {
TorrentsActivity_.intent(SearchActivity.this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start();
}
});
setSupportActionBar(searchToolbar); setSupportActionBar(searchToolbar);
// Get the user query, as coming from the standard SearchManager // Get the user query, as coming from the standard SearchManager
@ -172,7 +188,7 @@ public class SearchActivity extends AppCompatActivity {
searchView.setQueryRefinementEnabled(true); searchView.setQueryRefinementEnabled(true);
searchView.setIconified(false); searchView.setIconified(false);
searchView.setIconifiedByDefault(false); searchView.setIconifiedByDefault(false);
MenuItemCompat.setActionView(item, searchView); item.setActionView(searchView);
searchMenu = item; searchMenu = item;
final MenuItem sortBySeeders = menu.findItem(R.id.action_sort_seeders); final MenuItem sortBySeeders = menu.findItem(R.id.action_sort_seeders);
final MenuItem sortByAdded = menu.findItem(R.id.action_sort_added); final MenuItem sortByAdded = menu.findItem(R.id.action_sort_added);
@ -197,9 +213,9 @@ public class SearchActivity extends AppCompatActivity {
searchsitesList.setVisibility(searchInstalled ? View.VISIBLE : View.GONE); searchsitesList.setVisibility(searchInstalled ? View.VISIBLE : View.GONE);
} }
if (searchInstalled) { if (searchInstalled) {
getFragmentManager().beginTransaction().show(fragmentResults).commit(); getSupportFragmentManager().beginTransaction().show(fragmentResults).commit();
} else { } else {
getFragmentManager().beginTransaction().hide(fragmentResults).commit(); getSupportFragmentManager().beginTransaction().hide(fragmentResults).commit();
} }
installmoduleText.setVisibility(searchInstalled ? View.GONE : View.VISIBLE); installmoduleText.setVisibility(searchInstalled ? View.GONE : View.VISIBLE);
@ -208,6 +224,7 @@ public class SearchActivity extends AppCompatActivity {
@Override @Override
protected void onNewIntent(Intent intent) { protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handleIntent(intent); handleIntent(intent);
refreshSearch(); refreshSearch();
} }
@ -236,28 +253,9 @@ public class SearchActivity extends AppCompatActivity {
return true; return true;
} }
private OnItemClickListener onSearchSiteClicked = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
lastUsedSite = searchSites.get(position);
refreshSearch();
}
};
private AdapterView.OnItemSelectedListener onSearchSiteSelected = new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
lastUsedSite = searchSites.get(position);
refreshSearch();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
};
/** /**
* Extracts the query string from the search {@link Intent} * Extracts the query string from the search {@link Intent}
*
* @return The query string that was entered by the user * @return The query string that was entered by the user
*/ */
private String parseQuery(Intent intent) { private String parseQuery(Intent intent) {

1
app/src/main/java/org/transdroid/core/gui/search/SearchHistoryProvider.java

@ -24,6 +24,7 @@ import org.transdroid.BuildConfig;
/** /**
* Provides search suggestions by simply returning previous user entries. * Provides search suggestions by simply returning previous user entries.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class SearchHistoryProvider extends SearchRecentSuggestionsProvider { public class SearchHistoryProvider extends SearchRecentSuggestionsProvider {

11
app/src/main/java/org/transdroid/core/gui/search/SearchResultView.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 org.transdroid.R;
import org.transdroid.core.app.search.SearchResult;
import android.content.Context; import android.content.Context;
import android.text.format.DateUtils; import android.text.format.DateUtils;
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.core.app.search.SearchResult;
/** /**
* View that represents a {@link SearchResult} object from an in-app search * View that represents a {@link SearchResult} object from an in-app search
*
* @author Eric Kok * @author Eric Kok
*/ */
@EViewGroup(resName = "list_item_searchresult") @EViewGroup(resName = "list_item_searchresult")

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

@ -29,18 +29,19 @@ import java.util.List;
/** /**
* Adapter that contains a list of {@link SearchResult}s. * Adapter that contains a list of {@link SearchResult}s.
*
* @author Eric Kok * @author Eric Kok
*/ */
@EBean @EBean
public class SearchResultsAdapter extends BaseAdapter { public class SearchResultsAdapter extends BaseAdapter {
private List<SearchResult> results = null;
@RootContext @RootContext
protected Context context; protected Context context;
private List<SearchResult> results = null;
/** /**
* Allows updating the search results, replacing the old data * Allows updating the search results, replacing the old data
*
* @param results 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) {

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

@ -16,11 +16,9 @@
*/ */
package org.transdroid.core.gui.search; package org.transdroid.core.gui.search;
import android.app.Fragment;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import androidx.appcompat.app.AppCompatActivity;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.Menu; import android.view.Menu;
@ -32,6 +30,9 @@ import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import com.nispok.snackbar.Snackbar; import com.nispok.snackbar.Snackbar;
import com.nispok.snackbar.SnackbarManager; import com.nispok.snackbar.SnackbarManager;
@ -48,7 +49,6 @@ import org.transdroid.core.app.search.SearchHelper;
import org.transdroid.core.app.search.SearchHelper.SearchSortOrder; import org.transdroid.core.app.search.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.SystemSettings_;
import org.transdroid.core.gui.TorrentsActivity_; 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;
@ -58,6 +58,7 @@ import java.util.List;
/** /**
* 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(R.layout.fragment_searchresults) @EFragment(R.layout.fragment_searchresults)
@ -79,74 +80,6 @@ public class SearchResultsFragment extends Fragment {
protected TextView emptyText; protected TextView emptyText;
@ViewById @ViewById
protected ProgressBar loadingProgress; protected ProgressBar loadingProgress;
@AfterViews
protected void init() {
// On large screens where this fragment is shown next to the sites list; we show a continues grey vertical line
// to separate the lists visually
if (!NavigationHelper_.getInstance_(getActivity()).isSmallScreen()) {
resultsList.setBackgroundResource(R.drawable.details_list_background);
}
// Set up the list adapter, which allows multi-select
resultsList.setAdapter(resultsAdapter);
resultsList.setMultiChoiceModeListener(onItemsSelected);
if (results != null) {
showResults();
}
}
public void startSearch(String query, SearchSite site, SearchSortOrder sortBy) {
loadingProgress.setVisibility(View.VISIBLE);
resultsList.setVisibility(View.GONE);
emptyText.setVisibility(View.GONE);
performSearch(query, site, sortBy);
}
@Background
protected void performSearch(String query, SearchSite site, SearchSortOrder sortBy) {
results = searchHelper.search(query, site, sortBy);
resultsSource = site.isPrivate() ? site.getKey() : null;
showResults();
}
@UiThread
protected void showResults() {
loadingProgress.setVisibility(View.GONE);
if (results == null || results.size() == 0) {
resultsList.setVisibility(View.GONE);
emptyText.setVisibility(View.VISIBLE);
return;
}
resultsAdapter.update(results);
resultsList.setVisibility(View.VISIBLE);
emptyText.setVisibility(View.GONE);
}
public void clearResults() {
loadingProgress.setVisibility(View.GONE);
resultsList.setVisibility(View.GONE);
emptyText.setVisibility(View.VISIBLE);
}
@ItemClick(R.id.searchresults_list)
protected void onItemClicked(SearchResult item) {
if (item.getTorrentUrl() == null) {
SnackbarManager.show(Snackbar.with(getActivity()).text(R.string.error_notorrentfile).colorResource(R.color.red));
return;
}
// Don't broadcast this intent; we can safely assume this is intended for Transdroid only
Intent i = TorrentsActivity_.intent(getActivity()).get();
i.setData(Uri.parse(item.getTorrentUrl()));
i.putExtra("TORRENT_TITLE", item.getName());
if (resultsSource != null) {
i.putExtra("PRIVATE_SOURCE", resultsSource);
}
startActivity(i);
}
private MultiChoiceModeListener onItemsSelected = new MultiChoiceModeListener() { private MultiChoiceModeListener onItemsSelected = new MultiChoiceModeListener() {
SelectionManagerMode selectionManagerMode; SelectionManagerMode selectionManagerMode;
@ -169,7 +102,7 @@ public class SearchResultsFragment extends Fragment {
public boolean onActionItemClicked(ActionMode mode, MenuItem item) { public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// Get checked torrents // Get checked torrents
List<SearchResult> checked = new ArrayList<SearchResult>(); List<SearchResult> checked = new ArrayList<>();
for (int i = 0; i < resultsList.getCheckedItemPositions().size(); i++) { for (int i = 0; i < resultsList.getCheckedItemPositions().size(); i++) {
if (resultsList.getCheckedItemPositions().valueAt(i)) { if (resultsList.getCheckedItemPositions().valueAt(i)) {
checked.add(resultsAdapter.getItem(resultsList.getCheckedItemPositions().keyAt(i))); checked.add(resultsAdapter.getItem(resultsList.getCheckedItemPositions().keyAt(i)));
@ -224,4 +157,71 @@ public class SearchResultsFragment extends Fragment {
}; };
@AfterViews
protected void init() {
// On large screens where this fragment is shown next to the sites list; we show a continues grey vertical line
// to separate the lists visually
if (!NavigationHelper_.getInstance_(getActivity()).isSmallScreen()) {
resultsList.setBackgroundResource(R.drawable.details_list_background);
}
// Set up the list adapter, which allows multi-select
resultsList.setAdapter(resultsAdapter);
resultsList.setMultiChoiceModeListener(onItemsSelected);
if (results != null) {
showResults();
}
}
public void startSearch(String query, SearchSite site, SearchSortOrder sortBy) {
loadingProgress.setVisibility(View.VISIBLE);
resultsList.setVisibility(View.GONE);
emptyText.setVisibility(View.GONE);
performSearch(query, site, sortBy);
}
@Background
protected void performSearch(String query, SearchSite site, SearchSortOrder sortBy) {
results = searchHelper.search(query, site, sortBy);
resultsSource = site.isPrivate() ? site.getKey() : null;
showResults();
}
@UiThread
protected void showResults() {
loadingProgress.setVisibility(View.GONE);
if (results == null || results.size() == 0) {
resultsList.setVisibility(View.GONE);
emptyText.setVisibility(View.VISIBLE);
return;
}
resultsAdapter.update(results);
resultsList.setVisibility(View.VISIBLE);
emptyText.setVisibility(View.GONE);
}
public void clearResults() {
loadingProgress.setVisibility(View.GONE);
resultsList.setVisibility(View.GONE);
emptyText.setVisibility(View.VISIBLE);
}
@ItemClick(R.id.searchresults_list)
protected void onItemClicked(SearchResult item) {
if (item.getTorrentUrl() == null) {
SnackbarManager.show(Snackbar.with(getActivity()).text(R.string.error_notorrentfile).colorResource(R.color.red));
return;
}
// Don't broadcast this intent; we can safely assume this is intended for Transdroid only
Intent i = TorrentsActivity_.intent(getActivity()).get();
i.setData(Uri.parse(item.getTorrentUrl()));
i.putExtra("TORRENT_TITLE", item.getName());
if (resultsSource != null) {
i.putExtra("PRIVATE_SOURCE", resultsSource);
}
startActivity(i);
}
} }

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

@ -22,14 +22,16 @@ public interface SearchSetting extends SimpleListItem {
/** /**
* Should return a unique key for this search setting, so that it can be compared (using equals()) to other settings. * Should return a unique key for this search setting, so that it can be compared (using equals()) to other settings.
*
* @return A unique string identifying this search setting * @return A unique string identifying this search setting
*/ */
public String getKey(); 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
*/ */
public String getBaseUrl(); String getBaseUrl();
} }

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

@ -26,6 +26,7 @@ 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(R.layout.actionbar_searchsite) @EViewGroup(R.layout.actionbar_searchsite)

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

@ -27,6 +27,7 @@ import java.util.List;
/** /**
* 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. * 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.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class SearchSettingsDropDownAdapter extends FilterListItemAdapter { public class SearchSettingsDropDownAdapter extends FilterListItemAdapter {
@ -42,7 +43,7 @@ public class SearchSettingsDropDownAdapter extends FilterListItemAdapter {
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
// This returns the item to show in the action bar spinner // This returns the item to show in the action bar spinner
SearchSettingSelectionView filterItemView; SearchSettingSelectionView filterItemView;
if (convertView == null || !(convertView instanceof SearchSettingSelectionView)) { if (!(convertView instanceof SearchSettingSelectionView)) {
filterItemView = SearchSettingSelectionView_.build(context); filterItemView = SearchSettingSelectionView_.build(context);
} else { } else {
filterItemView = (SearchSettingSelectionView) convertView; filterItemView = (SearchSettingSelectionView) convertView;

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

@ -31,6 +31,7 @@ import org.transdroid.core.gui.navigation.NavigationHelper;
/** /**
* View that represents some {@link RssfeedSetting} object and displays name as well as loads a favicon for the feed's site and can load how many new * View that represents some {@link RssfeedSetting} object and displays name as well as loads a favicon for the feed's site and can load how many new
* items are available. * items are available.
*
* @author Eric Kok * @author Eric Kok
*/ */
@EViewGroup(R.layout.list_item_searchsite) @EViewGroup(R.layout.list_item_searchsite)

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

@ -30,18 +30,19 @@ import java.util.List;
/** /**
* 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}.
*
* @author Eric Kok * @author Eric Kok
*/ */
@EBean @EBean
public class SearchSitesAdapter extends BaseAdapter { public class SearchSitesAdapter extends BaseAdapter {
private List<SearchSetting> sites = null;
@RootContext @RootContext
protected Context context; protected Context context;
private List<SearchSetting> sites = null;
/** /**
* Allows updating the full internal list of sites at once, replacing the old list * Allows updating the full internal list of sites at once, replacing the old list
*
* @param sites The new list of search sites, either in-app or web search settings * @param sites The new list of search sites, either in-app or web search settings
*/ */
public void update(List<SearchSetting> sites) { public void update(List<SearchSetting> sites) {

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

@ -21,6 +21,7 @@ import android.content.Intent;
/** /**
* Used to clean up text as received from a generic ACTION_SEND intent. This class is highly custom-based for known * Used to clean up text as received from a generic ACTION_SEND intent. This class is highly custom-based for known
* applications, i.e. the EXTRA_TEXT send by some known applications. * applications, i.e. the EXTRA_TEXT send by some known applications.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class SendIntentHelper { public class SendIntentHelper {
@ -36,6 +37,7 @@ public class SendIntentHelper {
/** /**
* Cleans a SEND intent text string by removing irrelevant parts, so that the remaining text can be used as search * Cleans a SEND intent text string by removing irrelevant parts, so that the remaining text can be used as search
* string. Typically deals with specific known applications such as Shazam and YouTube's SEND intents. * string. Typically deals with specific known applications such as Shazam and YouTube's SEND intents.
*
* @param intent The original SEND intent that was received * @param intent The original SEND intent that was received
* @return A cleaned string to be used as search query * @return A cleaned string to be used as search query
*/ */

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

@ -19,7 +19,6 @@ package org.transdroid.core.gui.search;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.text.InputType;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -36,6 +35,7 @@ public class UrlEntryDialog {
/** /**
* Opens a dialog that allows entry of a single URL string, which (on confirmation) will be supplied to the calling activity's {@link * Opens a dialog that allows entry of a single URL string, which (on confirmation) will be supplied to the calling 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
*/ */
public static void show(final TorrentsActivity activity) { public static void show(final TorrentsActivity activity) {
@ -46,10 +46,11 @@ public class UrlEntryDialog {
CharSequence content = clipboard.getPrimaryClip().getItemAt(0).coerceToText(activity); CharSequence content = clipboard.getPrimaryClip().getItemAt(0).coerceToText(activity);
urlEdit.setText(content); urlEdit.setText(content);
} }
new MaterialDialog.Builder(activity).customView(inputLayout, false).positiveText(android.R.string.ok).negativeText(android.R.string.cancel) new MaterialDialog.Builder(activity)
.callback(new MaterialDialog.ButtonCallback() { .customView(inputLayout, false)
@Override .positiveText(android.R.string.ok)
public void onPositive(MaterialDialog dialog) { .negativeText(android.R.string.cancel)
.onPositive((dialog, which) -> {
String url = urlEdit.getText().toString(); String url = urlEdit.getText().toString();
Uri uri = Uri.parse(url); Uri uri = Uri.parse(url);
if (!TextUtils.isEmpty(url)) { if (!TextUtils.isEmpty(url)) {
@ -60,7 +61,6 @@ public class UrlEntryDialog {
activity.addTorrentByUrl(url, title); activity.addTorrentByUrl(url, title);
} }
} }
}
}).show(); }).show();
} }

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

@ -16,15 +16,16 @@
*/ */
package org.transdroid.core.gui.settings; package org.transdroid.core.gui.settings;
import org.transdroid.R;
import org.transdroid.core.gui.navigation.DialogHelper;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import org.transdroid.R;
import org.transdroid.core.gui.navigation.DialogHelper;
/** /**
* Fragment that shows info about the application developer and used open source libraries. * Fragment that shows info about the application developer and used open source libraries.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class AboutDialog implements DialogHelper.DialogSpecification { public class AboutDialog implements DialogHelper.DialogSpecification {

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

@ -16,15 +16,16 @@
*/ */
package org.transdroid.core.gui.settings; package org.transdroid.core.gui.settings;
import org.transdroid.R;
import org.transdroid.core.gui.navigation.DialogHelper;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import org.transdroid.R;
import org.transdroid.core.gui.navigation.DialogHelper;
/** /**
* Fragment that shows recent app changes. * Fragment that shows recent app changes.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class ChangelogDialog implements DialogHelper.DialogSpecification { public class ChangelogDialog implements DialogHelper.DialogSpecification {

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

@ -51,6 +51,25 @@ public class HelpSettingsActivity extends PreferenceCompatActivity {
protected ErrorLogSender errorLogSender; protected ErrorLogSender errorLogSender;
@Bean @Bean
protected SettingsPersistence settingsPersistence; protected SettingsPersistence settingsPersistence;
private OnPreferenceClickListener onSendLogClick = new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
errorLogSender.collectAndSendLog(HelpSettingsActivity.this, applicationSettings.getLastUsedServer());
return true;
}
};
private OnPreferenceClickListener onInstallHelpClick = preference -> {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(INSTALLHELP_URI)));
return true;
};
private OnPreferenceClickListener onChangeLogClick = preference -> {
showDialog(DIALOG_CHANGELOG);
return true;
};
private OnPreferenceClickListener onAboutClick = preference -> {
showDialog(DIALOG_ABOUT);
return true;
};
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -75,40 +94,6 @@ public class HelpSettingsActivity extends PreferenceCompatActivity {
MainSettingsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start(); MainSettingsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start();
} }
private OnPreferenceClickListener onSendLogClick = new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
errorLogSender.collectAndSendLog(HelpSettingsActivity.this, applicationSettings.getLastUsedServer());
return true;
}
};
private OnPreferenceClickListener onInstallHelpClick = new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(INSTALLHELP_URI)));
return true;
}
};
private OnPreferenceClickListener onChangeLogClick = new OnPreferenceClickListener() {
@SuppressWarnings("deprecation")
@Override
public boolean onPreferenceClick(Preference preference) {
showDialog(DIALOG_CHANGELOG);
return true;
}
};
private OnPreferenceClickListener onAboutClick = new OnPreferenceClickListener() {
@SuppressWarnings("deprecation")
@Override
public boolean onPreferenceClick(Preference preference) {
showDialog(DIALOG_ABOUT);
return true;
}
};
protected Dialog onCreateDialog(int id) { protected Dialog onCreateDialog(int id) {
switch (id) { switch (id) {
case DIALOG_CHANGELOG: case DIALOG_CHANGELOG:

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

@ -40,6 +40,7 @@ import java.util.Map;
* and then call initXPreference for each contained preference. {@link #onPreferencesChanged()} can be overridden to * and then call initXPreference for each contained preference. {@link #onPreferencesChanged()} can be overridden to
* react to preference changes, e.g. when field availability should be updated (and where preference dependency isn't * react to preference changes, e.g. when field availability should be updated (and where preference dependency isn't
* enough). * enough).
*
* @author Eric Kok * @author Eric Kok
*/ */
@EActivity @EActivity
@ -50,10 +51,15 @@ public abstract class KeyBoundPreferencesActivity extends PreferenceCompatActivi
private SharedPreferences sharedPrefs; private SharedPreferences sharedPrefs;
private Map<String, String> originalSummaries = new HashMap<>(); private Map<String, String> originalSummaries = new HashMap<>();
private OnSharedPreferenceChangeListener onPreferenceChangeListener = (sharedPreferences, key) -> {
showValueOnSummary(key);
onPreferencesChanged();
};
/** /**
* Should be called during the activity {@link #onCreate(android.os.Bundle)} (but after super.onCreate(Bundle)) to * Should be called during the activity {@link #onCreate(android.os.Bundle)} (but after super.onCreate(Bundle)) to
* load the preferences for this screen from an XML resource. * load the preferences for this screen from an XML resource.
*
* @param preferencesResId The XML resource to read preferences from, which may contain embedded * @param preferencesResId The XML resource to read preferences from, which may contain embedded
* {@link PreferenceScreen} objects * {@link PreferenceScreen} objects
* @param currentMaxKey The value of what is currently the last defined settings object, or -1 of no settings were * @param currentMaxKey The value of what is currently the last defined settings object, or -1 of no settings were
@ -86,14 +92,6 @@ public abstract class KeyBoundPreferencesActivity extends PreferenceCompatActivi
onPreferenceChangeListener); onPreferenceChangeListener);
} }
private OnSharedPreferenceChangeListener onPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
showValueOnSummary(key);
onPreferencesChanged();
}
};
/** /**
* Key-bound preference activities may override this method if they want to react to preference changes. * Key-bound preference activities may override this method if they want to react to preference changes.
*/ */
@ -103,6 +101,7 @@ public abstract class KeyBoundPreferencesActivity extends PreferenceCompatActivi
/** /**
* Updates a preference that allows for text entry via a dialog. This is used for both string and integer values. No * Updates a preference that allows for text entry via a dialog. This is used for both string and integer values. No
* default value will be shown. * default value will be shown.
*
* @param baseName The base name of the stored preference, e.g. item_name, which will then actually be stored under * @param baseName The base name of the stored preference, e.g. item_name, which will then actually be stored under
* item_name_[key] * item_name_[key]
* @return The concrete {@link EditTextPreference} that is bound to this preference * @return The concrete {@link EditTextPreference} that is bound to this preference
@ -113,6 +112,7 @@ public abstract class KeyBoundPreferencesActivity extends PreferenceCompatActivi
/** /**
* Updates a preference that allows for text entry via a dialog. This is used for both string and integer values. * Updates a preference that allows for text entry via a dialog. This is used for both string and integer values.
*
* @param baseName The base name of the stored preference, e.g. item_name, which will then actually be stored under * @param baseName The base name of the stored preference, e.g. item_name, which will then actually be stored under
* item_name_[key] * item_name_[key]
* @param defValue The default value for this preference, as shown when no value was yet stored * @param defValue The default value for this preference, as shown when no value was yet stored
@ -125,6 +125,7 @@ public abstract class KeyBoundPreferencesActivity extends PreferenceCompatActivi
/** /**
* Updates a preference (including dependency) that allows for text entry via a dialog. This is used for both string * Updates a preference (including dependency) that allows for text entry via a dialog. This is used for both string
* and integer values. * and integer values.
*
* @param baseName The base name of the stored preference, e.g. item_name, which will then actually be stored under * @param baseName The base name of the stored preference, e.g. item_name, which will then actually be stored under
* item_name_[key] * item_name_[key]
* @param defValue The default value for this preference, as shown when no value was yet stored * @param defValue The default value for this preference, as shown when no value was yet stored
@ -146,6 +147,7 @@ public abstract class KeyBoundPreferencesActivity extends PreferenceCompatActivi
/** /**
* Updates a preference that simply shows a check box. No default value will be shown. * Updates a preference that simply shows a check box. No default value will be shown.
*
* @param baseName The base name of the stored preference, e.g. item_name, which will then actually be stored under * @param baseName The base name of the stored preference, e.g. item_name, which will then actually be stored under
* item_name_[key] * item_name_[key]
* @return The concrete {@link CheckBoxPreference} that is bound to this preference * @return The concrete {@link CheckBoxPreference} that is bound to this preference
@ -156,6 +158,7 @@ public abstract class KeyBoundPreferencesActivity extends PreferenceCompatActivi
/** /**
* Updates a preference that simply shows a check box. * Updates a preference that simply shows a check box.
*
* @param baseName The base name of the stored preference, e.g. item_name, which will then actually be stored under * @param baseName The base name of the stored preference, e.g. item_name, which will then actually be stored under
* item_name_[key] * item_name_[key]
* @param defValue The default value for this preference, as shown when no value was yet stored * @param defValue The default value for this preference, as shown when no value was yet stored
@ -167,6 +170,7 @@ public abstract class KeyBoundPreferencesActivity extends PreferenceCompatActivi
/** /**
* Updates a preference (including dependency) that simply shows a check box. * Updates a preference (including dependency) that simply shows a check box.
*
* @param baseName The base name of the stored preference, e.g. item_name, which will then actually be stored under * @param baseName The base name of the stored preference, e.g. item_name, which will then actually be stored under
* item_name_[key] * item_name_[key]
* @param defValue The default value for this preference, as shown when no value was yet stored * @param defValue The default value for this preference, as shown when no value was yet stored
@ -185,6 +189,7 @@ public abstract class KeyBoundPreferencesActivity extends PreferenceCompatActivi
/** /**
* Updates a preference that allows picking an item from a list. No default value will be shown. * Updates a preference that allows picking an item from a list. No default value will be shown.
*
* @param baseName The base name of the stored preference, e.g. item_name, which will then actually be stored under * @param baseName The base name of the stored preference, e.g. item_name, which will then actually be stored under
* item_name_[key] * item_name_[key]
* @return The concrete {@link ListPreference} that is bound to this preference * @return The concrete {@link ListPreference} that is bound to this preference
@ -195,6 +200,7 @@ public abstract class KeyBoundPreferencesActivity extends PreferenceCompatActivi
/** /**
* Updates a preference that allows picking an item from a list. * Updates a preference that allows picking an item from a list.
*
* @param baseName The base name of the stored preference, e.g. item_name, which will then actually be stored under * @param baseName The base name of the stored preference, e.g. item_name, which will then actually be stored under
* item_name_[key] * item_name_[key]
* @param defValue The default value for this preference, as shown when no value was yet stored * @param defValue The default value for this preference, as shown when no value was yet stored

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

@ -19,13 +19,13 @@ package org.transdroid.core.gui.settings;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import androidx.preference.ListPreference; import androidx.preference.ListPreference;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceClickListener; import androidx.preference.Preference.OnPreferenceClickListener;
@ -55,6 +55,7 @@ import java.util.List;
/** /**
* The main activity that provides access to all application settings. It shows the configured serves, web search sites and RSS feeds along with other * The main activity that provides access to all application settings. It shows the configured serves, web search sites and RSS feeds along with other
* general settings. * general settings.
*
* @author Eric Kok * @author Eric Kok
*/ */
@EActivity @EActivity
@ -78,83 +79,44 @@ public class MainSettingsActivity extends PreferenceCompatActivity {
return true; return true;
} }
}; };
private OnPreferenceClickListener onAddWebsearch = new OnPreferenceClickListener() { private OnPreferenceClickListener onAddWebsearch = preference -> {
@Override
public boolean onPreferenceClick(Preference preference) {
WebsearchSettingsActivity_.intent(MainSettingsActivity.this).start(); WebsearchSettingsActivity_.intent(MainSettingsActivity.this).start();
return true; return true;
}
}; };
private OnPreferenceClickListener onAddRssfeed = new OnPreferenceClickListener() { private OnPreferenceClickListener onAddRssfeed = preference -> {
@Override
public boolean onPreferenceClick(Preference preference) {
RssfeedSettingsActivity_.intent(MainSettingsActivity.this).start(); RssfeedSettingsActivity_.intent(MainSettingsActivity.this).start();
return true; return true;
}
}; };
private OnPreferenceClickListener onBackgroundSettings = new OnPreferenceClickListener() { private OnPreferenceClickListener onBackgroundSettings = preference -> {
@Override
public boolean onPreferenceClick(Preference preference) {
NotificationSettingsActivity_.intent(MainSettingsActivity.this).start(); NotificationSettingsActivity_.intent(MainSettingsActivity.this).start();
return true; return true;
}
}; };
private OnPreferenceClickListener onSystemSettings = new OnPreferenceClickListener() { private OnPreferenceClickListener onSystemSettings = preference -> {
@Override
public boolean onPreferenceClick(Preference preference) {
SystemSettingsActivity_.intent(MainSettingsActivity.this).start(); SystemSettingsActivity_.intent(MainSettingsActivity.this).start();
return true; return true;
}
}; };
private OnPreferenceClickListener onHelpSettings = new OnPreferenceClickListener() { private OnPreferenceClickListener onHelpSettings = preference -> {
@Override
public boolean onPreferenceClick(Preference preference) {
HelpSettingsActivity_.intent(MainSettingsActivity.this).start(); HelpSettingsActivity_.intent(MainSettingsActivity.this).start();
return true; return true;
}
}; };
private OnPreferenceClickListener onDonate = new OnPreferenceClickListener() { private OnPreferenceClickListener onDonate = preference -> {
@Override
public boolean onPreferenceClick(Preference preference) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.donate_url)))); startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.donate_url))));
return true; return true;
}
};
private OnServerClickedListener onServerClicked = new OnServerClickedListener() {
@Override
public void onServerClicked(ServerSetting serverSetting) {
ServerSettingsActivity_.intent(MainSettingsActivity.this).key(serverSetting.getOrder()).start();
}
}; };
private OnSeedboxClickedListener onSeedboxClicked = new OnSeedboxClickedListener() { private OnServerClickedListener onServerClicked = serverSetting -> ServerSettingsActivity_.intent(MainSettingsActivity.this).key(serverSetting.getOrder()).start();
@Override private OnSeedboxClickedListener onSeedboxClicked = (serverSetting, provider, seedboxOffset) -> {
public void onSeedboxClicked(ServerSetting serverSetting, SeedboxProvider provider, int seedboxOffset) {
// NOTE: The seedboxOffset is the seedbox type-unique order that we need to supply uin the Extras bundle to // NOTE: The seedboxOffset is the seedbox type-unique order that we need to supply uin the Extras bundle to
// edit this specific seedbox // edit this specific seedbox
startActivity(provider.getSettings().getSettingsActivityIntent(MainSettingsActivity.this).putExtra("key", seedboxOffset)); startActivity(provider.getSettings().getSettingsActivityIntent(MainSettingsActivity.this).putExtra("key", seedboxOffset));
}
}; };
private OnWebsearchClickedListener onWebsearchClicked = new OnWebsearchClickedListener() { private OnWebsearchClickedListener onWebsearchClicked = websearchSetting -> WebsearchSettingsActivity_.intent(MainSettingsActivity.this).key(websearchSetting.getOrder()).start();
@Override private OnRssfeedClickedListener onRssfeedClicked = rssfeedSetting -> RssfeedSettingsActivity_.intent(MainSettingsActivity.this).key(rssfeedSetting.getOrder()).start();
public void onWebsearchClicked(WebsearchSetting websearchSetting) { private OnClickListener onAddSeedbox = (dialog, which) -> {
WebsearchSettingsActivity_.intent(MainSettingsActivity.this).key(websearchSetting.getOrder()).start();
}
};
private OnRssfeedClickedListener onRssfeedClicked = new OnRssfeedClickedListener() {
@Override
public void onRssfeedClicked(RssfeedSetting rssfeedSetting) {
RssfeedSettingsActivity_.intent(MainSettingsActivity.this).key(rssfeedSetting.getOrder()).start();
}
};
private OnClickListener onAddSeedbox = new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Start the configuration activity for this specific chosen seedbox // Start the configuration activity for this specific chosen seedbox
if (which == 0) if (which == 0)
ServerSettingsActivity_.intent(MainSettingsActivity.this).start(); ServerSettingsActivity_.intent(MainSettingsActivity.this).start();
else else
startActivity(SeedboxProvider.values()[which - 1].getSettings().getSettingsActivityIntent(MainSettingsActivity.this)); startActivity(SeedboxProvider.values()[which - 1].getSettings().getSettingsActivityIntent(MainSettingsActivity.this));
}
}; };
@Override @Override
@ -228,8 +190,8 @@ public class MainSettingsActivity extends PreferenceCompatActivity {
} }
// Allow selection of the default server // Allow selection of the default server
ListPreference defaultServerPreference = (ListPreference) findPreference("header_defaultserver"); ListPreference defaultServerPreference = (ListPreference) findPreference("header_defaultserver");
defaultServerPreference.setEntries(serverNames.toArray(new String[serverNames.size()])); defaultServerPreference.setEntries(serverNames.toArray(new String[0]));
defaultServerPreference.setEntryValues(serverCodes.toArray(new String[serverCodes.size()])); defaultServerPreference.setEntryValues(serverCodes.toArray(new String[0]));
// Add existing RSS feeds // Add existing RSS feeds
if (!enableRssUi) { if (!enableRssUi) {
@ -274,8 +236,8 @@ public class MainSettingsActivity extends PreferenceCompatActivity {
siteValues.add(websearch.getKey()); siteValues.add(websearch.getKey());
} }
// Supply the Preference list names and values // Supply the Preference list names and values
setSite.setEntries(siteNames.toArray(new String[siteNames.size()])); setSite.setEntries(siteNames.toArray(new String[0]));
setSite.setEntryValues(siteValues.toArray(new String[siteValues.size()])); setSite.setEntryValues(siteValues.toArray(new String[0]));
} }
@ -287,8 +249,7 @@ public class MainSettingsActivity extends PreferenceCompatActivity {
@Override @Override
protected Dialog onCreateDialog(int id) { protected Dialog onCreateDialog(int id) {
switch (id) { if (id == DIALOG_ADDSEEDBOX) {
case DIALOG_ADDSEEDBOX:
// Open dialog to pick one of the supported seedbox providers (or a normal server) // Open dialog to pick one of the supported seedbox providers (or a normal server)
String[] seedboxes = new String[SeedboxProvider.values().length + 1]; String[] seedboxes = new String[SeedboxProvider.values().length + 1];
seedboxes[0] = getString(R.string.pref_addserver_normal); seedboxes[0] = getString(R.string.pref_addserver_normal);

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

@ -76,4 +76,4 @@ public class PreferenceCompatActivity extends AppCompatActivity implements AppCo
} }
} }
} }
} }

2
app/src/main/java/org/transdroid/core/gui/settings/RingtonePreference.java

@ -62,7 +62,7 @@ public class RingtonePreference extends Preference {
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_RINGTONE && resultCode == Activity.RESULT_OK && data != null && data.getExtras() != null) { if (requestCode == REQUEST_RINGTONE && resultCode == Activity.RESULT_OK && data != null && data.getExtras() != null) {
Uri ringtone = (Uri) data.getExtras().get(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); Uri ringtone = (Uri) data.getExtras().get(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
getSharedPreferences().edit().putString(getKey(), ringtone == null? "": ringtone.toString()).apply(); getSharedPreferences().edit().putString(getKey(), ringtone == null ? "" : ringtone.toString()).apply();
} }
} }
} }

17
app/src/main/java/org/transdroid/core/gui/settings/RssfeedPreference.java

@ -24,6 +24,7 @@ import org.transdroid.core.app.settings.RssfeedSetting;
/** /**
* Represents a {@link RssfeedSetting} in a preferences screen. * Represents a {@link RssfeedSetting} in a preferences screen.
*
* @author Eric Kok * @author Eric Kok
*/ */
public class RssfeedPreference extends Preference { public class RssfeedPreference extends Preference {
@ -35,12 +36,18 @@ public class RssfeedPreference extends Preference {
public RssfeedPreference(Context context) { public RssfeedPreference(Context context) {
super(context); super(context);
OnPreferenceClickListener onPreferenceClicked = preference -> {
if (onRssfeedClickedListener != null)
onRssfeedClickedListener.onRssfeedClicked(rssfeedSetting);
return true;
};
setOnPreferenceClickListener(onPreferenceClicked); setOnPreferenceClickListener(onPreferenceClicked);
setIconSpaceReserved(false); setIconSpaceReserved(false);
} }
/** /**
* Set the RSS feed settings object that is bound to this preference item * Set the RSS feed settings object that is bound to this preference item
*
* @param rssfeedSetting The RSS feed settings * @param rssfeedSetting The RSS feed settings
* @return Itself, for method chaining * @return Itself, for method chaining
*/ */
@ -54,6 +61,7 @@ public class RssfeedPreference extends Preference {
/** /**
* Set a listener that will be notified of click events on this preference * Set a listener that will be notified of click events on this preference
*
* @param onRssfeedClickedListener The click listener to register * @param onRssfeedClickedListener The click listener to register
* @return Itself, for method chaining * @return Itself, for method chaining
*/ */
@ -62,15 +70,6 @@ public class RssfeedPreference extends Preference {
return this; return this;
} }
private OnPreferenceClickListener onPreferenceClicked = new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
if (onRssfeedClickedListener != null)
onRssfeedClickedListener.onRssfeedClicked(rssfeedSetting);
return true;
}
};
public interface OnRssfeedClickedListener { public interface OnRssfeedClickedListener {
void onRssfeedClicked(RssfeedSetting rssfeedSetting); void onRssfeedClicked(RssfeedSetting rssfeedSetting);
} }

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

Loading…
Cancel
Save