Merge branch 'develop' into folders
This commit is contained in:
commit
f76d3ad09e
|
@ -45,7 +45,7 @@ workflows:
|
|||
destination: app-play-debug.apk
|
||||
- run:
|
||||
name: Execute debug unit tests
|
||||
command: ./gradlew :core:testPlayDebugUnitTest -PdisablePreDex
|
||||
command: ./gradlew testPlayDebugUnitTest -PdisablePreDex
|
||||
- build:
|
||||
name: Build release
|
||||
build-steps:
|
||||
|
@ -54,19 +54,19 @@ workflows:
|
|||
command: ./gradlew assembleRelease -PdisablePreDex
|
||||
- run:
|
||||
name: Execute release unit tests
|
||||
command: ./gradlew :core:testPlayReleaseUnitTest -PdisablePreDex
|
||||
command: ./gradlew testPlayReleaseUnitTest -PdisablePreDex
|
||||
- build:
|
||||
name: Build integration tests
|
||||
build-steps:
|
||||
- run:
|
||||
name: Build integration tests
|
||||
command: ./gradlew :app:assemblePlayDebugAndroidTest -PdisablePreDex
|
||||
command: ./gradlew assemblePlayDebugAndroidTest -PdisablePreDex
|
||||
- build:
|
||||
name: Build free
|
||||
build-steps:
|
||||
- run:
|
||||
name: Build free (for F-Droid)
|
||||
command: ./gradlew assembleFreeRelease -PdisablePreDex -PfreeBuild
|
||||
command: ./gradlew assembleFreeRelease -PdisablePreDex
|
||||
|
||||
static-analysis:
|
||||
jobs:
|
||||
|
@ -85,19 +85,8 @@ workflows:
|
|||
curl -s -L https://github.com/yangziwen/diff-checkstyle/releases/download/0.0.4/diff-checkstyle.jar > diff-checkstyle.jar
|
||||
java -Dconfig_loc=config/checkstyle -jar diff-checkstyle.jar -c config/checkstyle/checkstyle-new-code.xml --git-dir . --base-rev $branchBaseCommit
|
||||
- build:
|
||||
name: Lint app
|
||||
name: Lint
|
||||
build-steps:
|
||||
- run:
|
||||
name: Lint app
|
||||
command: ./gradlew app:lintPlayRelease
|
||||
- run:
|
||||
name: Lint core
|
||||
command: ./gradlew core:lintPlayRelease
|
||||
- store_artifacts:
|
||||
name: Uploading app lint reports
|
||||
path: app/build/reports/lint-results-playRelease.html
|
||||
destination: lint-results-app.html
|
||||
- store_artifacts:
|
||||
name: Uploading core lint reports
|
||||
path: core/build/reports/lint-results-playRelease.html
|
||||
destination: lint-results-core.html
|
||||
name: Lint
|
||||
command: ./gradlew lintPlayRelease
|
||||
|
|
|
@ -4,6 +4,7 @@ host = https://www.transifex.com
|
|||
[antennapod.core-values]
|
||||
source_file = core/src/main/res/values/strings.xml
|
||||
source_lang = en
|
||||
trans.ar = core/src/main/res/values-ar/strings.xml
|
||||
trans.br = core/src/main/res/values-br/strings.xml
|
||||
trans.ca = core/src/main/res/values-ca/strings.xml
|
||||
trans.cs_CZ = core/src/main/res/values-cs/strings.xml
|
||||
|
@ -29,6 +30,7 @@ trans.pl_PL = core/src/main/res/values-pl/strings.xml
|
|||
trans.pt = core/src/main/res/values-pt/strings.xml
|
||||
trans.pt_BR = core/src/main/res/values-pt-rBR/strings.xml
|
||||
trans.ru_RU = core/src/main/res/values-ru/strings.xml
|
||||
trans.sk = core/src/main/res/values-sk/strings.xml
|
||||
trans.sv_SE = core/src/main/res/values-sv/strings.xml
|
||||
trans.tr = core/src/main/res/values-tr/strings.xml
|
||||
trans.uk_UA = core/src/main/res/values-uk/strings.xml
|
||||
|
@ -58,7 +60,6 @@ trans.he_IL = app/src/main/play/listings/iw-IL/full-description.txt
|
|||
trans.hu = app/src/main/play/listings/hu-HU/full-description.txt
|
||||
trans.id = app/src/main/play/listings/id/full-description.txt
|
||||
trans.it_IT = app/src/main/play/listings/it-IT/full-description.txt
|
||||
trans.iw = app/src/main/play/listings/iw-IL/full-description.txt
|
||||
trans.ja = app/src/main/play/listings/ja-JP/full-description.txt
|
||||
trans.ko = app/src/main/play/listings/ko-KR/full-description.txt
|
||||
trans.lt = app/src/main/play/listings/lt/full-description.txt
|
||||
|
@ -68,6 +69,7 @@ trans.pt_BR = app/src/main/play/listings/pt-BR/full-description.txt
|
|||
trans.pt = app/src/main/play/listings/pt-PT/full-description.txt
|
||||
trans.ro_RO = app/src/main/play/listings/ro/full-description.txt
|
||||
trans.ru_RU = app/src/main/play/listings/ru-RU/full-description.txt
|
||||
trans.sk = app/src/main/play/listings/sk/full-description.txt
|
||||
trans.sl_SI = app/src/main/play/listings/sl/full-description.txt
|
||||
trans.sv_SE = app/src/main/play/listings/sv-SE/full-description.txt
|
||||
trans.tr = app/src/main/play/listings/tr-TR/full-description.txt
|
||||
|
@ -98,7 +100,6 @@ trans.he_IL = app/src/main/play/listings/iw-IL/short-description.txt
|
|||
trans.hu = app/src/main/play/listings/hu-HU/short-description.txt
|
||||
trans.id = app/src/main/play/listings/id/short-description.txt
|
||||
trans.it_IT = app/src/main/play/listings/it-IT/short-description.txt
|
||||
trans.iw = app/src/main/play/listings/iw-IL/short-description.txt
|
||||
trans.ja = app/src/main/play/listings/ja-JP/short-description.txt
|
||||
trans.ko = app/src/main/play/listings/ko-KR/short-description.txt
|
||||
trans.lt = app/src/main/play/listings/lt/short-description.txt
|
||||
|
@ -108,6 +109,7 @@ trans.pt_BR = app/src/main/play/listings/pt-BR/short-description.txt
|
|||
trans.pt = app/src/main/play/listings/pt-PT/short-description.txt
|
||||
trans.ro_RO = app/src/main/play/listings/ro/short-description.txt
|
||||
trans.ru_RU = app/src/main/play/listings/ru-RU/short-description.txt
|
||||
trans.sk = app/src/main/play/listings/sk/short-description.txt
|
||||
trans.sl_SI = app/src/main/play/listings/sl/short-description.txt
|
||||
trans.sv_SE = app/src/main/play/listings/sv-SE/short-description.txt
|
||||
trans.tr = app/src/main/play/listings/tr-TR/short-description.txt
|
||||
|
|
471
CHANGELOG.md
471
CHANGELOG.md
|
@ -1,471 +0,0 @@
|
|||
Change Log
|
||||
==========
|
||||
|
||||
Version 1.8.1
|
||||
-------------
|
||||
* Enabled picture-in-picture for video podcasts by default (by @ByteHamster)
|
||||
* Fixed podcast discovery not showing local trends (by @tonytamsf)
|
||||
* Various bug fixes and improvements (by @ByteHamster)
|
||||
|
||||
Version 1.8.0
|
||||
-------------
|
||||
* Added per-feed playback speed setting (by @spacecowboy)
|
||||
* Support sorting in Podcast screen (by @orionlee)
|
||||
* Option to show stream button rather than download in lists (by @dsmith47)
|
||||
* Option to replace Episode cover with Podcast cover (by @xgouchet)
|
||||
* Transparent widget (by @M-arcel)
|
||||
* User interface tweaks (by @ByteHamster)
|
||||
* Tons of bug fixes and improvements
|
||||
|
||||
Version 1.7.3
|
||||
-------------
|
||||
* Display episode image on widget (by @brad)
|
||||
* Added checkbox to keep queue sorted (by @damoasda)
|
||||
* New UI for "Add podcast" screen (by @ByteHamster)
|
||||
* Added batch editing to the queue (by @ByteHamster)
|
||||
* Added option to adapt remaining time to playback speed (by @CedricCabessa)
|
||||
* Removed broken Flattr integration (by @ByteHamster)
|
||||
* Added filter to "All episodes" list (by @jhunnius)
|
||||
* Tons of bug fixes and performance improvements
|
||||
|
||||
Version 1.7.2
|
||||
-------------
|
||||
* Added configurable behavior of the back button
|
||||
* Added delete option to episode's context menu
|
||||
* New UI for batch edit feature
|
||||
* Set number of columns in subscription list
|
||||
* Lots of bug fixes
|
||||
|
||||
Version 1.7.1
|
||||
-------------
|
||||
|
||||
* Fix for database corruption
|
||||
|
||||
Version 1.7.0
|
||||
-------------
|
||||
|
||||
* NEW ExoPlayer (experimental)
|
||||
* Fix for Bluetooth Forward (Oreo)
|
||||
* Preference redesign + search
|
||||
* Notification improvements
|
||||
* Different screens for feed info and settings
|
||||
* Sort Queue with Random or Smart Shuffle
|
||||
* True Black Theme for AMOLED
|
||||
* Improvements to feed parsing
|
||||
* Fix for app being killed by Android Oreo
|
||||
|
||||
Version 1.6.5
|
||||
-------------
|
||||
|
||||
* Fix database corruption
|
||||
* Improvements to Feed parsing
|
||||
|
||||
Version 1.6.4
|
||||
-------------
|
||||
|
||||
* Fixes issues on Android Oreo
|
||||
* Avoids duplicate chapters
|
||||
* Experimental: Database import & export
|
||||
|
||||
Version 1.6.3
|
||||
-------------
|
||||
|
||||
* New features:
|
||||
* Support for Android Auto
|
||||
* Sort feeds by number of played episodes
|
||||
* Statistics modes
|
||||
* Setting: Enqueue downloaded
|
||||
* Launch screen
|
||||
* Improvements
|
||||
* Chapter duration
|
||||
* Feed title in deletion confirmation
|
||||
* Fixes:
|
||||
* Episodes refresh spinner
|
||||
* Publication date parsing
|
||||
* Unknown mime type
|
||||
|
||||
Version 1.6.2
|
||||
-------------
|
||||
|
||||
* New features:
|
||||
* Integration of fyyd Podcast Search Engine
|
||||
* Export subscriptions as HTML
|
||||
* Rename feeds
|
||||
* Auto-enable sleep timer
|
||||
* "has media" filter
|
||||
* Force gpodder full sync
|
||||
* Improvements:
|
||||
* Better support for Atom feeds, e.g. summary tag
|
||||
* Confirmation dialog on mark all as seen
|
||||
* Number of downloaded episodes in subscription counter
|
||||
* Gpodder sync error optional
|
||||
* Search results
|
||||
* MRSS support
|
||||
* Sanitize HTML from Atom feed
|
||||
* Fixes:
|
||||
* Reset sleep timer on shake to current waiting time
|
||||
* Cast dialog image
|
||||
* Mini player not showing up
|
||||
* Audio player cover fragment
|
||||
* Prevent out of memory and casting crashes
|
||||
|
||||
Version 1.6.0
|
||||
-------------
|
||||
* New features:
|
||||
* Experimental Chromecast support
|
||||
* Subscription overview
|
||||
* Proxy support
|
||||
* Statistics
|
||||
* Manual gpodder.net sync
|
||||
* Fixes:
|
||||
* Audioplayer controls
|
||||
* Audio ducking
|
||||
* Video control fade-out
|
||||
* External media controls
|
||||
* Feed parsing
|
||||
|
||||
Version 1.5.0
|
||||
-------------
|
||||
* Exclude episodes from auto download by keyword
|
||||
* Configure feeds to prevent them from refreshing automatically
|
||||
* Improved audio player
|
||||
* Improved UI
|
||||
* Bug fixes
|
||||
|
||||
Version 1.4.1
|
||||
-------------
|
||||
* Performance improvements
|
||||
* Hardware buttons now ff and rewind instead of skipping
|
||||
* Option to have forward button skip
|
||||
* Option to send crash reports directly to developers
|
||||
* Highlight currently playing episode
|
||||
* Widget improvements
|
||||
|
||||
Version 1.4.0.12
|
||||
----------------
|
||||
* Fix for crash on Huawei devices (media buttons may not work)
|
||||
|
||||
Version 1.4
|
||||
-----------
|
||||
* BLUETOOTH PERMISSION: Needed to be able to resume playback when a Bluetooth device reconnects with your phone
|
||||
* VIBRATE PERMISSION: Used optionally with the sleep timer
|
||||
* Native variable speed playback (experimental via options)
|
||||
* Improved sleep timer
|
||||
* Mark episodes as 'favorite'
|
||||
* Notification can skip episodes
|
||||
* Keep episodes when skipping them
|
||||
* Episode art on lock screen
|
||||
* Flexible episode cleanup
|
||||
* Rewind after pause
|
||||
* Usability improvements
|
||||
* Bug fixes
|
||||
|
||||
Version 1.3
|
||||
-----------
|
||||
* Bulk actions on feed episodes (download, queue, delete)
|
||||
* Reduced space used by images
|
||||
* Automatic refresh at a certain time of day
|
||||
* Customizable indicators and sorting for feeds
|
||||
* Ability to share feeds
|
||||
* Improved auto download
|
||||
* Many fixes and usability improvements
|
||||
|
||||
Version 1.2
|
||||
-----------
|
||||
* Optionally disable swiping and dragging in the queue
|
||||
* Resume playback after phone call
|
||||
* Filter episodes in the Podcast feed
|
||||
* Hide items in the Nav drawer
|
||||
* Customize times for fast forward and rewind
|
||||
* Resolved issues with opening some OPML files
|
||||
* Various bug fixes and usability improvements
|
||||
|
||||
Version 1.1
|
||||
-----------
|
||||
* iTunes podcast integration
|
||||
* Swipe to remove items from the queue
|
||||
* Set the number of parallel downloads
|
||||
* Fix for gpodder.net on old devices
|
||||
* Fixed date problems for some feeds
|
||||
* Display improvements
|
||||
* Usability improvements
|
||||
* Several other bugfixes
|
||||
|
||||
Version 1.0
|
||||
-----------
|
||||
* The queue can now be sorted
|
||||
* Added option to delete episode after playback
|
||||
* Fixed a bug that caused chapters to be displayed multiple times
|
||||
* Several other improvements and bugfixes
|
||||
|
||||
|
||||
Version 0.9.9.6
|
||||
---------------
|
||||
* Fixed problems related to variable playback speed plugins
|
||||
* Fixed automatic feed update problems
|
||||
* Several other bugfixes and improvements
|
||||
|
||||
Version 0.9.9.5
|
||||
---------------
|
||||
* Added support for paged feeds
|
||||
* Improved user interface
|
||||
* Added Japanese and Turkish translations
|
||||
* Fixed more image loading problems
|
||||
* Other bugfixes and improvements
|
||||
|
||||
Version 0.9.9.4
|
||||
---------------
|
||||
* Added option to keep notification and lockscreen controls when playback is paused
|
||||
* Fixed a bug where episode images were not loaded correctly
|
||||
* Fixed battery usage problems
|
||||
|
||||
Version 0.9.9.3
|
||||
---------------
|
||||
* Fixed video playback problems
|
||||
* Improved image loading
|
||||
* Other bugfixes and improvements
|
||||
|
||||
Version 0.9.9.2
|
||||
---------------
|
||||
* Added support for feed discovery if a website URL is entered
|
||||
* Added support for 'next'/'previous' media keys
|
||||
* Improved sleep timer
|
||||
* Timestamps in shownotes can now be used to jump to a specific position
|
||||
* Automatic Flattring is now configurable
|
||||
* Several bugfixes and improvements
|
||||
|
||||
Version 0.9.9.1
|
||||
---------------
|
||||
* Several bugfixes and improvements
|
||||
|
||||
Version 0.9.9.0
|
||||
---------------
|
||||
* New user interface
|
||||
* Failed downloads are now resumed when restarted
|
||||
* Added support for Podlove Alternate Feeds
|
||||
* Added support for "pcast"-protocol
|
||||
* Added backup & restore functionality. This feature has to be enabled in the Android settings in order to work
|
||||
* Several bugfixes and improvements
|
||||
|
||||
Version 0.9.8.3
|
||||
---------------
|
||||
* Added support for password-protected feeds and episodes
|
||||
* Added support for more types of episode images
|
||||
* Added Hebrew translation
|
||||
* Several bugfixes and improvements
|
||||
|
||||
Version 0.9.8.2
|
||||
---------------
|
||||
* Several bugfixes and improvements
|
||||
* Added Korean translation
|
||||
|
||||
Version 0.9.8.1
|
||||
---------------
|
||||
* Added option to flattr an episode automatically after 80 percent of the episode have been played
|
||||
* Added Polish translation
|
||||
* Several bugfixes and improvements
|
||||
|
||||
Version 0.9.8.0
|
||||
---------------
|
||||
* Added access to the gpodder.net directory
|
||||
* Added ability to sync podcast subscriptions with the gpodder.net service
|
||||
* Automatic download can now be turned on or off for specific podcasts
|
||||
* Added option to pause playback when another app is playing sounds
|
||||
* Added Dutch and Hindi translation
|
||||
* Resolved a problem with automatic podcast updates
|
||||
* Resolved a problem with the buttons' visibility in the episode screen
|
||||
* Resolved a problem where episodes would be re-downloaded unnecessarily
|
||||
* Several other bugfixes and usability improvements
|
||||
|
||||
Version 0.9.7.5
|
||||
---------------
|
||||
* Reduced application startup time
|
||||
* Reduced memory usage
|
||||
* Added option to change the playback speed
|
||||
* Added Swedish translation
|
||||
* Several bugfixes and improvements
|
||||
|
||||
Version 0.9.7.4
|
||||
---------------
|
||||
* Episode cache size can now be set to unlimited
|
||||
* Removing an episode in the queue via sliding can now be undone
|
||||
* Added support for Links in MP3 chapters
|
||||
* Added Czech(Czech Republic), Azerbaijani and Portuguese translations
|
||||
* Several bugfixes and improvements
|
||||
|
||||
Version 0.9.7.3
|
||||
---------------
|
||||
* Bluetooth devices now display metadata during playback (requires AVRCP 1.3 or higher)
|
||||
* User interface improvements
|
||||
* Several bugfixes
|
||||
|
||||
Version 0.9.7.2
|
||||
---------------
|
||||
* Automatic download can now be disabled
|
||||
* Added Italian (Italy) translation
|
||||
* Several bugfixes
|
||||
|
||||
Version 0.9.7.1
|
||||
---------------
|
||||
* Added automatic download of new episodes
|
||||
* Added option to specify the number of downloaded episodes to keep on the device
|
||||
* Added support for playback of external media files
|
||||
* Several improvements and bugfixes
|
||||
* Added Catalan translation
|
||||
|
||||
Version 0.9.7
|
||||
-------------
|
||||
* Improved user interface
|
||||
* OPML files can now be imported by selecting them in a file browser
|
||||
* The queue can now be organized via drag & drop
|
||||
* Added expandable notifications (only supported on Android 4.1 and above)
|
||||
* Added Danish, French, Romanian (Romania) and Ukrainian (Ukraine) translation (thanks to all translators!)
|
||||
* Several bugfixes and minor improvements
|
||||
|
||||
Version 0.9.6.4
|
||||
---------------
|
||||
* Added Chinese translation (Thanks tupunco!)
|
||||
* Added Portuguese (Brazil) translation (Thanks mbaltar!)
|
||||
* Several bugfixes
|
||||
|
||||
Version 0.9.6.3
|
||||
---------------
|
||||
* Added the ability change the location of AntennaPod's data folder
|
||||
* Added Spanish translation (Thanks frandavid100!)
|
||||
* Solved problems with several feeds
|
||||
|
||||
Version 0.9.6.2
|
||||
---------------
|
||||
* Fixed import problems with some OPML files
|
||||
* Fixed download problems
|
||||
* AntennaPod now recognizes changes of episode information
|
||||
* Other improvements and bugfixes
|
||||
|
||||
Version 0.9.6.1
|
||||
---------------
|
||||
* Added dark theme
|
||||
* Several bugfixes and improvements
|
||||
|
||||
Version 0.9.6
|
||||
-------------
|
||||
* Added support for VorbisComment chapters
|
||||
* AntennaPod now shows items as 'in progress' when playback has started
|
||||
* Reduced memory usage
|
||||
* Added support for more feed types
|
||||
* Several bugfixes
|
||||
|
||||
|
||||
Version 0.9.5.3
|
||||
---------------
|
||||
* Fixed crash when trying to start playback on some devices
|
||||
* Fixed problems with some feeds
|
||||
* Other bugfixes and improvements
|
||||
|
||||
Version 0.9.5.2
|
||||
---------------
|
||||
* Media player now doesn't use network bandwidth anymore if not in use
|
||||
* Other improvements and bugfixes
|
||||
|
||||
Version 0.9.5.1
|
||||
---------------
|
||||
* Added playback history
|
||||
* Improved behavior of download report notifications
|
||||
* Improved support for headset controls
|
||||
* Bugfixes in the feed parser
|
||||
* Moved 'OPML import' button into the 'add feed' screen and the 'OPML export' button into the settings screen
|
||||
|
||||
Version 0.9.5
|
||||
-------------
|
||||
* Experimental support for MP3 chapters
|
||||
* New menu options for the 'new' list and the queue
|
||||
* Auto-delete feature
|
||||
* Better Download error reports
|
||||
* Several Bugfixes
|
||||
|
||||
Version 0.9.4.6
|
||||
---------------
|
||||
* Enabled support for small-screen devices
|
||||
* Disabling the sleep timer should now work again
|
||||
|
||||
Version 0.9.4.5
|
||||
---------------
|
||||
* Added Russian translation (Thanks older!)
|
||||
* Added German translation
|
||||
* Several bugfixes
|
||||
|
||||
Version 0.9.4.4
|
||||
---------------
|
||||
* Added player controls at the bottom of the main screen and the feedlist screens
|
||||
* Improved media playback
|
||||
|
||||
Version 0.9.4.3
|
||||
---------------
|
||||
* Fixed several bugs in the feed parser
|
||||
* Improved behavior of download reports
|
||||
|
||||
Version 0.9.4.2
|
||||
---------------
|
||||
* Fixed bug in the OPML importer
|
||||
* Reduced memory usage of images
|
||||
* Fixed download problems on some devices
|
||||
|
||||
Version 0.9.4.1
|
||||
---------------
|
||||
* Changed behavior of download notifications
|
||||
|
||||
Version 0.9.4
|
||||
-------------
|
||||
* Faster and more reliable downloads
|
||||
* Added lockscreen player controls for Android 4.x devices
|
||||
* Several bugfixes
|
||||
|
||||
Version 0.9.3.1
|
||||
---------------
|
||||
* Added preference to hide feed items which don't have an episode
|
||||
* Improved image size for some some screen sizes
|
||||
* Added grid view for large screens
|
||||
* Several bugfixes
|
||||
|
||||
Version 0.9.3
|
||||
-------------
|
||||
* MiroGuide integration
|
||||
* Bugfixes in the audio- and videoplayer
|
||||
* Automatically add feeds to the queue when they have been downloaded
|
||||
|
||||
Version 0.9.2
|
||||
-------------
|
||||
* Bugfixes in the user interface
|
||||
* GUID and ID attributes are now recognized by the Feedparser
|
||||
* Stability improvements when adding several feeds at the same time
|
||||
* Fixed bugs that occured when adding certain feeds
|
||||
|
||||
Version 0.9.1.1
|
||||
--------------------
|
||||
* Changed Flattr credentials
|
||||
* Improved layout of Feed information screen
|
||||
* AntennaPod is now open source! The source code is available at https://github.com/danieloeh/AntennaPod
|
||||
|
||||
Version 0.9.1
|
||||
-----------------
|
||||
* Added support for links in SimpleChapters
|
||||
* Bugfix: Current Chapter wasn't always displayed correctly
|
||||
|
||||
Version 0.9
|
||||
--------------
|
||||
|
||||
* OPML export
|
||||
* Flattr integration
|
||||
* Sleep timer
|
||||
|
||||
Version 0.8.2
|
||||
-------------
|
||||
|
||||
* Added search
|
||||
* Improved OPML import experience
|
||||
* More bugfixes
|
||||
|
||||
Version 0.8.1
|
||||
------------
|
||||
|
||||
* Added support for SimpleChapters
|
||||
* OPML import
|
|
@ -35,3 +35,24 @@ Submit a pull request
|
|||
- Please do not upgrade dependencies or build tools unless you have a good reason for it. Doing so can easily introduce bugs that are hard to track down.
|
||||
- If you plan to do a change that touches many files (10+), please ask beforehand. This usually causes merge conflicts for other developers.
|
||||
- Please follow our code style. You can use Checkstyle within Android Studio using our [coniguration file](https://github.com/AntennaPod/AntennaPod/blob/develop/config/checkstyle/checkstyle-new-code.xml).
|
||||
- Please only change the English string resources. Translations are handled on [Transifex](https://www.transifex.com/antennapod/antennapod/).
|
||||
|
||||
|
||||
Testing and Verifying
|
||||
--------------------------
|
||||
As a developer contributing to AntennaPod, we ask that you test the feature yourself manually and better yet, add unit and functional tests to any feature of bug you fix.
|
||||
|
||||
### Running Unit Tests
|
||||
* `./gradlew :core:testPlayDebugUnitTest`
|
||||
|
||||
### Running Integration Tests
|
||||
|
||||
#### Using Android Studio
|
||||
* Create a configuration via 'Run->Edit Configurations...'
|
||||
|
||||
<img width="768" alt="antennapod-run-tests"
|
||||
src="https://user-images.githubusercontent.com/149837/105122859-e1317180-5a8b-11eb-8d45-d54a3b051a9b.png">
|
||||
|
||||
#### Using the command line
|
||||
* Start an AVD or plug in your phone
|
||||
* `sh .github/workflows/runTests.sh`
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -18,7 +18,7 @@ You can use the [AntennaPod Forum](https://forum.antennapod.org/) for discussion
|
|||
Bug reports and feature requests can be submitted [here](https://github.com/AntennaPod/AntennaPod/issues) (please read the [instructions](https://github.com/AntennaPod/AntennaPod/blob/master/CONTRIBUTING.md) on how to report a bug and how to submit a feature request first!).
|
||||
|
||||
## Help to test AntennaPod
|
||||
AntennaPod has many users and we don't want them to run into trouble when we add a new feature. It's important that we have a significant group test our app, so that we know all possible combinations of phones, Android versions and use cases work as expected. Check out our wiki on how to join our [Alpha and Beta testing programmes](https://github.com/AntennaPod/AntennaPod/wiki/Help-test-AntennaPod)!
|
||||
AntennaPod has many users and we don't want them to run into trouble when we add a new feature. It's important that we have a significant group test our app, so that we know all possible combinations of phones, Android versions and use cases work as expected. Check out our wiki on how to join our [Beta testing program](https://antennapod.org/documentation/general/beta)! If a bug is reported during the beta period, chances are high that it will be fixed before the stable version. If it is reported later, fixing might take another full beta cycle. So definitely let us know if something is not right.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -19,11 +19,10 @@ android {
|
|||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
// Version code schema:
|
||||
// "1.2.3-SNAPSHOT" -> 1020300
|
||||
// "1.2.3-RC4" -> 1020304
|
||||
// "1.2.3-beta4" -> 1020304
|
||||
// "1.2.3" -> 1020395
|
||||
versionCode 2020000
|
||||
versionName "2.2.0"
|
||||
versionCode 2020001
|
||||
versionName "2.2.0-beta1"
|
||||
|
||||
multiDexEnabled false
|
||||
vectorDrawables.useSupportLibrary true
|
||||
|
@ -156,14 +155,9 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
freeImplementation project(":core")
|
||||
// free build hack: skip some dependencies
|
||||
if (!doFreeBuild()) {
|
||||
playImplementation project(":core")
|
||||
implementation 'com.google.android.play:core:1.8.0'
|
||||
} else {
|
||||
System.out.println("app: free build hack, skipping some dependencies")
|
||||
}
|
||||
implementation project(":core")
|
||||
implementation project(':ui:app-start-intent')
|
||||
implementation project(':ui:common')
|
||||
|
||||
annotationProcessor "androidx.annotation:annotation:$annotationVersion"
|
||||
implementation "androidx.appcompat:appcompat:$appcompatVersion"
|
||||
|
@ -191,14 +185,15 @@ dependencies {
|
|||
|
||||
implementation "com.joanzapata.iconify:android-iconify-fontawesome:$iconifyVersion"
|
||||
implementation "com.joanzapata.iconify:android-iconify-material:$iconifyVersion"
|
||||
implementation 'com.yqritc:recyclerview-flexibledivider:1.4.0'
|
||||
implementation 'com.github.shts:TriangleLabelView:1.1.2'
|
||||
implementation 'com.leinardi.android:speed-dial:3.1.1'
|
||||
implementation 'com.github.leinardi:FloatingActionButtonSpeedDial:3.1.1'
|
||||
implementation "com.github.AntennaPod:AntennaPod-AudioPlayer:$audioPlayerVersion"
|
||||
implementation 'com.github.mfietz:fyydlin:v0.5.0'
|
||||
implementation 'com.github.ByteHamster:SearchPreference:v2.0.0'
|
||||
implementation 'com.github.skydoves:balloon:1.1.5'
|
||||
|
||||
// Non-free dependencies:
|
||||
playImplementation 'com.google.android.play:core:1.8.0'
|
||||
compileOnly "com.google.android.wearable:wearable:$wearableSupportVersion"
|
||||
|
||||
androidTestImplementation "org.awaitility:awaitility:$awaitilityVersion"
|
||||
|
|
|
@ -3,8 +3,10 @@ package de.test.antennapod;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.test.espresso.NoMatchingViewException;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.espresso.PerformException;
|
||||
import androidx.test.espresso.UiController;
|
||||
|
@ -15,6 +17,9 @@ import androidx.test.espresso.contrib.RecyclerViewActions;
|
|||
import androidx.test.espresso.util.HumanReadables;
|
||||
import androidx.test.espresso.util.TreeIterables;
|
||||
import android.view.View;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
|
@ -33,6 +38,7 @@ import java.util.concurrent.TimeoutException;
|
|||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
|
||||
|
@ -57,7 +63,7 @@ public class EspressoTestUtils {
|
|||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "wait for a specific view for" + millis + " millis.";
|
||||
return "wait for a specific view for " + millis + " millis.";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -87,6 +93,33 @@ public class EspressoTestUtils {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until a certain view becomes visible, but at the longest until the timeout.
|
||||
* Unlike {@link #waitForView(Matcher, long)} it doesn't stick to the initial root view.
|
||||
*
|
||||
* @param viewMatcher The view to wait for.
|
||||
* @param timeoutMillis Maximum waiting period in milliseconds.
|
||||
* @throws Exception Throws an Exception in case of a timeout.
|
||||
*/
|
||||
public static void waitForViewGlobally(@NonNull Matcher<View> viewMatcher, long timeoutMillis) throws Exception {
|
||||
long startTime = System.currentTimeMillis();
|
||||
long endTime = startTime + timeoutMillis;
|
||||
|
||||
do {
|
||||
try {
|
||||
onView(viewMatcher).check(matches(isDisplayed()));
|
||||
// no Exception thrown -> check successful
|
||||
return;
|
||||
} catch (NoMatchingViewException | AssertionFailedError ignore) {
|
||||
// check was not successful "not found" -> continue waiting
|
||||
}
|
||||
//noinspection BusyWait
|
||||
Thread.sleep(50);
|
||||
} while (System.currentTimeMillis() < endTime);
|
||||
|
||||
throw new Exception("Timeout after " + timeoutMillis + " ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform action of waiting for a specific view id.
|
||||
* https://stackoverflow.com/a/30338665/
|
||||
|
@ -113,7 +146,7 @@ public class EspressoTestUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Clear all app databases
|
||||
* Clear all app databases.
|
||||
*/
|
||||
public static void clearPreferences() {
|
||||
File root = InstrumentationRegistry.getInstrumentation().getTargetContext().getFilesDir().getParentFile();
|
||||
|
|
|
@ -11,15 +11,11 @@ import org.junit.Rule;
|
|||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.test.espresso.intent.rule.IntentsTestRule;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.fragment.EpisodesFragment;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
import de.test.antennapod.ui.UITestUtils;
|
||||
|
@ -70,7 +66,6 @@ public class ShareDialogTest {
|
|||
onView(withText(R.string.all_episodes_short_label)).perform(click());
|
||||
|
||||
Matcher<View> allEpisodesMatcher;
|
||||
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10);
|
||||
allEpisodesMatcher = Matchers.allOf(withId(android.R.id.list), isDisplayed(), hasMinimumChildCount(2));
|
||||
onView(isRoot()).perform(waitForView(allEpisodesMatcher, 1000));
|
||||
onView(allEpisodesMatcher).perform(actionOnItemAtPosition(0, click()));
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
package de.test.antennapod.feed;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@SmallTest
|
||||
public class FeedItemTest {
|
||||
private static final String TEXT_LONG = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
|
||||
private static final String TEXT_SHORT = "Lorem ipsum";
|
||||
|
||||
/**
|
||||
* If one of `description` or `content:encoded` is null, use the other one.
|
||||
*/
|
||||
@Test
|
||||
public void testShownotesNullValues() throws Exception {
|
||||
testShownotes(null, TEXT_LONG);
|
||||
testShownotes(TEXT_LONG, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* If `description` is reasonably longer than `content:encoded`, use `description`.
|
||||
*/
|
||||
@Test
|
||||
public void testShownotesLength() throws Exception {
|
||||
testShownotes(TEXT_SHORT, TEXT_LONG);
|
||||
testShownotes(TEXT_LONG, TEXT_SHORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the shownotes equal TEXT_LONG, using the given `description` and `content:encoded`
|
||||
* @param description Description of the feed item
|
||||
* @param contentEncoded `content:encoded` of the feed item
|
||||
*/
|
||||
private void testShownotes(String description, String contentEncoded) throws Exception {
|
||||
FeedItem item = new FeedItem();
|
||||
item.setDescription(description);
|
||||
item.setContentEncoded(contentEncoded);
|
||||
assertEquals(TEXT_LONG, item.loadShownotes().call());
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package de.test.antennapod.handler;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.test.antennapod.util.syndication.feedgenerator.AtomGenerator;
|
||||
import org.junit.Test;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Tests for Atom feeds in FeedHandler.
|
||||
*/
|
||||
@SmallTest
|
||||
public class AtomParserTest extends FeedParserTestBase {
|
||||
@Test
|
||||
public void testAtomBasic() throws Exception {
|
||||
Feed f1 = createTestFeed(10, true);
|
||||
Feed f2 = runFeedTest(f1, new AtomGenerator(), "UTF-8", 0);
|
||||
feedValid(f1, f2, Feed.TYPE_ATOM1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLogoWithWhitespace() throws Exception {
|
||||
String logo = "https://example.com/image.png";
|
||||
Feed f1 = createTestFeed(0, false);
|
||||
f1.setImageUrl(null);
|
||||
Feed f2 = runFeedTest(f1, new AtomGenerator() {
|
||||
@Override
|
||||
protected void writeAdditionalAttributes(XmlSerializer xml) throws IOException {
|
||||
xml.startTag(null, "logo");
|
||||
xml.text(" " + logo + "\n");
|
||||
xml.endTag(null, "logo");
|
||||
}
|
||||
}, "UTF-8", 0);
|
||||
assertEquals(logo, f2.getImageUrl());
|
||||
}
|
||||
}
|
|
@ -1,154 +0,0 @@
|
|||
package de.test.antennapod.handler;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import de.danoeh.antennapod.core.feed.Chapter;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.syndication.handler.FeedHandler;
|
||||
import de.danoeh.antennapod.core.syndication.handler.UnsupportedFeedtypeException;
|
||||
import de.test.antennapod.util.syndication.feedgenerator.FeedGenerator;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests for FeedHandler.
|
||||
*/
|
||||
public abstract class FeedParserTestBase {
|
||||
private static final String FEEDS_DIR = "testfeeds";
|
||||
|
||||
private File file = null;
|
||||
private OutputStream outputStream = null;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
File destDir = context.getExternalFilesDir(FEEDS_DIR);
|
||||
assertNotNull(destDir);
|
||||
|
||||
file = new File(destDir, "feed.xml");
|
||||
file.delete();
|
||||
|
||||
assertNotNull(file);
|
||||
assertFalse(file.exists());
|
||||
|
||||
outputStream = new FileOutputStream(file);
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
file.delete();
|
||||
file = null;
|
||||
|
||||
outputStream.close();
|
||||
outputStream = null;
|
||||
}
|
||||
|
||||
protected Feed runFeedTest(Feed feed, FeedGenerator g, String encoding, long flags)
|
||||
throws IOException, UnsupportedFeedtypeException, SAXException, ParserConfigurationException {
|
||||
g.writeFeed(feed, outputStream, encoding, flags);
|
||||
FeedHandler handler = new FeedHandler();
|
||||
Feed parsedFeed = new Feed(feed.getDownload_url(), feed.getLastUpdate());
|
||||
parsedFeed.setFile_url(file.getAbsolutePath());
|
||||
parsedFeed.setDownloaded(true);
|
||||
handler.parseFeed(parsedFeed);
|
||||
return parsedFeed;
|
||||
}
|
||||
|
||||
protected void feedValid(Feed feed, Feed parsedFeed, String feedType) {
|
||||
assertEquals(feed.getTitle(), parsedFeed.getTitle());
|
||||
if (feedType.equals(Feed.TYPE_ATOM1)) {
|
||||
assertEquals(feed.getFeedIdentifier(), parsedFeed.getFeedIdentifier());
|
||||
} else {
|
||||
assertEquals(feed.getLanguage(), parsedFeed.getLanguage());
|
||||
}
|
||||
|
||||
assertEquals(feed.getLink(), parsedFeed.getLink());
|
||||
assertEquals(feed.getDescription(), parsedFeed.getDescription());
|
||||
assertEquals(feed.getPaymentLink(), parsedFeed.getPaymentLink());
|
||||
assertEquals(feed.getImageUrl(), parsedFeed.getImageUrl());
|
||||
|
||||
if (feed.getItems() != null) {
|
||||
assertNotNull(parsedFeed.getItems());
|
||||
assertEquals(feed.getItems().size(), parsedFeed.getItems().size());
|
||||
|
||||
for (int i = 0; i < feed.getItems().size(); i++) {
|
||||
FeedItem item = feed.getItems().get(i);
|
||||
FeedItem parsedItem = parsedFeed.getItems().get(i);
|
||||
|
||||
if (item.getItemIdentifier() != null) {
|
||||
assertEquals(item.getItemIdentifier(), parsedItem.getItemIdentifier());
|
||||
}
|
||||
assertEquals(item.getTitle(), parsedItem.getTitle());
|
||||
assertEquals(item.getDescription(), parsedItem.getDescription());
|
||||
assertEquals(item.getContentEncoded(), parsedItem.getContentEncoded());
|
||||
assertEquals(item.getLink(), parsedItem.getLink());
|
||||
assertEquals(item.getPubDate().getTime(), parsedItem.getPubDate().getTime());
|
||||
assertEquals(item.getPaymentLink(), parsedItem.getPaymentLink());
|
||||
|
||||
if (item.hasMedia()) {
|
||||
assertTrue(parsedItem.hasMedia());
|
||||
FeedMedia media = item.getMedia();
|
||||
FeedMedia parsedMedia = parsedItem.getMedia();
|
||||
|
||||
assertEquals(media.getDownload_url(), parsedMedia.getDownload_url());
|
||||
assertEquals(media.getSize(), parsedMedia.getSize());
|
||||
assertEquals(media.getMime_type(), parsedMedia.getMime_type());
|
||||
}
|
||||
|
||||
assertEquals(feed.getImageUrl(), item.getImageLocation());
|
||||
|
||||
if (item.getChapters() != null) {
|
||||
assertNotNull(parsedItem.getChapters());
|
||||
assertEquals(item.getChapters().size(), parsedItem.getChapters().size());
|
||||
List<Chapter> chapters = item.getChapters();
|
||||
List<Chapter> parsedChapters = parsedItem.getChapters();
|
||||
for (int j = 0; j < chapters.size(); j++) {
|
||||
Chapter chapter = chapters.get(j);
|
||||
Chapter parsedChapter = parsedChapters.get(j);
|
||||
|
||||
assertEquals(chapter.getTitle(), parsedChapter.getTitle());
|
||||
assertEquals(chapter.getLink(), parsedChapter.getLink());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Feed createTestFeed(int numItems, boolean withFeedMedia) {
|
||||
Feed feed = new Feed(0, null, "title", "http://example.com", "This is the description",
|
||||
"http://example.com/payment", "Daniel", "en", null, "http://example.com/feed",
|
||||
"http://example.com/picture", file.getAbsolutePath(), "http://example.com/feed", true);
|
||||
feed.setItems(new ArrayList<>());
|
||||
|
||||
for (int i = 0; i < numItems; i++) {
|
||||
FeedItem item = new FeedItem(0, "item-" + i, "http://example.com/item-" + i,
|
||||
"http://example.com/items/" + i, new Date(i * 60000), FeedItem.UNPLAYED, feed);
|
||||
feed.getItems().add(item);
|
||||
if (withFeedMedia) {
|
||||
item.setMedia(new FeedMedia(0, item, 4711, 0, 1024 * 1024, "audio/mp3", null,
|
||||
"http://example.com/media-" + i, false, null, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
return feed;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
package de.test.antennapod.handler;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.feed.MediaType;
|
||||
import de.danoeh.antennapod.core.syndication.namespace.NSMedia;
|
||||
import de.test.antennapod.util.syndication.feedgenerator.Rss2Generator;
|
||||
import org.junit.Test;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Tests for RSS feeds in FeedHandler.
|
||||
*/
|
||||
@SmallTest
|
||||
public class RssParserTest extends FeedParserTestBase {
|
||||
@Test
|
||||
public void testRss2Basic() throws Exception {
|
||||
Feed f1 = createTestFeed(10, true);
|
||||
Feed f2 = runFeedTest(f1, new Rss2Generator(), "UTF-8", Rss2Generator.FEATURE_WRITE_GUID);
|
||||
feedValid(f1, f2, Feed.TYPE_RSS2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImageWithWhitespace() throws Exception {
|
||||
String image = "https://example.com/image.png";
|
||||
Feed f1 = createTestFeed(0, false);
|
||||
f1.setImageUrl(null);
|
||||
Feed f2 = runFeedTest(f1, new Rss2Generator() {
|
||||
@Override
|
||||
protected void writeAdditionalAttributes(XmlSerializer xml) throws IOException {
|
||||
xml.startTag(null, "image");
|
||||
xml.startTag(null, "url");
|
||||
xml.text(" " + image + "\n");
|
||||
xml.endTag(null, "url");
|
||||
xml.endTag(null, "image");
|
||||
}
|
||||
}, "UTF-8", 0);
|
||||
assertEquals(image, f2.getImageUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMediaContentMime() throws Exception {
|
||||
Feed f1 = createTestFeed(0, false);
|
||||
f1.setImageUrl(null);
|
||||
Feed f2 = runFeedTest(f1, new Rss2Generator() {
|
||||
@Override
|
||||
protected void writeAdditionalAttributes(XmlSerializer xml) throws IOException {
|
||||
xml.setPrefix(NSMedia.NSTAG, NSMedia.NSURI);
|
||||
xml.startTag(null, "item");
|
||||
xml.startTag(NSMedia.NSURI, "content");
|
||||
xml.attribute(null, "url", "https://www.example.com/file.mp4");
|
||||
xml.attribute(null, "medium", "video");
|
||||
xml.endTag(NSMedia.NSURI, "content");
|
||||
xml.endTag(null, "item");
|
||||
}
|
||||
}, "UTF-8", 0);
|
||||
assertEquals(MediaType.VIDEO, f2.getItems().get(0).getMedia().getMediaType());
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import androidx.test.filters.LargeTest;
|
|||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
|
||||
import de.danoeh.antennapod.core.feed.FeedItemFilter;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.After;
|
||||
|
@ -107,7 +108,12 @@ public class PlaybackTest {
|
|||
}
|
||||
|
||||
private void setupPlaybackController() {
|
||||
controller = new PlaybackController(activityTestRule.getActivity());
|
||||
controller = new PlaybackController(activityTestRule.getActivity()) {
|
||||
@Override
|
||||
public void loadMediaInfo() {
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
controller.init();
|
||||
}
|
||||
|
||||
|
@ -252,7 +258,7 @@ public class PlaybackTest {
|
|||
onView(isRoot()).perform(waitForView(withText(R.string.all_episodes_short_label), 1000));
|
||||
onView(withText(R.string.all_episodes_short_label)).perform(click());
|
||||
|
||||
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10);
|
||||
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10, FeedItemFilter.unfiltered());
|
||||
Matcher<View> allEpisodesMatcher = allOf(withId(android.R.id.list), isDisplayed(), hasMinimumChildCount(2));
|
||||
onView(isRoot()).perform(waitForView(allEpisodesMatcher, 1000));
|
||||
onView(allEpisodesMatcher).perform(actionOnItemAtPosition(0, clickChildViewWithId(R.id.secondaryActionButton)));
|
||||
|
@ -287,7 +293,7 @@ public class PlaybackTest {
|
|||
uiTestUtils.addLocalFeedData(true);
|
||||
DBWriter.clearQueue().get();
|
||||
activityTestRule.launchActivity(new Intent());
|
||||
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10);
|
||||
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10, FeedItemFilter.unfiltered());
|
||||
|
||||
startLocalPlayback();
|
||||
FeedMedia media = episodes.get(0).getMedia();
|
||||
|
|
|
@ -6,6 +6,7 @@ import androidx.test.annotation.UiThreadTest;
|
|||
import androidx.test.filters.LargeTest;
|
||||
|
||||
import de.danoeh.antennapod.core.preferences.SleepTimerPreferences;
|
||||
import de.danoeh.antennapod.core.widget.WidgetUpdater;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.junit.After;
|
||||
|
@ -187,8 +188,8 @@ public class PlaybackServiceTaskManagerTest {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onWidgetUpdaterTick() {
|
||||
|
||||
public WidgetUpdater.WidgetState requestWidgetState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -248,8 +249,9 @@ public class PlaybackServiceTaskManagerTest {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onWidgetUpdaterTick() {
|
||||
public WidgetUpdater.WidgetState requestWidgetState() {
|
||||
countDownLatch.countDown();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -348,8 +350,8 @@ public class PlaybackServiceTaskManagerTest {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onWidgetUpdaterTick() {
|
||||
|
||||
public WidgetUpdater.WidgetState requestWidgetState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -391,8 +393,8 @@ public class PlaybackServiceTaskManagerTest {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onWidgetUpdaterTick() {
|
||||
|
||||
public WidgetUpdater.WidgetState requestWidgetState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -449,8 +451,8 @@ public class PlaybackServiceTaskManagerTest {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onWidgetUpdaterTick() {
|
||||
|
||||
public WidgetUpdater.WidgetState requestWidgetState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -3,13 +3,13 @@ package de.test.antennapod.storage;
|
|||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import de.danoeh.antennapod.core.ClientConfig;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
import de.test.antennapod.ui.UITestUtils;
|
||||
|
@ -29,8 +29,7 @@ public class AutoDownloadTest {
|
|||
|
||||
private Context context;
|
||||
private UITestUtils stubFeedsServer;
|
||||
|
||||
private AutomaticDownloadAlgorithm automaticDownloadAlgorithmOrig;
|
||||
private StubDownloadAlgorithm stubDownloadAlgorithm;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
@ -39,16 +38,19 @@ public class AutoDownloadTest {
|
|||
stubFeedsServer = new UITestUtils(context);
|
||||
stubFeedsServer.setup();
|
||||
|
||||
automaticDownloadAlgorithmOrig = ClientConfig.automaticDownloadAlgorithm;
|
||||
|
||||
EspressoTestUtils.clearPreferences();
|
||||
EspressoTestUtils.clearDatabase();
|
||||
UserPreferences.setAllowMobileStreaming(true);
|
||||
|
||||
// Setup: enable automatic download
|
||||
// it is not needed, as the actual automatic download is stubbed.
|
||||
stubDownloadAlgorithm = new StubDownloadAlgorithm();
|
||||
DBTasks.setDownloadAlgorithm(stubDownloadAlgorithm);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
ClientConfig.automaticDownloadAlgorithm = automaticDownloadAlgorithmOrig;
|
||||
DBTasks.setDownloadAlgorithm(new AutomaticDownloadAlgorithm());
|
||||
EspressoTestUtils.tryKillPlaybackService();
|
||||
stubFeedsServer.tearDown();
|
||||
}
|
||||
|
@ -74,11 +76,6 @@ public class AutoDownloadTest {
|
|||
FeedItem item0 = queue.get(0);
|
||||
FeedItem item1 = queue.get(1);
|
||||
|
||||
// Setup: enable automatic download
|
||||
// it is not needed, as the actual automatic download is stubbed.
|
||||
StubDownloadAlgorithm stubDownloadAlgorithm = new StubDownloadAlgorithm();
|
||||
ClientConfig.automaticDownloadAlgorithm = stubDownloadAlgorithm;
|
||||
|
||||
// Actual test
|
||||
// Play the first one in the queue
|
||||
playEpisode(item0);
|
||||
|
@ -92,11 +89,10 @@ public class AutoDownloadTest {
|
|||
} catch (ConditionTimeoutException cte) {
|
||||
long actual = stubDownloadAlgorithm.getCurrentlyPlayingAtDownload();
|
||||
fail("when auto download is triggered, the next episode should be playing: ("
|
||||
+ item1.getId() + ", " + item1.getTitle() + ") . "
|
||||
+ item1.getId() + ", " + item1.getTitle() + ") . "
|
||||
+ "Actual playing: (" + actual + ")"
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void playEpisode(@NonNull FeedItem item) {
|
||||
|
@ -111,7 +107,7 @@ public class AutoDownloadTest {
|
|||
.until(() -> item.getMedia().getId() == PlaybackPreferences.getCurrentlyPlayingFeedMediaId());
|
||||
}
|
||||
|
||||
private static class StubDownloadAlgorithm implements AutomaticDownloadAlgorithm {
|
||||
private static class StubDownloadAlgorithm extends AutomaticDownloadAlgorithm {
|
||||
private long currentlyPlaying = -1;
|
||||
|
||||
@Override
|
||||
|
|
|
@ -2,16 +2,14 @@ package de.test.antennapod.ui;
|
|||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import androidx.test.espresso.Espresso;
|
||||
import androidx.test.espresso.intent.rule.IntentsTestRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import com.robotium.solo.Solo;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.storage.PodDBAdapter;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
|
@ -20,6 +18,12 @@ import org.junit.runner.RunWith;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.storage.PodDBAdapter;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.replaceText;
|
||||
|
@ -28,18 +32,17 @@ import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
|||
import static androidx.test.espresso.contrib.ActivityResultMatchers.hasResultCode;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static de.test.antennapod.EspressoTestUtils.clickPreference;
|
||||
import static de.test.antennapod.EspressoTestUtils.openNavDrawer;
|
||||
import static de.test.antennapod.EspressoTestUtils.waitForView;
|
||||
import static de.test.antennapod.EspressoTestUtils.waitForViewGlobally;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* User interface tests for MainActivity
|
||||
* User interface tests for MainActivity.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class MainActivityTest {
|
||||
|
@ -48,19 +51,19 @@ public class MainActivityTest {
|
|||
private UITestUtils uiTestUtils;
|
||||
|
||||
@Rule
|
||||
public IntentsTestRule<MainActivity> mActivityRule = new IntentsTestRule<>(MainActivity.class, false, false);
|
||||
public IntentsTestRule<MainActivity> activityRule = new IntentsTestRule<>(MainActivity.class, false, false);
|
||||
|
||||
@Before
|
||||
public void setUp() throws IOException {
|
||||
EspressoTestUtils.clearPreferences();
|
||||
EspressoTestUtils.clearDatabase();
|
||||
|
||||
mActivityRule.launchActivity(new Intent());
|
||||
activityRule.launchActivity(new Intent());
|
||||
|
||||
uiTestUtils = new UITestUtils(InstrumentationRegistry.getInstrumentation().getTargetContext());
|
||||
uiTestUtils.setup();
|
||||
|
||||
solo = new Solo(InstrumentationRegistry.getInstrumentation(), mActivityRule.getActivity());
|
||||
solo = new Solo(InstrumentationRegistry.getInstrumentation(), activityRule.getActivity());
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -71,6 +74,7 @@ public class MainActivityTest {
|
|||
|
||||
@Test
|
||||
public void testAddFeed() throws Exception {
|
||||
// connect to podcast feed
|
||||
uiTestUtils.addHostedFeedData();
|
||||
final Feed feed = uiTestUtils.hostedFeeds.get(0);
|
||||
openNavDrawer();
|
||||
|
@ -78,9 +82,14 @@ public class MainActivityTest {
|
|||
onView(withId(R.id.addViaUrlButton)).perform(scrollTo(), click());
|
||||
onView(withId(R.id.urlEditText)).perform(replaceText(feed.getDownload_url()));
|
||||
onView(withText(R.string.confirm_label)).perform(scrollTo(), click());
|
||||
|
||||
// subscribe podcast
|
||||
Espresso.closeSoftKeyboard();
|
||||
waitForViewGlobally(withText(R.string.subscribe_label), 15000);
|
||||
onView(withText(R.string.subscribe_label)).perform(click());
|
||||
onView(isRoot()).perform(waitForView(withId(R.id.butShowSettings), 5000));
|
||||
|
||||
// wait for podcast feed item list
|
||||
waitForViewGlobally(withId(R.id.butShowSettings), 15000);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -100,7 +109,7 @@ public class MainActivityTest {
|
|||
onView(allOf(withId(R.id.toolbar), isDisplayed())).check(
|
||||
matches(hasDescendant(withText(R.string.subscriptions_label))));
|
||||
solo.goBack();
|
||||
assertThat(mActivityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -113,7 +122,7 @@ public class MainActivityTest {
|
|||
solo.goBackToActivity(MainActivity.class.getSimpleName());
|
||||
solo.goBack();
|
||||
solo.goBack();
|
||||
assertTrue(((MainActivity)solo.getCurrentActivity()).isDrawerOpen());
|
||||
assertTrue(((MainActivity) solo.getCurrentActivity()).isDrawerOpen());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -127,7 +136,7 @@ public class MainActivityTest {
|
|||
solo.goBack();
|
||||
solo.goBack();
|
||||
solo.goBack();
|
||||
assertThat(mActivityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -142,7 +151,7 @@ public class MainActivityTest {
|
|||
solo.goBack();
|
||||
onView(withText(R.string.yes)).perform(click());
|
||||
Thread.sleep(100);
|
||||
assertThat(mActivityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -155,6 +164,6 @@ public class MainActivityTest {
|
|||
solo.goBackToActivity(MainActivity.class.getSimpleName());
|
||||
solo.goBack();
|
||||
solo.goBack();
|
||||
assertThat(mActivityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import de.danoeh.antennapod.core.storage.APCleanupAlgorithm;
|
|||
import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm;
|
||||
import de.danoeh.antennapod.core.storage.APQueueCleanupAlgorithm;
|
||||
import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
|
||||
import de.danoeh.antennapod.core.storage.ExceptFavoriteCleanupAlgorithm;
|
||||
import de.danoeh.antennapod.fragment.EpisodesFragment;
|
||||
import de.danoeh.antennapod.fragment.QueueFragment;
|
||||
import de.danoeh.antennapod.fragment.SubscriptionFragment;
|
||||
|
@ -371,6 +372,17 @@ public class PreferencesTest {
|
|||
.until(() -> enableAutodownloadOnBattery == UserPreferences.isEnableAutodownloadOnBattery());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEpisodeCleanupFavoriteOnly() {
|
||||
clickPreference(R.string.network_pref);
|
||||
onView(withText(R.string.pref_automatic_download_title)).perform(click());
|
||||
onView(withText(R.string.pref_episode_cleanup_title)).perform(click());
|
||||
onView(isRoot()).perform(waitForView(withText(R.string.episode_cleanup_except_favorite_removal), 1000));
|
||||
onView(withText(R.string.episode_cleanup_except_favorite_removal)).perform(click());
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> UserPreferences.getEpisodeCleanupAlgorithm() instanceof ExceptFavoriteCleanupAlgorithm);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEpisodeCleanupQueueOnly() {
|
||||
clickPreference(R.string.network_pref);
|
||||
|
|
|
@ -15,6 +15,7 @@ import de.danoeh.antennapod.core.service.playback.PlayerStatus;
|
|||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
import de.danoeh.antennapod.fragment.QueueFragment;
|
||||
import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
import de.test.antennapod.IgnoreOnCi;
|
||||
import org.awaitility.Awaitility;
|
||||
|
@ -71,8 +72,13 @@ public class SpeedChangeTest {
|
|||
UserPreferences.setPlaybackSpeedArray(Arrays.asList(1.0f, 2.0f, 3.0f));
|
||||
|
||||
EspressoTestUtils.tryKillPlaybackService();
|
||||
activityRule.launchActivity(new Intent().putExtra(MainActivity.EXTRA_OPEN_PLAYER, true));
|
||||
controller = new PlaybackController(activityRule.getActivity());
|
||||
activityRule.launchActivity(new Intent().putExtra(MainActivityStarter.EXTRA_OPEN_PLAYER, true));
|
||||
controller = new PlaybackController(activityRule.getActivity()) {
|
||||
@Override
|
||||
public void loadMediaInfo() {
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
controller.init();
|
||||
controller.getMedia(); // To load media
|
||||
}
|
||||
|
|
|
@ -1,131 +0,0 @@
|
|||
package de.test.antennapod.util.syndication.feedgenerator;
|
||||
|
||||
import android.util.Xml;
|
||||
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.util.DateUtils;
|
||||
|
||||
/**
|
||||
* Creates Atom feeds. See FeedGenerator for more information.
|
||||
*/
|
||||
public class AtomGenerator implements FeedGenerator {
|
||||
|
||||
private static final String NS_ATOM = "http://www.w3.org/2005/Atom";
|
||||
|
||||
private static final long FEATURE_USE_RFC3339LOCAL = 1;
|
||||
|
||||
@Override
|
||||
public void writeFeed(Feed feed, OutputStream outputStream, String encoding, long flags) throws IOException {
|
||||
if (feed == null) throw new IllegalArgumentException("feed = null");
|
||||
if (outputStream == null) throw new IllegalArgumentException("outputStream = null");
|
||||
if (encoding == null) throw new IllegalArgumentException("encoding = null");
|
||||
|
||||
XmlSerializer xml = Xml.newSerializer();
|
||||
xml.setOutput(outputStream, encoding);
|
||||
xml.startDocument(encoding, null);
|
||||
|
||||
xml.startTag(null, "feed");
|
||||
xml.attribute(null, "xmlns", NS_ATOM);
|
||||
|
||||
// Write Feed data
|
||||
if (feed.getIdentifyingValue() != null) {
|
||||
xml.startTag(null, "id");
|
||||
xml.text(feed.getIdentifyingValue());
|
||||
xml.endTag(null, "id");
|
||||
}
|
||||
if (feed.getTitle() != null) {
|
||||
xml.startTag(null, "title");
|
||||
xml.text(feed.getTitle());
|
||||
xml.endTag(null, "title");
|
||||
}
|
||||
if (feed.getLink() != null) {
|
||||
xml.startTag(null, "link");
|
||||
xml.attribute(null, "rel", "alternate");
|
||||
xml.attribute(null, "href", feed.getLink());
|
||||
xml.endTag(null, "link");
|
||||
}
|
||||
if (feed.getDescription() != null) {
|
||||
xml.startTag(null, "subtitle");
|
||||
xml.text(feed.getDescription());
|
||||
xml.endTag(null, "subtitle");
|
||||
}
|
||||
if (feed.getImageUrl() != null) {
|
||||
xml.startTag(null, "logo");
|
||||
xml.text(feed.getImageUrl());
|
||||
xml.endTag(null, "logo");
|
||||
}
|
||||
|
||||
if (feed.getPaymentLink() != null) {
|
||||
GeneratorUtil.addPaymentLink(xml, feed.getPaymentLink(), false);
|
||||
}
|
||||
|
||||
// Write FeedItem data
|
||||
if (feed.getItems() != null) {
|
||||
for (FeedItem item : feed.getItems()) {
|
||||
xml.startTag(null, "entry");
|
||||
|
||||
if (item.getIdentifyingValue() != null) {
|
||||
xml.startTag(null, "id");
|
||||
xml.text(item.getIdentifyingValue());
|
||||
xml.endTag(null, "id");
|
||||
}
|
||||
if (item.getTitle() != null) {
|
||||
xml.startTag(null, "title");
|
||||
xml.text(item.getTitle());
|
||||
xml.endTag(null, "title");
|
||||
}
|
||||
if (item.getLink() != null) {
|
||||
xml.startTag(null, "link");
|
||||
xml.attribute(null, "rel", "alternate");
|
||||
xml.attribute(null, "href", item.getLink());
|
||||
xml.endTag(null, "link");
|
||||
}
|
||||
if (item.getPubDate() != null) {
|
||||
xml.startTag(null, "published");
|
||||
if ((flags & FEATURE_USE_RFC3339LOCAL) != 0) {
|
||||
xml.text(DateUtils.formatRFC3339Local(item.getPubDate()));
|
||||
} else {
|
||||
xml.text(DateUtils.formatRFC3339UTC(item.getPubDate()));
|
||||
}
|
||||
xml.endTag(null, "published");
|
||||
}
|
||||
if (item.getDescription() != null) {
|
||||
xml.startTag(null, "content");
|
||||
xml.text(item.getDescription());
|
||||
xml.endTag(null, "content");
|
||||
}
|
||||
if (item.getMedia() != null) {
|
||||
FeedMedia media = item.getMedia();
|
||||
xml.startTag(null, "link");
|
||||
xml.attribute(null, "rel", "enclosure");
|
||||
xml.attribute(null, "href", media.getDownload_url());
|
||||
xml.attribute(null, "type", media.getMime_type());
|
||||
xml.attribute(null, "length", String.valueOf(media.getSize()));
|
||||
xml.endTag(null, "link");
|
||||
}
|
||||
|
||||
if (item.getPaymentLink() != null) {
|
||||
GeneratorUtil.addPaymentLink(xml, item.getPaymentLink(), false);
|
||||
}
|
||||
|
||||
xml.endTag(null, "entry");
|
||||
}
|
||||
}
|
||||
|
||||
writeAdditionalAttributes(xml);
|
||||
|
||||
xml.endTag(null, "feed");
|
||||
xml.endDocument();
|
||||
}
|
||||
|
||||
protected void writeAdditionalAttributes(XmlSerializer xml) throws IOException {
|
||||
|
||||
}
|
||||
}
|
|
@ -60,7 +60,8 @@
|
|||
<activity
|
||||
android:name=".activity.SplashActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize">
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
@ -83,11 +84,39 @@
|
|||
android:configChanges="keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout|density|uiMode|keyboard|navigation"
|
||||
android:windowSoftInputMode="stateAlwaysHidden"
|
||||
android:launchMode="singleTask"
|
||||
android:label="@string/app_name">
|
||||
android:label="@string/app_name"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="antennapod.org"
|
||||
android:pathPrefix="/deeplink/main"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="antennapod.org"
|
||||
android:pathPrefix="/deeplink/search"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="de.danoeh.antennapod.intents.MAIN_ACTIVITY" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activity.DownloadAuthenticationActivity"
|
||||
android:theme="@style/Theme.AntennaPod.Dark.Translucent"
|
||||
android:launchMode="singleInstance"/>
|
||||
|
||||
<activity
|
||||
|
@ -101,7 +130,8 @@
|
|||
|
||||
<activity
|
||||
android:name=".activity.WidgetConfigActivity"
|
||||
android:label="@string/widget_settings">
|
||||
android:label="@string/widget_settings"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGUR"/>
|
||||
</intent-filter>
|
||||
|
@ -114,21 +144,21 @@
|
|||
android:exported="false">
|
||||
</service>
|
||||
|
||||
<receiver android:name=".core.receiver.PlayerWidget">
|
||||
<receiver
|
||||
android:name=".core.receiver.PlayerWidget"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="de.danoeh.antennapod.FORCE_WIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/player_widget_info"/>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="de.danoeh.antennapod.STOP_WIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/player_widget_info"/>
|
||||
</receiver>
|
||||
|
||||
<activity android:name=".activity.StorageErrorActivity">
|
||||
|
@ -136,7 +166,8 @@
|
|||
<activity
|
||||
android:name=".activity.OpmlImportActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:label="@string/opml_import_label">
|
||||
android:label="@string/opml_import_label"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
||||
|
@ -185,18 +216,14 @@
|
|||
android:name=".activity.VideoplayerActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:screenOrientation="sensorLandscape">
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="de.danoeh.antennapod.activity.MainActivity"/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data android:scheme="file"/>
|
||||
<data android:mimeType="video/*"/>
|
||||
<action android:name="de.danoeh.antennapod.intents.VIDEO_PLAYER" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
@ -204,7 +231,8 @@
|
|||
android:name=".activity.OnlineFeedViewActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:theme="@style/Theme.AntennaPod.Dark.Translucent"
|
||||
android:label="@string/add_feed_label">
|
||||
android:label="@string/add_feed_label"
|
||||
android:exported="true">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="de.danoeh.antennapod.activity.MainActivity"/>
|
||||
|
@ -292,33 +320,26 @@
|
|||
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activity.gpoddernet.GpodnetAuthenticationActivity"
|
||||
android:configChanges="orientation"
|
||||
android:label="@string/gpodnet_auth_label">
|
||||
<intent-filter>
|
||||
<action android:name=".activity.gpoddernet.GpodnetAuthenticationActivity"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="de.danoeh.antennapod.activity.PreferenceActivity"/>
|
||||
</activity>
|
||||
|
||||
<receiver android:name=".receiver.ConnectivityActionReceiver">
|
||||
<receiver
|
||||
android:name=".receiver.ConnectivityActionReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".receiver.PowerConnectionReceiver">
|
||||
<receiver
|
||||
android:name=".receiver.PowerConnectionReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
|
||||
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".receiver.SPAReceiver">
|
||||
<receiver
|
||||
android:name=".receiver.SPAReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="de.danoeh.antennapdsp.intent.SP_APPS_QUERY_FEEDS_RESPONSE"/>
|
||||
</intent-filter>
|
||||
|
@ -333,6 +354,10 @@
|
|||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths"/>
|
||||
</provider>
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.actions"
|
||||
android:resource="@xml/actions" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
@ -4,14 +4,14 @@ mfietz;6860662;Maintainer (retired)
|
|||
TomHennen;5216560;Maintainer (retired)
|
||||
orionlee;250644;Contributor
|
||||
domingos86;9538859;Contributor
|
||||
damoasda;46045854;Contributor
|
||||
tonytamsf;149837;Contributor
|
||||
andersonvom;69922;Contributor
|
||||
damoasda;46045854;Contributor
|
||||
TacoTheDank;32376686;Contributor
|
||||
shortspider;5712543;Contributor
|
||||
spacecowboy;223655;Contributor
|
||||
ebraminio;833473;Contributor
|
||||
asdoi;36813904;Contributor
|
||||
spacecowboy;223655;Contributor
|
||||
patheticpat;16046;Contributor
|
||||
brad;1614;Contributor
|
||||
Cj-Malone;10121513;Contributor
|
||||
|
@ -44,6 +44,7 @@ mchelen;30691;Contributor
|
|||
dethstar;1239177;Contributor
|
||||
drabux;10663142;Contributor
|
||||
saqura;1935380;Contributor
|
||||
binarytoto;75904760;Contributor
|
||||
bibz;5141956;Contributor
|
||||
hzulla;1705654;Contributor
|
||||
deandreamatias;21011641;Contributor
|
||||
|
@ -65,6 +66,7 @@ HrBDev;25826502;Contributor
|
|||
HolgerJeromin;2410353;Contributor
|
||||
xisberto;1914956;Contributor
|
||||
jmue;898577;Contributor
|
||||
jonasburian;15125616;Contributor
|
||||
katrinleinweber;9948149;Contributor
|
||||
LatinSuD;451487;Contributor
|
||||
24hours;650407;Contributor
|
||||
|
@ -74,25 +76,27 @@ archibishop;36948493;Contributor
|
|||
alifeflow;24603829;Contributor
|
||||
avirajrsingh;69088913;Contributor
|
||||
toggles;14695;Contributor
|
||||
connectety;26038710;Contributor
|
||||
matdb;48329535;Contributor
|
||||
damlayildiz;56313500;Contributor
|
||||
kingargyle;177042;Contributor
|
||||
dsmith47;14109426;Contributor
|
||||
hannesaa2;18496079;Contributor
|
||||
jhunnius;9149031;Contributor
|
||||
a1291762;327162;Contributor
|
||||
ShadowIce;59123;Contributor
|
||||
Niffler;8172446;Contributor
|
||||
raghulj;57007;Contributor
|
||||
raghulrm;5362986;Contributor
|
||||
mamehacker;16738348;Contributor
|
||||
skitt;2128935;Contributor
|
||||
Thom-Merrilin;76849828;Contributor
|
||||
wseemann;2296196;Contributor
|
||||
markamaze;17114678;Contributor
|
||||
mohitshah3111999;42018918;Contributor
|
||||
moralesg;14352147;Contributor
|
||||
mr-intj;6268767;Contributor
|
||||
tuxayo;2678215;Contributor
|
||||
schlch;56929215;Contributor
|
||||
alimemonzx;44647595;Contributor
|
||||
dev-darrell;52300159;Contributor
|
||||
jmdouglas;10855634;Contributor
|
||||
|
@ -106,24 +110,27 @@ arantius;84729;Contributor
|
|||
BoJacobs;25435640;Contributor
|
||||
chetan882777;36985543;Contributor
|
||||
chrissicool;232590;Contributor
|
||||
britiger;2057760;Contributor
|
||||
cszucko;1810383;Contributor
|
||||
CWftw;1498303;Contributor
|
||||
connectety;26038710;Contributor
|
||||
danielm5;66779;Contributor
|
||||
ariedov;958646;Contributor
|
||||
brettle;118192;Contributor
|
||||
edwinhere;19705425;Contributor
|
||||
eirikv;4076243;Contributor
|
||||
eerden;277513;Contributor
|
||||
Geist5000;37940313;Contributor
|
||||
jklippel;8657220;Contributor
|
||||
jannic;232606;Contributor
|
||||
Foso;5015532;Contributor
|
||||
Kaligule;3586246;Contributor
|
||||
kvithayathil;1056073;Contributor
|
||||
luiscruz;1080714;Contributor
|
||||
MStrecke;5202211;Contributor
|
||||
mlasson;5814258;Contributor
|
||||
schwedenmut;9077622;Contributor
|
||||
M-arcel;56698158;Contributor
|
||||
mgborowiec;29843126;Contributor
|
||||
msoose;30473690;Contributor
|
||||
mo;7117;Contributor
|
||||
mdeveloper20;2319126;Contributor
|
||||
|
@ -138,6 +145,7 @@ ortylp;470439;Contributor
|
|||
ramzan;55637406;Contributor
|
||||
iamrichR;44210678;Contributor
|
||||
SamWhited;512573;Contributor
|
||||
SebiderSushi;23618858;Contributor
|
||||
selivan;1208989;Contributor
|
||||
sonnayasomnambula;7716779;Contributor
|
||||
sethoscope;534043;Contributor
|
||||
|
@ -148,14 +156,17 @@ vimsick;20211590;Contributor
|
|||
lyallemma;25173082;Contributor
|
||||
edent;837136;Contributor
|
||||
atrus6;357881;Contributor
|
||||
timakro;8438790;Contributor
|
||||
heyyviv;56256802;Contributor
|
||||
waylife;3348620;Contributor
|
||||
yarons;406826;Contributor
|
||||
amhokies;3124968;Contributor
|
||||
andrewc1;19559401;Contributor
|
||||
axq;5077221;Contributor
|
||||
binarytoto;75904760;Contributor
|
||||
chrk2205;44704035;Contributor
|
||||
fossterer;4236021;Contributor
|
||||
lightonflux;1377943;Contributor
|
||||
minusf;3632883;Contributor
|
||||
s3lph;5564491;Contributor
|
||||
tamizh138;26201258;Contributor
|
||||
zawad2221;32180355;Contributor
|
||||
|
|
|
|
@ -90,12 +90,6 @@
|
|||
website="https://github.com/square/okio"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="RecyclerView-FlexibleDivider"
|
||||
author="yqritc"
|
||||
website="https://github.com/yqritc/RecyclerView-FlexibleDivider"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="RxAndroid"
|
||||
author="ReactiveX"
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
Arabic;abuzar3.khalid, badarotti, keunes, nabilMaghura, rex07, shubbar
|
||||
Arabic;abuzar3.khalid, badarotti, keunes, MustafaAlgurabi, nabilMaghura, rex07, shubbar
|
||||
Asturian (ast_ES);enolp
|
||||
Basque;gaztainalde, keunes, Osoitz, pospolos
|
||||
Breton;Belvar, keunes
|
||||
Bulgarian;keunes, solusitor
|
||||
Bulgarian;keunes, ma4ko, solusitor
|
||||
Catalan;carles.llacer, dvd1985, exort12, IvanAmarante, javiercoll, keunes, Kintu, lambdani, marcmetallextrem, xc70
|
||||
Chinese (zh_CN);brnme, cyril3, Felix2yu, gaohongyuan, Guaidaodl, Huck0, iconteral, jhxie, jxj2zzz79pfp9bpo, keunes, kyleehee, molisiye, owen8877, RainSlide, RangerNJU, Sak94664, spice2wolf, tupunco, wongsyrone, yangyang, yiqiok
|
||||
Chinese (zh_TW);bobchao, ijliao, keunes, mapobi, pggdt, ymhuang0808
|
||||
Czech (cs_CZ);anotheranonymoususer, elich, Hanzmeister, svetlemodry, Thomaash
|
||||
Danish;JFreak, jhertel, keunes, SebastianKiwiDk, twikedk
|
||||
Czech (cs_CZ);anotheranonymoususer, elich, Hanzmeister, md.share, svetlemodry, Thomaash
|
||||
Danish;JFreak, jhertel, keunes, petterbejo, SebastianKiwiDk
|
||||
Dutch;e2jk, keunes, rwv, Vistaus
|
||||
Estonian;Eraser, keunes, mahfiaz
|
||||
Finnish;Ban3, keunes, Sahtor
|
||||
French;ChaoticMind, clombion, Cornegidouille, e2jk, keunes, lacouture, LouFex, Matth78, Poussinou, sterylmreep
|
||||
French;ChaoticMind, clombion, Cornegidouille, e2jk, keunes, lacouture, LouFex, Matth78, petterbejo, Poussinou, RomainTT, sterylmreep
|
||||
Galician;antiparvos, pikamoku, Raichely
|
||||
German;_Er, ByteHamster, ceving, dadosch, DerSilly, elkangaroo, enz, f_grubm, finsterwalder, hbilke, HolgerJeromin, JoeMcFly, kalei, keunes, mfietz, pudeeh, Quiss42, repat, tomte, tweimer, Willhelm, ypid
|
||||
German;_Er, ByteHamster, ceving, dadosch, DerSilly, elkangaroo, enz, f_grubm, finsterwalder, forght, hbilke, HolgerJeromin, JoeMcFly, kalei, keunes, max.wittig, mfietz, Michael_Strecke, petterbejo, pudeeh, Quiss42, repat, toaskoas, tomte, tweimer, Willhelm, ypid
|
||||
Modern Greek (1453-);AnimaRain, antonist, keunes, pavlosv
|
||||
Hebrew (he_IL);amir.dafnyman, E1i9, mongoose4004, pinkasey, rellieberman, Yaron
|
||||
Hindi (hi_IN);keunes, purple.coder, siddhusengar, thelazyoxymoron
|
||||
|
@ -24,20 +24,20 @@ Italian (it_IT);aalex70, allin, alvami, Bonnee, dontknowcris, giuseppep, Guybrus
|
|||
Japanese;keunes, KotaKato, Naofumi, sh3llc4t, TranslatorG
|
||||
Kannada (kn_IN);chiraag.nataraj, keunes, thejeshgn
|
||||
Ko;changwoo, keunes, libliboom
|
||||
Lithuanian;keunes, naglis
|
||||
Lithuanian;keunes, naglis, Sharper
|
||||
Macedonian;krisfremen
|
||||
Malayalam;joice, keunes, rashivkp
|
||||
Norwegian Bokmål (nb_NO);abstrakct, ahysing, bablecopherye, corkie, forteller, heraldo, jakobkg, keunes, kongk, sevenmaster, timbast
|
||||
Persian;ahangarha, danialbehzadi, ebadi, ebraminio, F7D, hamidrezabayat76, keunes, sinamoghaddas
|
||||
Polish (pl_PL);befeleme, hiro2020, Iwangelion, keunes, lomapur, mandlus, maniexx, Mephistofeles, shark103, tyle
|
||||
Polish (pl_PL);befeleme, hiro2020, Iwangelion, kamila.miodek1991, keunes, lomapur, mandlus, maniexx, Mephistofeles, shark103, tyle
|
||||
Portuguese;emansije, keunes, smarquespt
|
||||
Portuguese (pt_BR);alexupits, alysonborges, andersonvom, arua, caioau, carlo_valente, castrors, edman, keunes, lipefire, mbaltar, olivoto, rogervezaro, RubeensVinicius, SamWilliam
|
||||
Portuguese (pt_BR);alexupits, alysonborges, andersonvom, aracnus, arua, bandreghetti, caioau, carlo_valente, castrors, edman, keunes, lipefire, mbaltar, olivoto, rogervezaro, RubeensVinicius, SamWilliam
|
||||
Romanian (ro_RO);corneliu.e, fuzzmz, keunes, ralienpp
|
||||
Russian (ru_RU);ashed, btimofeev, Duke_Raven, gammja, homocomputeris, IgorPolyakov, keunes, mercutiy, null, overmind88, Platun0v, PtilopsisLeucotis, s.chebotar, un_logic, Vladryyu, whereisthetea
|
||||
Slovak;ati3, keunes, marulinko, tiborepcek
|
||||
Slovenian (sl_SI);keunes, panter23
|
||||
Spanish;AleksSyntek, andersonvom, andrespelaezp, deandreamatias, dvd1985, elojodepajaro, Fitoschido, frandavid100, hard_ware, javiercoll, keunes, LatinSuD, leogrignafini, rafael.osuna, tres.14159, vfmatzkin, wakutiteo
|
||||
Swahili (macrolanguage);keunes, kmtra
|
||||
Russian (ru_RU);ashed, btimofeev, Duke_Raven, gammja, homocomputeris, IgorPolyakov, keunes, mercutiy, null, overmind88, Platun0v, PtilopsisLeucotis, s.chebotar, tepxd, un_logic, Vladryyu, whereisthetea
|
||||
Slovak;ati3, jose1711, keunes, marulinko, tiborepcek
|
||||
Slovenian (sl_SI);asovic, keunes, panter23, trus2
|
||||
Spanish;AleksSyntek, andersonvom, andrespelaezp, Atreyu94, CaeM0R, deandreamatias, dvd1985, elojodepajaro, Fitoschido, frandavid100, hard_ware, javiercoll, keunes, LatinSuD, leogrignafini, rafael.osuna, tres.14159, vfmatzkin, wakutiteo
|
||||
Swahili (macrolanguage);1silvester, keunes, kmtra
|
||||
Swedish (sv_SE);bpnilsson, keunes, nilso, TwoD
|
||||
Telugu;keunes, veeven
|
||||
Turkish;AhmedDuran, brsata, Erdy, keunes, overbite, Slsdem
|
||||
|
|
|
|
@ -1,95 +1,76 @@
|
|||
package de.danoeh.antennapod.activity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadRequest;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||
import de.danoeh.antennapod.dialog.AuthenticationDialog;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
|
||||
/**
|
||||
* Shows a username and a password text field.
|
||||
* The activity MUST be started with the ARG_DOWNlOAD_REQUEST argument set to a non-null value.
|
||||
* Other arguments are optional.
|
||||
* The activity's result will be the same DownloadRequest with the entered username and password.
|
||||
*/
|
||||
public class DownloadAuthenticationActivity extends AppCompatActivity {
|
||||
|
||||
/**
|
||||
* The download request object that contains information about the resource that requires a username and a password
|
||||
* The download request object that contains information about the resource that requires a username and a password.
|
||||
*/
|
||||
public static final String ARG_DOWNLOAD_REQUEST = "request";
|
||||
/**
|
||||
* True if the request should be sent to the DownloadRequester when this activity is finished, false otherwise.
|
||||
* The default value is false.
|
||||
*/
|
||||
public static final String ARG_SEND_TO_DOWNLOAD_REQUESTER_BOOL = "send_to_downloadrequester";
|
||||
|
||||
private static final String RESULT_REQUEST = "request";
|
||||
|
||||
private EditText etxtUsername;
|
||||
private EditText etxtPassword;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(UserPreferences.getNoTitleTheme());
|
||||
setTheme(UserPreferences.getTranslucentTheme());
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.download_authentication_activity);
|
||||
TextView txtvDescription = findViewById(R.id.txtvDescription);
|
||||
etxtUsername = findViewById(R.id.etxtUsername);
|
||||
etxtPassword = findViewById(R.id.etxtPassword);
|
||||
Button butConfirm = findViewById(R.id.butConfirm);
|
||||
Button butCancel = findViewById(R.id.butCancel);
|
||||
|
||||
Validate.isTrue(getIntent().hasExtra(ARG_DOWNLOAD_REQUEST), "Download request missing");
|
||||
DownloadRequest request = getIntent().getParcelableExtra(ARG_DOWNLOAD_REQUEST);
|
||||
boolean sendToDownloadRequester = getIntent().getBooleanExtra(ARG_SEND_TO_DOWNLOAD_REQUESTER_BOOL, false);
|
||||
|
||||
String newDescription = txtvDescription.getText() + ":\n\n" + request.getTitle();
|
||||
txtvDescription.setText(newDescription);
|
||||
new AuthenticationDialog(this, R.string.authentication_label, true, "", "") {
|
||||
@Override
|
||||
protected void onConfirmed(String username, String password) {
|
||||
Completable.fromAction(
|
||||
() -> {
|
||||
request.setUsername(username);
|
||||
request.setPassword(password);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
etxtUsername.setText(savedInstanceState.getString("username"));
|
||||
etxtPassword.setText(savedInstanceState.getString("password"));
|
||||
}
|
||||
|
||||
butConfirm.setOnClickListener(v -> {
|
||||
String username = etxtUsername.getText().toString();
|
||||
String password = etxtPassword.getText().toString();
|
||||
request.setUsername(username);
|
||||
request.setPassword(password);
|
||||
Intent result = new Intent();
|
||||
result.putExtra(RESULT_REQUEST, request);
|
||||
setResult(Activity.RESULT_OK, result);
|
||||
|
||||
if (sendToDownloadRequester) {
|
||||
DownloadRequester.getInstance().download(DownloadAuthenticationActivity.this, request);
|
||||
if (request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
|
||||
long mediaId = request.getFeedfileId();
|
||||
FeedMedia media = DBReader.getFeedMedia(mediaId);
|
||||
if (media != null) {
|
||||
FeedPreferences preferences = media.getItem().getFeed().getPreferences();
|
||||
if (TextUtils.isEmpty(preferences.getPassword())
|
||||
|| TextUtils.isEmpty(preferences.getUsername())) {
|
||||
preferences.setUsername(username);
|
||||
preferences.setPassword(password);
|
||||
DBWriter.setFeedPreferences(preferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(() -> {
|
||||
DownloadRequester.getInstance().download(DownloadAuthenticationActivity.this, request);
|
||||
finish();
|
||||
});
|
||||
}
|
||||
finish();
|
||||
});
|
||||
|
||||
butCancel.setOnClickListener(v -> {
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
finish();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString("username", etxtUsername.getText().toString());
|
||||
outState.putString("password", etxtPassword.getText().toString());
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
finish();
|
||||
}
|
||||
}.show();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.content.Intent;
|
|||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
|
@ -40,7 +41,7 @@ import de.danoeh.antennapod.core.preferences.UserPreferences;
|
|||
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.util.StorageUtils;
|
||||
import de.danoeh.antennapod.core.util.ThemeUtils;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
|
||||
import de.danoeh.antennapod.dialog.RatingDialog;
|
||||
import de.danoeh.antennapod.fragment.AddFeedFragment;
|
||||
|
@ -51,9 +52,11 @@ import de.danoeh.antennapod.fragment.FeedItemlistFragment;
|
|||
import de.danoeh.antennapod.fragment.NavDrawerFragment;
|
||||
import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
|
||||
import de.danoeh.antennapod.fragment.QueueFragment;
|
||||
import de.danoeh.antennapod.fragment.SearchFragment;
|
||||
import de.danoeh.antennapod.fragment.SubscriptionFragment;
|
||||
import de.danoeh.antennapod.fragment.TransitionEffect;
|
||||
import de.danoeh.antennapod.preferences.PreferenceUpgrader;
|
||||
import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
|
||||
import de.danoeh.antennapod.view.LockableBottomSheetBehavior;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
@ -75,7 +78,6 @@ public class MainActivity extends CastEnabledActivity {
|
|||
public static final String EXTRA_FRAGMENT_TAG = "fragment_tag";
|
||||
public static final String EXTRA_FRAGMENT_ARGS = "fragment_args";
|
||||
public static final String EXTRA_FEED_ID = "fragment_feed_id";
|
||||
public static final String EXTRA_OPEN_PLAYER = "open_player";
|
||||
public static final String EXTRA_REFRESH_ON_START = "refresh_on_start";
|
||||
public static final String EXTRA_STARTED_FROM_SEARCH = "started_from_search";
|
||||
public static final String KEY_GENERATED_VIEW_ID = "generated_view_id";
|
||||
|
@ -184,16 +186,16 @@ public class MainActivity extends CastEnabledActivity {
|
|||
}
|
||||
};
|
||||
|
||||
public void setupToolbarToggle(@Nullable Toolbar toolbar) {
|
||||
public void setupToolbarToggle(@NonNull Toolbar toolbar, boolean displayUpArrow) {
|
||||
if (drawerLayout != null) { // Tablet layout does not have a drawer
|
||||
drawerLayout.removeDrawerListener(drawerToggle);
|
||||
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar,
|
||||
R.string.drawer_open, R.string.drawer_close);
|
||||
drawerLayout.addDrawerListener(drawerToggle);
|
||||
drawerToggle.syncState();
|
||||
drawerToggle.setDrawerIndicatorEnabled(getSupportFragmentManager().getBackStackEntryCount() == 0);
|
||||
drawerToggle.setDrawerIndicatorEnabled(!displayUpArrow);
|
||||
drawerToggle.setToolbarNavigationClickListener(v -> getSupportFragmentManager().popBackStack());
|
||||
} else if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
||||
} else if (!displayUpArrow) {
|
||||
toolbar.setNavigationIcon(null);
|
||||
} else {
|
||||
toolbar.setNavigationIcon(ThemeUtils.getDrawableFromAttr(this, R.attr.homeAsUpIndicator));
|
||||
|
@ -508,9 +510,11 @@ public class MainActivity extends CastEnabledActivity {
|
|||
}
|
||||
}
|
||||
sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
} else if (intent.getBooleanExtra(EXTRA_OPEN_PLAYER, false)) {
|
||||
} else if (intent.getBooleanExtra(MainActivityStarter.EXTRA_OPEN_PLAYER, false)) {
|
||||
sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
|
||||
bottomSheetCallback.onSlide(null, 1.0f);
|
||||
} else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
|
||||
handleDeeplink(intent.getData());
|
||||
}
|
||||
// to avoid handling the intent twice when the configuration changes
|
||||
setIntent(new Intent(MainActivity.this, MainActivity.class));
|
||||
|
@ -520,6 +524,7 @@ public class MainActivity extends CastEnabledActivity {
|
|||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
handleNavIntent();
|
||||
}
|
||||
|
||||
public Snackbar showSnackbarAbovePlayer(CharSequence text, int duration) {
|
||||
|
@ -540,6 +545,59 @@ public class MainActivity extends CastEnabledActivity {
|
|||
return showSnackbarAbovePlayer(getResources().getText(text), duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the deep link incoming via App Actions.
|
||||
* Performs an in-app search or opens the relevant feature of the app
|
||||
* depending on the query.
|
||||
*
|
||||
* @param uri incoming deep link
|
||||
*/
|
||||
private void handleDeeplink(Uri uri) {
|
||||
if (uri == null || uri.getPath() == null) {
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "Handling deeplink: " + uri.toString());
|
||||
switch (uri.getPath()) {
|
||||
case "/deeplink/search":
|
||||
String query = uri.getQueryParameter("query");
|
||||
if (query == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadChildFragment(SearchFragment.newInstance(query));
|
||||
break;
|
||||
case "/deeplink/main":
|
||||
String feature = uri.getQueryParameter("page");
|
||||
if (feature == null) {
|
||||
return;
|
||||
}
|
||||
switch (feature) {
|
||||
case "DOWNLOADS":
|
||||
loadFragment(DownloadsFragment.TAG, null);
|
||||
break;
|
||||
case "HISTORY":
|
||||
loadFragment(PlaybackHistoryFragment.TAG, null);
|
||||
break;
|
||||
case "EPISODES":
|
||||
loadFragment(EpisodesFragment.TAG, null);
|
||||
break;
|
||||
case "QUEUE":
|
||||
loadFragment(QueueFragment.TAG, null);
|
||||
break;
|
||||
case "SUBSCRIPTIONS":
|
||||
loadFragment(SubscriptionFragment.TAG, null);
|
||||
break;
|
||||
default:
|
||||
showSnackbarAbovePlayer(getString(R.string.app_action_not_found, feature),
|
||||
Snackbar.LENGTH_LONG);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Hardware keyboard support
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
|
@ -592,5 +650,4 @@ public class MainActivity extends CastEnabledActivity {
|
|||
}
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
package de.danoeh.antennapod.activity;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
@ -17,7 +15,6 @@ import android.widget.ImageButton;
|
|||
import android.widget.SeekBar;
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
|
||||
|
@ -28,17 +25,16 @@ import org.greenrobot.eventbus.ThreadMode;
|
|||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import androidx.core.app.ActivityOptionsCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.feed.MediaType;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
|
@ -50,11 +46,9 @@ import de.danoeh.antennapod.core.util.ShareUtils;
|
|||
import de.danoeh.antennapod.core.util.StorageUtils;
|
||||
import de.danoeh.antennapod.core.util.TimeSpeedConverter;
|
||||
import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil;
|
||||
import de.danoeh.antennapod.core.util.playback.ExternalMedia;
|
||||
import de.danoeh.antennapod.core.util.playback.MediaPlayerError;
|
||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
|
||||
import de.danoeh.antennapod.dialog.PlaybackControlsDialog;
|
||||
import de.danoeh.antennapod.dialog.ShareDialog;
|
||||
import de.danoeh.antennapod.dialog.SkipPreferenceDialog;
|
||||
|
@ -64,7 +58,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
|
|||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
|
||||
/**
|
||||
* Provides general features which are both needed for playing audio and video
|
||||
* files.
|
||||
|
@ -72,9 +65,6 @@ import io.reactivex.schedulers.Schedulers;
|
|||
public abstract class MediaplayerActivity extends CastEnabledActivity implements OnSeekBarChangeListener {
|
||||
private static final String TAG = "MediaplayerActivity";
|
||||
private static final String PREFS = "MediaPlayerActivityPreferences";
|
||||
private static final String PREF_SHOW_TIME_LEFT = "showTimeLeft";
|
||||
private static final int REQUEST_CODE_STORAGE_PLAY_VIDEO = 42;
|
||||
private static final int REQUEST_CODE_STORAGE_PLAY_AUDIO = 43;
|
||||
|
||||
PlaybackController controller;
|
||||
|
||||
|
@ -87,6 +77,8 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
|||
private ImageButton butFF;
|
||||
private TextView txtvFF;
|
||||
private ImageButton butSkip;
|
||||
private CardView cardViewSeek;
|
||||
private TextView txtvSeek;
|
||||
|
||||
private boolean showTimeLeft = false;
|
||||
|
||||
|
@ -96,12 +88,6 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
|||
|
||||
private PlaybackController newPlaybackController() {
|
||||
return new PlaybackController(this) {
|
||||
|
||||
@Override
|
||||
public void setupGUI() {
|
||||
MediaplayerActivity.this.setupGUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPositionObserverUpdate() {
|
||||
MediaplayerActivity.this.onPositionObserverUpdate();
|
||||
|
@ -143,8 +129,8 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean loadMediaInfo() {
|
||||
return MediaplayerActivity.this.loadMediaInfo();
|
||||
public void loadMediaInfo() {
|
||||
MediaplayerActivity.this.loadMediaInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -467,17 +453,15 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
|||
* to the PlaybackService to ensure that the activity has the right
|
||||
* FeedMedia object.
|
||||
*/
|
||||
boolean loadMediaInfo() {
|
||||
void loadMediaInfo() {
|
||||
Log.d(TAG, "loadMediaInfo()");
|
||||
if(controller == null || controller.getMedia() == null) {
|
||||
return false;
|
||||
if (controller == null || controller.getMedia() == null) {
|
||||
return;
|
||||
}
|
||||
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
showTimeLeft = prefs.getBoolean(PREF_SHOW_TIME_LEFT, false);
|
||||
showTimeLeft = UserPreferences.shouldShowRemainingTime();
|
||||
onPositionObserverUpdate();
|
||||
checkFavorite();
|
||||
updatePlaybackSpeedButton();
|
||||
return true;
|
||||
}
|
||||
|
||||
void updatePlaybackSpeedButton() {
|
||||
|
@ -492,9 +476,11 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
|||
setContentView(getContentViewResourceId());
|
||||
sbPosition = findViewById(R.id.sbPosition);
|
||||
txtvPosition = findViewById(R.id.txtvPosition);
|
||||
cardViewSeek = findViewById(R.id.cardViewSeek);
|
||||
txtvSeek = findViewById(R.id.txtvSeek);
|
||||
|
||||
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
showTimeLeft = prefs.getBoolean(PREF_SHOW_TIME_LEFT, false);
|
||||
showTimeLeft = UserPreferences.shouldShowRemainingTime();
|
||||
Log.d("timeleft", showTimeLeft ? "true" : "false");
|
||||
txtvLength = findViewById(R.id.txtvLength);
|
||||
if (txtvLength != null) {
|
||||
|
@ -518,9 +504,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
|||
}
|
||||
txtvLength.setText(length);
|
||||
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putBoolean(PREF_SHOW_TIME_LEFT, showTimeLeft);
|
||||
editor.apply();
|
||||
UserPreferences.setShowRemainTimeSetting(showTimeLeft);
|
||||
Log.d("timeleft on click", showTimeLeft ? "true" : "false");
|
||||
});
|
||||
}
|
||||
|
@ -618,21 +602,21 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
|||
}
|
||||
if (fromUser) {
|
||||
prog = progress / ((float) seekBar.getMax());
|
||||
int duration = controller.getDuration();
|
||||
TimeSpeedConverter converter = new TimeSpeedConverter(controller.getCurrentPlaybackSpeedMultiplier());
|
||||
int position = converter.convert((int) (prog * duration));
|
||||
txtvPosition.setText(Converter.getDurationStringLong(position));
|
||||
|
||||
if (showTimeLeft) {
|
||||
int timeLeft = converter.convert(duration - (int) (prog * duration));
|
||||
txtvLength.setText("-" + Converter.getDurationStringLong(timeLeft));
|
||||
}
|
||||
int position = converter.convert((int) (prog * controller.getDuration()));
|
||||
txtvSeek.setText(Converter.getDurationStringLong(position));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
|
||||
cardViewSeek.setScaleX(.8f);
|
||||
cardViewSeek.setScaleY(.8f);
|
||||
cardViewSeek.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.alpha(1f).scaleX(1f).scaleY(1f)
|
||||
.setDuration(200)
|
||||
.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -640,6 +624,13 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
|||
if (controller != null) {
|
||||
controller.seekTo((int) (prog * controller.getDuration()));
|
||||
}
|
||||
cardViewSeek.setScaleX(1f);
|
||||
cardViewSeek.setScaleY(1f);
|
||||
cardViewSeek.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.alpha(0f).scaleX(.8f).scaleY(.8f)
|
||||
.setDuration(200)
|
||||
.start();
|
||||
}
|
||||
|
||||
private void checkFavorite() {
|
||||
|
@ -663,50 +654,6 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
|||
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
|
||||
}
|
||||
|
||||
void playExternalMedia(Intent intent, MediaType type) {
|
||||
if (intent == null || intent.getData() == null) {
|
||||
return;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 23
|
||||
&& ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
|
||||
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
|
||||
Toast.makeText(this, R.string.needs_storage_permission, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
int code = REQUEST_CODE_STORAGE_PLAY_AUDIO;
|
||||
if (type == MediaType.VIDEO) {
|
||||
code = REQUEST_CODE_STORAGE_PLAY_VIDEO;
|
||||
}
|
||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, code);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Received VIEW intent: " + intent.getData().getPath());
|
||||
ExternalMedia media = new ExternalMedia(intent.getData().getPath(), type);
|
||||
|
||||
new PlaybackServiceStarter(this, media)
|
||||
.callEvenIfRunning(true)
|
||||
.startWhenPrepared(true)
|
||||
.shouldStream(false)
|
||||
.prepareImmediately(true)
|
||||
.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, int[] grantResults) {
|
||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
if (requestCode == REQUEST_CODE_STORAGE_PLAY_AUDIO) {
|
||||
playExternalMedia(getIntent(), MediaType.AUDIO);
|
||||
} else if (requestCode == REQUEST_CODE_STORAGE_PLAY_VIDEO) {
|
||||
playExternalMedia(getIntent(), MediaType.VIDEO);
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this, R.string.needs_storage_permission, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static FeedItem getFeedItem(@Nullable Playable playable) {
|
||||
if (playable instanceof FeedMedia) {
|
||||
|
|
|
@ -4,10 +4,14 @@ import android.app.Dialog;
|
|||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.LightingColorFilter;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
@ -15,6 +19,7 @@ import android.widget.AdapterView;
|
|||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
@ -28,7 +33,6 @@ import de.danoeh.antennapod.core.event.DownloadEvent;
|
|||
import de.danoeh.antennapod.core.event.FeedListUpdateEvent;
|
||||
import de.danoeh.antennapod.core.event.PlayerStatusEvent;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
||||
import de.danoeh.antennapod.core.feed.VolumeAdaptionSetting;
|
||||
import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
||||
|
@ -41,6 +45,7 @@ import de.danoeh.antennapod.core.service.download.Downloader;
|
|||
import de.danoeh.antennapod.core.service.download.HttpDownloader;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequestException;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||
import de.danoeh.antennapod.core.syndication.handler.FeedHandler;
|
||||
|
@ -49,7 +54,6 @@ import de.danoeh.antennapod.core.syndication.handler.UnsupportedFeedtypeExceptio
|
|||
import de.danoeh.antennapod.core.util.DownloadError;
|
||||
import de.danoeh.antennapod.core.util.FileNameGenerator;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import de.danoeh.antennapod.core.util.Optional;
|
||||
import de.danoeh.antennapod.core.util.StorageUtils;
|
||||
import de.danoeh.antennapod.core.util.URLChecker;
|
||||
import de.danoeh.antennapod.core.util.playback.RemoteMedia;
|
||||
|
@ -58,9 +62,11 @@ import de.danoeh.antennapod.core.util.syndication.HtmlToPlainText;
|
|||
import de.danoeh.antennapod.databinding.OnlinefeedviewActivityBinding;
|
||||
import de.danoeh.antennapod.dialog.AuthenticationDialog;
|
||||
import de.danoeh.antennapod.discovery.PodcastSearcherRegistry;
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.observers.DisposableMaybeObserver;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
@ -87,6 +93,9 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
|
|||
// Optional argument: specify a title for the actionbar.
|
||||
private static final int RESULT_ERROR = 2;
|
||||
private static final String TAG = "OnlineFeedViewActivity";
|
||||
private static final String PREFS = "OnlineFeedViewActivityPreferences";
|
||||
private static final String PREF_LAST_AUTO_DOWNLOAD = "lastAutoDownload";
|
||||
|
||||
private volatile List<Feed> feeds;
|
||||
private Feed feed;
|
||||
private String selectedDownloadUrl;
|
||||
|
@ -248,7 +257,8 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
|
|||
url = URLChecker.prepareURL(url);
|
||||
feed = new Feed(url, null);
|
||||
if (username != null && password != null) {
|
||||
feed.setPreferences(new FeedPreferences(0, false, FeedPreferences.AutoDeleteAction.GLOBAL, VolumeAdaptionSetting.OFF, username, password));
|
||||
feed.setPreferences(new FeedPreferences(0, false, FeedPreferences.AutoDeleteAction.GLOBAL,
|
||||
VolumeAdaptionSetting.OFF, username, password));
|
||||
}
|
||||
String fileUrl = new File(getExternalCacheDir(),
|
||||
FileNameGenerator.generateFileName(feed.getDownload_url())).toString();
|
||||
|
@ -283,11 +293,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
|
|||
dialog.show();
|
||||
}
|
||||
} else {
|
||||
String errorMsg = status.getReason().getErrorString(OnlineFeedViewActivity.this);
|
||||
if (status.getReasonDetailed() != null) {
|
||||
errorMsg += " (" + status.getReasonDetailed() + ")";
|
||||
}
|
||||
showErrorDialog(errorMsg);
|
||||
showErrorDialog(status.getReason().getErrorString(OnlineFeedViewActivity.this), status.getReasonDetailed());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -316,37 +322,47 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
|
|||
}
|
||||
Log.d(TAG, "Parsing feed");
|
||||
|
||||
parser = Observable.fromCallable(this::doParseFeed)
|
||||
parser = Maybe.fromCallable(this::doParseFeed)
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(optionalResult -> {
|
||||
if(optionalResult.isPresent()) {
|
||||
FeedHandlerResult result = optionalResult.get();
|
||||
beforeShowFeedInformation(result.feed);
|
||||
.subscribeWith(new DisposableMaybeObserver<FeedHandlerResult>() {
|
||||
@Override
|
||||
public void onSuccess(@NonNull FeedHandlerResult result) {
|
||||
showFeedInformation(result.feed, result.alternateFeedUrls);
|
||||
}
|
||||
}, error -> {
|
||||
String errorMsg = DownloadError.ERROR_PARSER_EXCEPTION.getErrorString(
|
||||
OnlineFeedViewActivity.this) + " (" + error.getMessage() + ")";
|
||||
showErrorDialog(errorMsg);
|
||||
Log.d(TAG, "Feed parser exception: " + Log.getStackTraceString(error));
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
// Ignore null result: We showed the discovery dialog.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable error) {
|
||||
showErrorDialog(error.getMessage(), "");
|
||||
Log.d(TAG, "Feed parser exception: " + Log.getStackTraceString(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Optional<FeedHandlerResult> doParseFeed() throws Exception {
|
||||
/**
|
||||
* Try to parse the feed.
|
||||
* @return The FeedHandlerResult if successful.
|
||||
* Null if unsuccessful but we started another attempt.
|
||||
* @throws Exception If unsuccessful but we do not know a resolution.
|
||||
*/
|
||||
@Nullable
|
||||
private FeedHandlerResult doParseFeed() throws Exception {
|
||||
FeedHandler handler = new FeedHandler();
|
||||
try {
|
||||
return Optional.of(handler.parseFeed(feed));
|
||||
return handler.parseFeed(feed);
|
||||
} catch (UnsupportedFeedtypeException e) {
|
||||
Log.d(TAG, "Unsupported feed type detected");
|
||||
if ("html".equalsIgnoreCase(e.getRootElement())) {
|
||||
boolean dialogShown = showFeedDiscoveryDialog(new File(feed.getFile_url()), feed.getDownload_url());
|
||||
if (dialogShown) {
|
||||
return Optional.empty();
|
||||
return null; // Should not display an error message
|
||||
} else {
|
||||
Log.d(TAG, "Supplied feed is an HTML web page that has no references to any feed");
|
||||
throw e;
|
||||
throw new UnsupportedFeedtypeException(getString(R.string.download_error_unsupported_type_html));
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
|
@ -360,23 +376,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the feed has been downloaded and parsed and before showFeedInformation is called.
|
||||
* This method is executed on a background thread
|
||||
*/
|
||||
private void beforeShowFeedInformation(Feed feed) {
|
||||
Log.d(TAG, "Removing HTML from feed description");
|
||||
|
||||
feed.setDescription(HtmlToPlainText.getPlainText(feed.getDescription()));
|
||||
|
||||
Log.d(TAG, "Removing HTML from shownotes");
|
||||
if (feed.getItems() != null) {
|
||||
for (FeedItem item : feed.getItems()) {
|
||||
item.setDescription(HtmlToPlainText.getPlainText(item.getDescription()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when feed parsed successfully.
|
||||
* This method is executed on the GUI thread.
|
||||
|
@ -420,7 +419,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
|
|||
|
||||
viewBinding.titleLabel.setText(feed.getTitle());
|
||||
viewBinding.authorLabel.setText(feed.getAuthor());
|
||||
description.setText(feed.getDescription());
|
||||
description.setText(HtmlToPlainText.getPlainText(feed.getDescription()));
|
||||
|
||||
viewBinding.subscribeButton.setOnClickListener(v -> {
|
||||
if (feedInFeedlist(feed)) {
|
||||
|
@ -445,6 +444,11 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
|
|||
IntentUtils.sendLocalBroadcast(this, PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE);
|
||||
});
|
||||
|
||||
if (UserPreferences.isEnableAutodownload()) {
|
||||
SharedPreferences preferences = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
viewBinding.autoDownloadCheckBox.setChecked(preferences.getBoolean(PREF_LAST_AUTO_DOWNLOAD, true));
|
||||
}
|
||||
|
||||
final int MAX_LINES_COLLAPSED = 10;
|
||||
description.setMaxLines(MAX_LINES_COLLAPSED);
|
||||
description.setOnClickListener(v -> {
|
||||
|
@ -511,10 +515,17 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
|
|||
if (didPressSubscribe) {
|
||||
didPressSubscribe = false;
|
||||
if (UserPreferences.isEnableAutodownload()) {
|
||||
boolean autoDownload = viewBinding.autoDownloadCheckBox.isChecked();
|
||||
|
||||
Feed feed1 = DBReader.getFeed(getFeedId(feed));
|
||||
FeedPreferences feedPreferences = feed1.getPreferences();
|
||||
feedPreferences.setAutoDownload(viewBinding.autoDownloadCheckBox.isChecked());
|
||||
feed1.savePreferences();
|
||||
feedPreferences.setAutoDownload(autoDownload);
|
||||
DBWriter.setFeedPreferences(feedPreferences);
|
||||
|
||||
SharedPreferences preferences = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putBoolean(PREF_LAST_AUTO_DOWNLOAD, autoDownload);
|
||||
editor.apply();
|
||||
}
|
||||
openFeed();
|
||||
}
|
||||
|
@ -553,12 +564,16 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
@UiThread
|
||||
private void showErrorDialog(String errorMsg) {
|
||||
private void showErrorDialog(String errorMsg, String details) {
|
||||
if (!isFinishing() && !isPaused) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.error_label);
|
||||
if (errorMsg != null) {
|
||||
builder.setMessage(errorMsg);
|
||||
String total = errorMsg + "\n\n" + details;
|
||||
SpannableString errorMessage = new SpannableString(total);
|
||||
errorMessage.setSpan(new ForegroundColorSpan(0x88888888),
|
||||
errorMsg.length(), total.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
builder.setMessage(errorMessage);
|
||||
} else {
|
||||
builder.setMessage(R.string.download_error_error_unknown);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ import android.widget.ImageView;
|
|||
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.Menu;
|
||||
|
@ -37,12 +36,12 @@ import java.lang.ref.WeakReference;
|
|||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.feed.MediaType;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.service.playback.PlayerStatus;
|
||||
import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil;
|
||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||
import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
|
||||
import de.danoeh.antennapod.view.AspectRatioVideoView;
|
||||
|
||||
/**
|
||||
|
@ -88,9 +87,7 @@ public class VideoplayerActivity extends MediaplayerActivity {
|
|||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (TextUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) {
|
||||
playExternalMedia(getIntent(), MediaType.VIDEO);
|
||||
} else if (PlaybackService.isCasting()) {
|
||||
if (PlaybackService.isCasting()) {
|
||||
Intent intent = PlaybackService.getPlayerActivityIntent(this);
|
||||
if (!intent.getComponent().getClassName().equals(VideoplayerActivity.class.getName())) {
|
||||
destroyingDueToReload = true;
|
||||
|
@ -135,17 +132,13 @@ public class VideoplayerActivity extends MediaplayerActivity {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected boolean loadMediaInfo() {
|
||||
if (!super.loadMediaInfo() || controller == null) {
|
||||
return false;
|
||||
}
|
||||
protected void loadMediaInfo() {
|
||||
super.loadMediaInfo();
|
||||
Playable media = controller.getMedia();
|
||||
if (media != null) {
|
||||
getSupportActionBar().setSubtitle(media.getEpisodeTitle());
|
||||
getSupportActionBar().setTitle(media.getFeedTitle());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -347,7 +340,7 @@ public class VideoplayerActivity extends MediaplayerActivity {
|
|||
Log.d(TAG, "ReloadNotification received, switching to Castplayer now");
|
||||
destroyingDueToReload = true;
|
||||
finish();
|
||||
startActivity(new Intent(this, MainActivity.class).putExtra(MainActivity.EXTRA_OPEN_PLAYER, true));
|
||||
new MainActivityStarter(this).withOpenPlayer().start();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,33 +2,34 @@ package de.danoeh.antennapod.activity;
|
|||
|
||||
import android.Manifest;
|
||||
import android.app.WallpaperManager;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.widget.ImageView;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.receiver.PlayerWidget;
|
||||
import de.danoeh.antennapod.core.service.PlayerWidgetJobService;
|
||||
import de.danoeh.antennapod.core.widget.WidgetUpdaterJobService;
|
||||
|
||||
public class WidgetConfigActivity extends AppCompatActivity {
|
||||
private int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
|
||||
|
||||
private SeekBar opacitySeekBar;
|
||||
private TextView opacityTextView;
|
||||
private RelativeLayout widgetPreview;
|
||||
private View widgetPreview;
|
||||
private CheckBox ckRewind;
|
||||
private CheckBox ckFastForward;
|
||||
private CheckBox ckSkip;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
@ -73,6 +74,32 @@ public class WidgetConfigActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
});
|
||||
|
||||
widgetPreview.findViewById(R.id.txtNoPlaying).setVisibility(View.GONE);
|
||||
TextView title = widgetPreview.findViewById(R.id.txtvTitle);
|
||||
title.setVisibility(View.VISIBLE);
|
||||
title.setText(R.string.app_name);
|
||||
TextView progress = widgetPreview.findViewById(R.id.txtvProgress);
|
||||
progress.setVisibility(View.VISIBLE);
|
||||
progress.setText(R.string.position_default_label);
|
||||
|
||||
ckRewind = findViewById(R.id.ckRewind);
|
||||
ckRewind.setOnClickListener(v -> displayPreviewPanel());
|
||||
ckFastForward = findViewById(R.id.ckFastForward);
|
||||
ckFastForward.setOnClickListener(v -> displayPreviewPanel());
|
||||
ckSkip = findViewById(R.id.ckSkip);
|
||||
ckSkip.setOnClickListener(v -> displayPreviewPanel());
|
||||
}
|
||||
|
||||
private void displayPreviewPanel() {
|
||||
boolean showExtendedPreview = ckRewind.isChecked() || ckFastForward.isChecked() || ckSkip.isChecked();
|
||||
widgetPreview.findViewById(R.id.extendedButtonsContainer)
|
||||
.setVisibility(showExtendedPreview ? View.VISIBLE : View.GONE);
|
||||
widgetPreview.findViewById(R.id.butPlay).setVisibility(showExtendedPreview ? View.GONE : View.VISIBLE);
|
||||
widgetPreview.findViewById(R.id.butFastForward)
|
||||
.setVisibility(ckFastForward.isChecked() ? View.VISIBLE : View.GONE);
|
||||
widgetPreview.findViewById(R.id.butSkip).setVisibility(ckSkip.isChecked() ? View.VISIBLE : View.GONE);
|
||||
widgetPreview.findViewById(R.id.butRew).setVisibility(ckRewind.isChecked() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void displayDeviceBackground() {
|
||||
|
@ -91,13 +118,16 @@ public class WidgetConfigActivity extends AppCompatActivity {
|
|||
SharedPreferences prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(PlayerWidget.KEY_WIDGET_COLOR + appWidgetId, backgroundColor);
|
||||
editor.putBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, ckSkip.isChecked());
|
||||
editor.putBoolean(PlayerWidget.KEY_WIDGET_REWIND + appWidgetId, ckRewind.isChecked());
|
||||
editor.putBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + appWidgetId, ckFastForward.isChecked());
|
||||
editor.apply();
|
||||
|
||||
Intent resultValue = new Intent();
|
||||
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
|
||||
setResult(RESULT_OK, resultValue);
|
||||
finish();
|
||||
PlayerWidgetJobService.updateWidget(this);
|
||||
WidgetUpdaterJobService.performBackgroundUpdate(this);
|
||||
}
|
||||
|
||||
private int getColorWithAlpha(int color, int opacity) {
|
||||
|
|
|
@ -1,395 +0,0 @@
|
|||
package de.danoeh.antennapod.activity.gpoddernet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ViewFlipper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import de.danoeh.antennapod.BuildConfig;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
|
||||
import de.danoeh.antennapod.core.sync.SyncService;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetDevice;
|
||||
|
||||
/**
|
||||
* Guides the user through the authentication process
|
||||
* Step 1: Request username and password from user
|
||||
* Step 2: Choose device from a list of available devices or create a new one
|
||||
* Step 3: Choose from a list of actions
|
||||
*/
|
||||
public class GpodnetAuthenticationActivity extends AppCompatActivity {
|
||||
private static final String TAG = "GpodnetAuthActivity";
|
||||
|
||||
private ViewFlipper viewFlipper;
|
||||
|
||||
private static final int STEP_DEFAULT = -1;
|
||||
private static final int STEP_LOGIN = 0;
|
||||
private static final int STEP_DEVICE = 1;
|
||||
private static final int STEP_FINISH = 2;
|
||||
|
||||
private int currentStep = -1;
|
||||
|
||||
private GpodnetService service;
|
||||
private volatile String username;
|
||||
private volatile String password;
|
||||
private volatile GpodnetDevice selectedDevice;
|
||||
|
||||
private View[] views;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(UserPreferences.getTheme());
|
||||
super.onCreate(savedInstanceState);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
setContentView(R.layout.gpodnetauth_activity);
|
||||
service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetPreferences.getHostname());
|
||||
|
||||
viewFlipper = findViewById(R.id.viewflipper);
|
||||
LayoutInflater inflater = (LayoutInflater)
|
||||
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
views = new View[]{
|
||||
inflater.inflate(R.layout.gpodnetauth_credentials, viewFlipper, false),
|
||||
inflater.inflate(R.layout.gpodnetauth_device, viewFlipper, false),
|
||||
inflater.inflate(R.layout.gpodnetauth_finish, viewFlipper, false)
|
||||
};
|
||||
for (View view : views) {
|
||||
viewFlipper.addView(view);
|
||||
}
|
||||
advance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void setupLoginView(View view) {
|
||||
final EditText username = view.findViewById(R.id.etxtUsername);
|
||||
final EditText password = view.findViewById(R.id.etxtPassword);
|
||||
final Button login = view.findViewById(R.id.butLogin);
|
||||
final TextView txtvError = view.findViewById(R.id.txtvError);
|
||||
final ProgressBar progressBar = view.findViewById(R.id.progBarLogin);
|
||||
|
||||
password.setOnEditorActionListener((v, actionID, event) ->
|
||||
actionID == EditorInfo.IME_ACTION_GO && login.performClick());
|
||||
|
||||
login.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
final String usernameStr = username.getText().toString();
|
||||
final String passwordStr = password.getText().toString();
|
||||
|
||||
if (usernameHasUnwantedChars(usernameStr)) {
|
||||
txtvError.setText(R.string.gpodnetsync_username_characters_error);
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Checking login credentials");
|
||||
AsyncTask<GpodnetService, Void, Void> authTask = new AsyncTask<GpodnetService, Void, Void>() {
|
||||
|
||||
volatile Exception exception;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
login.setEnabled(false);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
txtvError.setVisibility(View.GONE);
|
||||
// hide the keyboard
|
||||
InputMethodManager inputManager = (InputMethodManager)
|
||||
getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
inputManager.hideSoftInputFromWindow(login.getWindowToken(),
|
||||
InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
super.onPostExecute(aVoid);
|
||||
login.setEnabled(true);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
|
||||
if (exception == null) {
|
||||
advance();
|
||||
} else {
|
||||
txtvError.setText(exception.getCause().getMessage());
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(GpodnetService... params) {
|
||||
try {
|
||||
params[0].authenticate(usernameStr, passwordStr);
|
||||
GpodnetAuthenticationActivity.this.username = usernameStr;
|
||||
GpodnetAuthenticationActivity.this.password = passwordStr;
|
||||
} catch (GpodnetServiceException e) {
|
||||
e.printStackTrace();
|
||||
exception = e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
authTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, service);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupDeviceView(View view) {
|
||||
final EditText deviceID = view.findViewById(R.id.etxtDeviceID);
|
||||
final EditText caption = view.findViewById(R.id.etxtCaption);
|
||||
final Button createNewDevice = view.findViewById(R.id.butCreateNewDevice);
|
||||
final Button chooseDevice = view.findViewById(R.id.butChooseExistingDevice);
|
||||
final TextView txtvError = view.findViewById(R.id.txtvError);
|
||||
final ProgressBar progBarCreateDevice = view.findViewById(R.id.progbarCreateDevice);
|
||||
final Spinner spinnerDevices = view.findViewById(R.id.spinnerChooseDevice);
|
||||
|
||||
|
||||
// load device list
|
||||
final AtomicReference<List<GpodnetDevice>> devices = new AtomicReference<>();
|
||||
new AsyncTask<GpodnetService, Void, List<GpodnetDevice>>() {
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
chooseDevice.setEnabled(false);
|
||||
spinnerDevices.setEnabled(false);
|
||||
createNewDevice.setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<GpodnetDevice> gpodnetDevices) {
|
||||
super.onPostExecute(gpodnetDevices);
|
||||
if (gpodnetDevices != null) {
|
||||
List<String> deviceNames = new ArrayList<>();
|
||||
for (GpodnetDevice device : gpodnetDevices) {
|
||||
deviceNames.add(device.getCaption());
|
||||
}
|
||||
spinnerDevices.setAdapter(new ArrayAdapter<>(GpodnetAuthenticationActivity.this,
|
||||
android.R.layout.simple_spinner_dropdown_item, deviceNames));
|
||||
spinnerDevices.setEnabled(true);
|
||||
if (!deviceNames.isEmpty()) {
|
||||
chooseDevice.setEnabled(true);
|
||||
}
|
||||
devices.set(gpodnetDevices);
|
||||
deviceID.setText(generateDeviceID(gpodnetDevices));
|
||||
createNewDevice.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<GpodnetDevice> doInBackground(GpodnetService... params) {
|
||||
try {
|
||||
return params[0].getDevices();
|
||||
} catch (GpodnetServiceException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}.execute(service);
|
||||
|
||||
|
||||
createNewDevice.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (checkDeviceIDText(deviceID, caption, txtvError, devices.get())) {
|
||||
final String deviceStr = deviceID.getText().toString();
|
||||
final String captionStr = caption.getText().toString();
|
||||
|
||||
new AsyncTask<GpodnetService, Void, GpodnetDevice>() {
|
||||
|
||||
private volatile Exception exception;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
createNewDevice.setEnabled(false);
|
||||
chooseDevice.setEnabled(false);
|
||||
progBarCreateDevice.setVisibility(View.VISIBLE);
|
||||
txtvError.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(GpodnetDevice result) {
|
||||
super.onPostExecute(result);
|
||||
createNewDevice.setEnabled(true);
|
||||
chooseDevice.setEnabled(true);
|
||||
progBarCreateDevice.setVisibility(View.GONE);
|
||||
if (exception == null) {
|
||||
selectedDevice = result;
|
||||
advance();
|
||||
} else {
|
||||
txtvError.setText(exception.getMessage());
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GpodnetDevice doInBackground(GpodnetService... params) {
|
||||
try {
|
||||
params[0].configureDevice(deviceStr, captionStr, GpodnetDevice.DeviceType.MOBILE);
|
||||
return new GpodnetDevice(deviceStr, captionStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0);
|
||||
} catch (GpodnetServiceException e) {
|
||||
e.printStackTrace();
|
||||
exception = e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.execute(service);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
chooseDevice.setOnClickListener(v -> {
|
||||
final int position = spinnerDevices.getSelectedItemPosition();
|
||||
if (position != AdapterView.INVALID_POSITION) {
|
||||
selectedDevice = devices.get().get(position);
|
||||
advance();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private String generateDeviceID(List<GpodnetDevice> gpodnetDevices) {
|
||||
// devices names must be of a certain form:
|
||||
// https://gpoddernet.readthedocs.org/en/latest/api/reference/general.html#devices
|
||||
// This is more restrictive than needed, but I think it makes for more readable names.
|
||||
String baseId = Build.MODEL.replaceAll("\\W", "");
|
||||
String id = baseId;
|
||||
int num = 0;
|
||||
|
||||
while (isDeviceWithIdInList(id, gpodnetDevices)) {
|
||||
id = baseId + "_" + num;
|
||||
num++;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private boolean isDeviceWithIdInList(String id, List<GpodnetDevice> gpodnetDevices) {
|
||||
if (gpodnetDevices == null) {
|
||||
return false;
|
||||
}
|
||||
for (GpodnetDevice device : gpodnetDevices) {
|
||||
if (device.getId().equals(id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean checkDeviceIDText(EditText deviceID, EditText caption, TextView txtvError, List<GpodnetDevice> devices) {
|
||||
String text = deviceID.getText().toString();
|
||||
if (text.length() == 0) {
|
||||
txtvError.setText(R.string.gpodnetauth_device_errorEmpty);
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
return false;
|
||||
} else if (caption.length() == 0) {
|
||||
txtvError.setText(R.string.gpodnetauth_device_caption_errorEmpty);
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
return false;
|
||||
} else {
|
||||
if (devices != null) {
|
||||
if (isDeviceWithIdInList(text, devices)) {
|
||||
txtvError.setText(R.string.gpodnetauth_device_errorAlreadyUsed);
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
return false;
|
||||
}
|
||||
txtvError.setVisibility(View.GONE);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void setupFinishView(View view) {
|
||||
final Button sync = view.findViewById(R.id.butSyncNow);
|
||||
final Button back = view.findViewById(R.id.butGoMainscreen);
|
||||
|
||||
sync.setOnClickListener(v -> {
|
||||
finish();
|
||||
SyncService.sync(getApplicationContext());
|
||||
});
|
||||
back.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(GpodnetAuthenticationActivity.this, MainActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
});
|
||||
}
|
||||
|
||||
private void writeLoginCredentials() {
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Writing login credentials");
|
||||
GpodnetPreferences.setUsername(username);
|
||||
GpodnetPreferences.setPassword(password);
|
||||
GpodnetPreferences.setDeviceID(selectedDevice.getId());
|
||||
}
|
||||
|
||||
private void advance() {
|
||||
if (currentStep < STEP_FINISH) {
|
||||
|
||||
View view = views[currentStep + 1];
|
||||
if (currentStep == STEP_DEFAULT) {
|
||||
setupLoginView(view);
|
||||
} else if (currentStep == STEP_LOGIN) {
|
||||
if (username == null || password == null) {
|
||||
throw new IllegalStateException("Username and password must not be null here");
|
||||
} else {
|
||||
setupDeviceView(view);
|
||||
}
|
||||
} else if (currentStep == STEP_DEVICE) {
|
||||
if (selectedDevice == null) {
|
||||
throw new IllegalStateException("Device must not be null here");
|
||||
} else {
|
||||
writeLoginCredentials();
|
||||
setupFinishView(view);
|
||||
}
|
||||
}
|
||||
if (currentStep != STEP_DEFAULT) {
|
||||
viewFlipper.showNext();
|
||||
}
|
||||
currentStep++;
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean usernameHasUnwantedChars(String username) {
|
||||
Pattern special = Pattern.compile("[!@#$%&*()+=|<>?{}\\[\\]~]");
|
||||
Matcher containsUnwantedChars = special.matcher(username);
|
||||
return containsUnwantedChars.find();
|
||||
}
|
||||
}
|
|
@ -20,9 +20,9 @@ import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
|||
import de.danoeh.antennapod.core.util.Converter;
|
||||
import de.danoeh.antennapod.core.util.EmbeddedChapterImage;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import de.danoeh.antennapod.core.util.ThemeUtils;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||
import de.danoeh.antennapod.view.CircularProgressBar;
|
||||
import de.danoeh.antennapod.ui.common.CircularProgressBar;
|
||||
|
||||
public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapter.ChapterHolder> {
|
||||
private Playable media;
|
||||
|
@ -42,7 +42,7 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte
|
|||
hasImages = false;
|
||||
if (media.getChapters() != null) {
|
||||
for (Chapter chapter : media.getChapters()) {
|
||||
if (!ignoreChapter(chapter) && !TextUtils.isEmpty(chapter.getImageUrl())) {
|
||||
if (!TextUtils.isEmpty(chapter.getImageUrl())) {
|
||||
hasImages = true;
|
||||
}
|
||||
}
|
||||
|
@ -125,14 +125,7 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte
|
|||
if (media == null || media.getChapters() == null) {
|
||||
return 0;
|
||||
}
|
||||
// ignore invalid chapters
|
||||
int counter = 0;
|
||||
for (Chapter chapter : media.getChapters()) {
|
||||
if (!ignoreChapter(chapter)) {
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
return counter;
|
||||
return media.getChapters().size();
|
||||
}
|
||||
|
||||
static class ChapterHolder extends RecyclerView.ViewHolder {
|
||||
|
@ -171,22 +164,8 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte
|
|||
notifyItemChanged(currentChapterIndex, "foo");
|
||||
}
|
||||
|
||||
private boolean ignoreChapter(Chapter c) {
|
||||
return media.getDuration() > 0 && media.getDuration() < c.getStart();
|
||||
}
|
||||
|
||||
public Chapter getItem(int position) {
|
||||
int i = 0;
|
||||
for (Chapter chapter : media.getChapters()) {
|
||||
if (!ignoreChapter(chapter)) {
|
||||
if (i == position) {
|
||||
return chapter;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return media.getChapters().get(position);
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
|
|
|
@ -20,7 +20,7 @@ import de.danoeh.antennapod.core.storage.DBReader;
|
|||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequestException;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||
import de.danoeh.antennapod.core.util.ThemeUtils;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
import de.danoeh.antennapod.view.viewholder.DownloadItemViewHolder;
|
||||
|
||||
/**
|
||||
|
@ -68,16 +68,14 @@ public class DownloadLogAdapter extends BaseAdapter {
|
|||
holder.icon.setContentDescription(context.getString(R.string.download_successful));
|
||||
holder.secondaryActionButton.setVisibility(View.INVISIBLE);
|
||||
holder.reason.setVisibility(View.GONE);
|
||||
holder.tapForDetails.setVisibility(View.GONE);
|
||||
} else {
|
||||
holder.icon.setTextColor(ContextCompat.getColor(context, R.color.download_failed_red));
|
||||
holder.icon.setText("{fa-times-circle}");
|
||||
holder.icon.setContentDescription(context.getString(R.string.error_label));
|
||||
String reasonText = status.getReason().getErrorString(context);
|
||||
if (status.getReasonDetailed() != null) {
|
||||
reasonText += ": " + status.getReasonDetailed();
|
||||
}
|
||||
holder.reason.setText(reasonText);
|
||||
holder.reason.setText(status.getReason().getErrorString(context));
|
||||
holder.reason.setVisibility(View.VISIBLE);
|
||||
holder.tapForDetails.setVisibility(View.VISIBLE);
|
||||
|
||||
if (newerWasSuccessful(position, status.getFeedfileType(), status.getFeedfileId())) {
|
||||
holder.secondaryActionButton.setVisibility(View.INVISIBLE);
|
||||
|
|
|
@ -15,8 +15,8 @@ import de.danoeh.antennapod.core.feed.FeedMedia;
|
|||
import de.danoeh.antennapod.core.service.download.DownloadRequest;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadStatus;
|
||||
import de.danoeh.antennapod.core.service.download.Downloader;
|
||||
import de.danoeh.antennapod.core.util.ThemeUtils;
|
||||
import de.danoeh.antennapod.view.CircularProgressBar;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
import de.danoeh.antennapod.ui.common.CircularProgressBar;
|
||||
|
||||
public class DownloadlistAdapter extends BaseAdapter {
|
||||
|
||||
|
@ -68,8 +68,8 @@ public class DownloadlistAdapter extends BaseAdapter {
|
|||
holder.secondaryActionButton.setContentDescription(context.getString(R.string.cancel_download_label));
|
||||
holder.secondaryActionButton.setTag(downloader);
|
||||
holder.secondaryActionButton.setOnClickListener(butSecondaryListener);
|
||||
holder.secondaryActionProgress.setPercentage(0, request);
|
||||
|
||||
boolean percentageWasSet = false;
|
||||
String status = "";
|
||||
if (request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
|
||||
status += context.getString(R.string.download_type_feed);
|
||||
|
@ -85,8 +85,12 @@ public class DownloadlistAdapter extends BaseAdapter {
|
|||
status += " / " + Formatter.formatShortFileSize(context, request.getSize());
|
||||
holder.secondaryActionProgress.setPercentage(
|
||||
0.01f * Math.max(1, request.getProgressPercent()), request);
|
||||
percentageWasSet = true;
|
||||
}
|
||||
}
|
||||
if (!percentageWasSet) {
|
||||
holder.secondaryActionProgress.setPercentage(0, request);
|
||||
}
|
||||
holder.status.setText(status);
|
||||
|
||||
return convertView;
|
||||
|
|
|
@ -20,6 +20,7 @@ import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
|||
import de.danoeh.antennapod.core.util.DateUtils;
|
||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
|
||||
import de.danoeh.antennapod.core.util.syndication.HtmlToPlainText;
|
||||
import de.danoeh.antennapod.dialog.StreamingConfirmationDialog;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -59,7 +60,7 @@ public class FeedItemlistDescriptionAdapter extends ArrayAdapter<FeedItem> {
|
|||
holder.title.setText(item.getTitle());
|
||||
holder.pubDate.setText(DateUtils.formatAbbrev(getContext(), item.getPubDate()));
|
||||
if (item.getDescription() != null) {
|
||||
String description = item.getDescription()
|
||||
String description = HtmlToPlainText.getPlainText(item.getDescription())
|
||||
.replaceAll("\n", " ")
|
||||
.replaceAll("\\s+", " ")
|
||||
.trim();
|
||||
|
|
|
@ -10,7 +10,7 @@ import de.danoeh.antennapod.R;
|
|||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.fragment.FeedItemlistFragment;
|
||||
import de.danoeh.antennapod.view.SquareImageView;
|
||||
import de.danoeh.antennapod.ui.common.SquareImageView;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
|
|
|
@ -24,7 +24,6 @@ import de.danoeh.antennapod.core.feed.Feed;
|
|||
import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.storage.NavDrawerData;
|
||||
import de.danoeh.antennapod.core.util.ThemeUtils;
|
||||
import de.danoeh.antennapod.fragment.AddFeedFragment;
|
||||
import de.danoeh.antennapod.fragment.DownloadsFragment;
|
||||
import de.danoeh.antennapod.fragment.EpisodesFragment;
|
||||
|
@ -32,6 +31,7 @@ import de.danoeh.antennapod.fragment.NavDrawerFragment;
|
|||
import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
|
||||
import de.danoeh.antennapod.fragment.QueueFragment;
|
||||
import de.danoeh.antennapod.fragment.SubscriptionFragment;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
@ -76,7 +76,7 @@ public class NavListAdapter extends RecyclerView.Adapter<NavListAdapter.Holder>
|
|||
}
|
||||
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (key.equals(UserPreferences.PREF_HIDDEN_DRAWER_ITEMS)) {
|
||||
if (UserPreferences.PREF_HIDDEN_DRAWER_ITEMS.equals(key)) {
|
||||
loadItems();
|
||||
}
|
||||
}
|
||||
|
@ -314,7 +314,7 @@ public class NavListAdapter extends RecyclerView.Adapter<NavListAdapter.Holder>
|
|||
}
|
||||
|
||||
Glide.with(context)
|
||||
.load(feed.getImageLocation())
|
||||
.load(feed.getImageUrl())
|
||||
.apply(new RequestOptions()
|
||||
.placeholder(R.color.light_gray)
|
||||
.error(R.color.light_gray)
|
||||
|
|
|
@ -66,7 +66,7 @@ public abstract class StatisticsListAdapter extends RecyclerView.Adapter<Recycle
|
|||
StatisticsHolder holder = (StatisticsHolder) h;
|
||||
StatisticsItem statsItem = statisticsData.get(position - 1);
|
||||
Glide.with(context)
|
||||
.load(statsItem.feed.getImageLocation())
|
||||
.load(statsItem.feed.getImageUrl())
|
||||
.apply(new RequestOptions()
|
||||
.placeholder(R.color.light_gray)
|
||||
.error(R.color.light_gray)
|
||||
|
|
|
@ -22,8 +22,8 @@ import de.danoeh.antennapod.activity.MainActivity;
|
|||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.feed.LocalFeedUpdater;
|
||||
import de.danoeh.antennapod.core.storage.NavDrawerData;
|
||||
import de.danoeh.antennapod.core.util.ThemeUtils;
|
||||
import de.danoeh.antennapod.fragment.FeedItemlistFragment;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
import jp.shts.android.library.TriangleLabelView;
|
||||
|
||||
/**
|
||||
|
@ -112,7 +112,7 @@ public class SubscriptionsAdapter extends BaseAdapter implements AdapterView.OnI
|
|||
boolean textAndImageCombined = feed.isLocalFeed()
|
||||
&& LocalFeedUpdater.getDefaultIconUrl(convertView.getContext()).equals(feed.getImageUrl());
|
||||
new CoverLoader(mainActivityRef.get())
|
||||
.withUri(feed.getImageLocation())
|
||||
.withUri(feed.getImageUrl())
|
||||
.withPlaceholderView(holder.feedTitle, textAndImageCombined)
|
||||
.withCoverView(holder.imageView)
|
||||
.load();
|
||||
|
@ -123,7 +123,6 @@ public class SubscriptionsAdapter extends BaseAdapter implements AdapterView.OnI
|
|||
.withCoverView(holder.imageView)
|
||||
.load();
|
||||
}
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,21 +2,19 @@ package de.danoeh.antennapod.config;
|
|||
|
||||
import de.danoeh.antennapod.BuildConfig;
|
||||
import de.danoeh.antennapod.core.ClientConfig;
|
||||
import de.danoeh.antennapod.core.storage.APDownloadAlgorithm;
|
||||
|
||||
/**
|
||||
* Configures the ClientConfig class of the core package.
|
||||
*/
|
||||
class ClientConfigurator {
|
||||
|
||||
private ClientConfigurator(){}
|
||||
private ClientConfigurator() {
|
||||
}
|
||||
|
||||
static {
|
||||
ClientConfig.USER_AGENT = "AntennaPod/" + BuildConfig.VERSION_NAME;
|
||||
ClientConfig.applicationCallbacks = new ApplicationCallbacksImpl();
|
||||
ClientConfig.downloadServiceCallbacks = new DownloadServiceCallbacksImpl();
|
||||
ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl();
|
||||
ClientConfig.automaticDownloadAlgorithm = new APDownloadAlgorithm();
|
||||
ClientConfig.castCallbacks = new CastCallbackImpl();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,8 +30,8 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks {
|
|||
@Override
|
||||
public PendingIntent getAuthentificationNotificationContentIntent(Context context, DownloadRequest request) {
|
||||
final Intent activityIntent = new Intent(context.getApplicationContext(), DownloadAuthenticationActivity.class);
|
||||
activityIntent.setAction("request" + request.getFeedfileId());
|
||||
activityIntent.putExtra(DownloadAuthenticationActivity.ARG_DOWNLOAD_REQUEST, request);
|
||||
activityIntent.putExtra(DownloadAuthenticationActivity.ARG_SEND_TO_DOWNLOAD_REQUESTER_BOOL, true);
|
||||
return PendingIntent.getActivity(context.getApplicationContext(),
|
||||
R.id.pending_intent_download_service_auth, activityIntent, PendingIntent.FLAG_ONE_SHOT);
|
||||
}
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
package de.danoeh.antennapod.config;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.activity.VideoplayerActivity;
|
||||
import de.danoeh.antennapod.core.PlaybackServiceCallbacks;
|
||||
import de.danoeh.antennapod.core.feed.MediaType;
|
||||
|
||||
public class PlaybackServiceCallbacksImpl implements PlaybackServiceCallbacks {
|
||||
@Override
|
||||
public Intent getPlayerActivityIntent(Context context, MediaType mediaType, boolean remotePlayback) {
|
||||
if (mediaType == MediaType.AUDIO || remotePlayback) {
|
||||
return new Intent(context, MainActivity.class).putExtra(MainActivity.EXTRA_OPEN_PLAYER, true);
|
||||
} else {
|
||||
Intent i = new Intent(context, VideoplayerActivity.class);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +1,50 @@
|
|||
package de.danoeh.antennapod.dialog;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.text.method.HideReturnsTransformationMethod;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.databinding.AuthenticationDialogBinding;
|
||||
|
||||
/**
|
||||
* Displays a dialog with a username and password text field and an optional checkbox to save username and preferences.
|
||||
*/
|
||||
public abstract class AuthenticationDialog extends AlertDialog.Builder {
|
||||
boolean passwordHidden = true;
|
||||
|
||||
public AuthenticationDialog(Context context, int titleRes, boolean enableUsernameField,
|
||||
String usernameInitialValue, String passwordInitialValue) {
|
||||
super(context);
|
||||
setTitle(titleRes);
|
||||
View rootView = View.inflate(context, R.layout.authentication_dialog, null);
|
||||
setView(rootView);
|
||||
AuthenticationDialogBinding viewBinding = AuthenticationDialogBinding.inflate(LayoutInflater.from(context));
|
||||
setView(viewBinding.getRoot());
|
||||
|
||||
final EditText etxtUsername = rootView.findViewById(R.id.etxtUsername);
|
||||
final EditText etxtPassword = rootView.findViewById(R.id.etxtPassword);
|
||||
|
||||
etxtUsername.setEnabled(enableUsernameField);
|
||||
viewBinding.usernameEditText.setEnabled(enableUsernameField);
|
||||
if (usernameInitialValue != null) {
|
||||
etxtUsername.setText(usernameInitialValue);
|
||||
viewBinding.usernameEditText.setText(usernameInitialValue);
|
||||
}
|
||||
if (passwordInitialValue != null) {
|
||||
etxtPassword.setText(passwordInitialValue);
|
||||
viewBinding.passwordEditText.setText(passwordInitialValue);
|
||||
}
|
||||
viewBinding.showPasswordButton.setOnClickListener(v -> {
|
||||
if (passwordHidden) {
|
||||
viewBinding.passwordEditText.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
|
||||
viewBinding.showPasswordButton.setAlpha(1.0f);
|
||||
} else {
|
||||
viewBinding.passwordEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
|
||||
viewBinding.showPasswordButton.setAlpha(0.6f);
|
||||
}
|
||||
passwordHidden = !passwordHidden;
|
||||
});
|
||||
|
||||
setOnCancelListener(dialog -> onCancelled());
|
||||
setOnDismissListener(dialog -> onCancelled());
|
||||
setNegativeButton(R.string.cancel_label, null);
|
||||
setPositiveButton(R.string.confirm_label, (dialog, which)
|
||||
-> onConfirmed(etxtUsername.getText().toString(), etxtPassword.getText().toString()));
|
||||
-> onConfirmed(viewBinding.usernameEditText.getText().toString(),
|
||||
viewBinding.passwordEditText.getText().toString()));
|
||||
}
|
||||
|
||||
protected void onCancelled() {
|
||||
|
|
|
@ -28,7 +28,7 @@ import de.danoeh.antennapod.core.storage.DownloadRequester;
|
|||
import de.danoeh.antennapod.core.util.FeedItemPermutors;
|
||||
import de.danoeh.antennapod.core.util.LongList;
|
||||
import de.danoeh.antennapod.core.util.SortOrder;
|
||||
import de.danoeh.antennapod.core.util.ThemeUtils;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
|
|
@ -16,7 +16,7 @@ import java.util.Set;
|
|||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.feed.FeedItemFilter;
|
||||
import de.danoeh.antennapod.core.feed.FeedItemFilterGroup;
|
||||
import de.danoeh.antennapod.view.RecursiveRadioGroup;
|
||||
import de.danoeh.antennapod.ui.common.RecursiveRadioGroup;
|
||||
|
||||
public abstract class FilterDialog {
|
||||
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
package de.danoeh.antennapod.dialog;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
|
||||
|
||||
/**
|
||||
* Creates a dialog that lets the user change the hostname for the gpodder.net service.
|
||||
*/
|
||||
public class GpodnetSetHostnameDialog {
|
||||
|
||||
private GpodnetSetHostnameDialog(){}
|
||||
|
||||
private static final String TAG = "GpodnetSetHostnameDialog";
|
||||
|
||||
public static AlertDialog createDialog(final Context context) {
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
|
||||
final EditText et = new EditText(context);
|
||||
et.setText(GpodnetPreferences.getHostname());
|
||||
et.setInputType(InputType.TYPE_TEXT_VARIATION_URI);
|
||||
dialog.setTitle(R.string.pref_gpodnet_sethostname_title)
|
||||
.setView(setupContentView(context, et))
|
||||
.setPositiveButton(R.string.confirm_label, (dialog1, which) -> {
|
||||
final Editable e = et.getText();
|
||||
if (e != null) {
|
||||
GpodnetPreferences.setHostname(e.toString());
|
||||
}
|
||||
dialog1.dismiss();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel_label, (dialog1, which) -> dialog1.cancel())
|
||||
.setNeutralButton(R.string.pref_gpodnet_sethostname_use_default_host, (dialog1, which) -> {
|
||||
GpodnetPreferences.setHostname(GpodnetService.DEFAULT_BASE_HOST);
|
||||
dialog1.dismiss();
|
||||
})
|
||||
.setCancelable(true);
|
||||
return dialog.show();
|
||||
}
|
||||
|
||||
private static View setupContentView(Context context, EditText et) {
|
||||
LinearLayout ll = new LinearLayout(context);
|
||||
ll.addView(et);
|
||||
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) et.getLayoutParams();
|
||||
if (params != null) {
|
||||
params.setMargins(8, 8, 8, 8);
|
||||
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
params.height = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
}
|
||||
return ll;
|
||||
}
|
||||
}
|
|
@ -41,7 +41,7 @@ public class PlaybackControlsDialog extends DialogFragment {
|
|||
super.onStart();
|
||||
controller = new PlaybackController(getActivity()) {
|
||||
@Override
|
||||
public void setupGUI() {
|
||||
public void loadMediaInfo() {
|
||||
setupUi();
|
||||
setupAudioTracks();
|
||||
}
|
||||
|
|
|
@ -47,12 +47,12 @@ public class SleepTimerDialog extends DialogFragment {
|
|||
super.onStart();
|
||||
controller = new PlaybackController(getActivity()) {
|
||||
@Override
|
||||
public void setupGUI() {
|
||||
public void onSleepTimerUpdate() {
|
||||
updateTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSleepTimerUpdate() {
|
||||
public void loadMediaInfo() {
|
||||
updateTime();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
|
|||
import de.danoeh.antennapod.core.feed.SubscriptionsFilter;
|
||||
import de.danoeh.antennapod.core.feed.SubscriptionsFilterGroup;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.view.RecursiveRadioGroup;
|
||||
import de.danoeh.antennapod.ui.common.RecursiveRadioGroup;
|
||||
|
||||
public class SubscriptionsFilterDialog {
|
||||
public static void showDialog(Context context) {
|
||||
|
|
|
@ -56,12 +56,12 @@ public class VariableSpeedDialog extends DialogFragment {
|
|||
super.onStart();
|
||||
controller = new PlaybackController(getActivity()) {
|
||||
@Override
|
||||
public void setupGUI() {
|
||||
public void onPlaybackSpeedChange() {
|
||||
updateSpeed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackSpeedChange() {
|
||||
public void loadMediaInfo() {
|
||||
updateSpeed();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -18,7 +18,7 @@ public class GpodnetPodcastSearcher implements PodcastSearcher {
|
|||
return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> {
|
||||
try {
|
||||
GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(),
|
||||
GpodnetPreferences.getHostname());
|
||||
GpodnetPreferences.getHosturl());
|
||||
List<GpodnetPodcast> gpodnetPodcasts = service.searchPodcasts(query, 0);
|
||||
List<PodcastSearchResult> results = new ArrayList<>();
|
||||
for (GpodnetPodcast podcast : gpodnetPodcasts) {
|
||||
|
|
|
@ -15,11 +15,11 @@ public class PodcastSearcherRegistry {
|
|||
public static List<SearcherInfo> getSearchProviders() {
|
||||
if (searchProviders == null) {
|
||||
searchProviders = new ArrayList<>();
|
||||
searchProviders.add(new SearcherInfo(new CombinedSearcher(), 1.f));
|
||||
searchProviders.add(new SearcherInfo(new ItunesPodcastSearcher(), 1.f));
|
||||
searchProviders.add(new SearcherInfo(new FyydPodcastSearcher(), 1.f));
|
||||
searchProviders.add(new SearcherInfo(new CombinedSearcher(), 1.0f));
|
||||
searchProviders.add(new SearcherInfo(new GpodnetPodcastSearcher(), 0.0f));
|
||||
searchProviders.add(new SearcherInfo(new PodcastIndexPodcastSearcher(), 0.0f));
|
||||
searchProviders.add(new SearcherInfo(new FyydPodcastSearcher(), 1.0f));
|
||||
searchProviders.add(new SearcherInfo(new ItunesPodcastSearcher(), 1.0f));
|
||||
searchProviders.add(new SearcherInfo(new PodcastIndexPodcastSearcher(), 1.0f));
|
||||
}
|
||||
return searchProviders;
|
||||
}
|
||||
|
|
|
@ -50,9 +50,11 @@ public class AddFeedFragment extends Fragment {
|
|||
public static final String TAG = "AddFeedFragment";
|
||||
private static final int REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH = 1;
|
||||
private static final int REQUEST_CODE_ADD_LOCAL_FOLDER = 2;
|
||||
private static final String KEY_UP_ARROW = "up_arrow";
|
||||
|
||||
private AddfeedBinding viewBinding;
|
||||
private MainActivity activity;
|
||||
private boolean displayUpArrow;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
|
@ -64,7 +66,11 @@ public class AddFeedFragment extends Fragment {
|
|||
activity = (MainActivity) getActivity();
|
||||
|
||||
Toolbar toolbar = viewBinding.toolbar;
|
||||
((MainActivity) getActivity()).setupToolbarToggle(toolbar);
|
||||
displayUpArrow = getParentFragmentManager().getBackStackEntryCount() != 0;
|
||||
if (savedInstanceState != null) {
|
||||
displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW);
|
||||
}
|
||||
((MainActivity) getActivity()).setupToolbarToggle(toolbar, displayUpArrow);
|
||||
|
||||
viewBinding.searchItunesButton.setOnClickListener(v
|
||||
-> activity.loadChildFragment(OnlineSearchFragment.newInstance(ItunesPodcastSearcher.class)));
|
||||
|
@ -119,6 +125,12 @@ public class AddFeedFragment extends Fragment {
|
|||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
outState.putBoolean(KEY_UP_ARROW, displayUpArrow);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
private void showAddViaUrlDialog() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
builder.setTitle(R.string.add_podcast_by_url);
|
||||
|
@ -196,7 +208,11 @@ public class AddFeedFragment extends Fragment {
|
|||
if (documentFile == null) {
|
||||
throw new IllegalArgumentException("Unable to retrieve document tree");
|
||||
}
|
||||
Feed dirFeed = new Feed(Feed.PREFIX_LOCAL_FOLDER + uri.toString(), null, documentFile.getName());
|
||||
String title = documentFile.getName();
|
||||
if (title == null) {
|
||||
title = getString(R.string.local_folder);
|
||||
}
|
||||
Feed dirFeed = new Feed(Feed.PREFIX_LOCAL_FOLDER + uri.toString(), null, title);
|
||||
dirFeed.setItems(Collections.emptyList());
|
||||
dirFeed.setSortOrder(SortOrder.EPISODE_TITLE_A_Z);
|
||||
Feed fromDatabase = DBTasks.updateFeed(getContext(), dirFeed, false);
|
||||
|
|
|
@ -104,13 +104,12 @@ public class AllEpisodesFragment extends EpisodesListFragment {
|
|||
@NonNull
|
||||
@Override
|
||||
protected List<FeedItem> loadData() {
|
||||
return feedItemFilter.filter(DBReader.getRecentlyPublishedEpisodes(0, page * EPISODES_PER_PAGE));
|
||||
return DBReader.getRecentlyPublishedEpisodes(0, page * EPISODES_PER_PAGE, feedItemFilter);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected List<FeedItem> loadMoreData() {
|
||||
return feedItemFilter.filter(DBReader.getRecentlyPublishedEpisodes((page - 1) * EPISODES_PER_PAGE,
|
||||
EPISODES_PER_PAGE));
|
||||
return DBReader.getRecentlyPublishedEpisodes((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE, feedItemFilter);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package de.danoeh.antennapod.fragment;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
|
@ -17,7 +15,9 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
|
@ -29,11 +29,14 @@ import de.danoeh.antennapod.activity.CastEnabledActivity;
|
|||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.event.FavoritesEvent;
|
||||
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
|
||||
import de.danoeh.antennapod.core.feed.Chapter;
|
||||
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.util.ChapterUtils;
|
||||
import de.danoeh.antennapod.core.util.Converter;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import de.danoeh.antennapod.core.util.TimeSpeedConverter;
|
||||
|
@ -45,7 +48,8 @@ import de.danoeh.antennapod.dialog.SkipPreferenceDialog;
|
|||
import de.danoeh.antennapod.dialog.SleepTimerDialog;
|
||||
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
|
||||
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
|
||||
import de.danoeh.antennapod.view.PlaybackSpeedIndicatorView;
|
||||
import de.danoeh.antennapod.view.ChapterSeekBar;
|
||||
import de.danoeh.antennapod.ui.common.PlaybackSpeedIndicatorView;
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
@ -62,14 +66,13 @@ import java.util.List;
|
|||
* Shows the audio player.
|
||||
*/
|
||||
public class AudioPlayerFragment extends Fragment implements
|
||||
SeekBar.OnSeekBarChangeListener, Toolbar.OnMenuItemClickListener {
|
||||
ChapterSeekBar.OnSeekBarChangeListener, Toolbar.OnMenuItemClickListener {
|
||||
public static final String TAG = "AudioPlayerFragment";
|
||||
private static final int POS_COVER = 0;
|
||||
private static final int POS_DESCR = 1;
|
||||
private static final int POS_CHAPTERS = 2;
|
||||
private static final int NUM_CONTENT_FRAGMENTS = 3;
|
||||
private static final String PREFS = "AudioPlayerFragmentPreferences";
|
||||
private static final String PREF_SHOW_TIME_LEFT = "showTimeLeft";
|
||||
public static final String PREFS = "AudioPlayerFragmentPreferences";
|
||||
private static final float EPSILON = 0.001f;
|
||||
|
||||
PlaybackSpeedIndicatorView butPlaybackSpeed;
|
||||
|
@ -77,7 +80,7 @@ public class AudioPlayerFragment extends Fragment implements
|
|||
private ViewPager2 pager;
|
||||
private TextView txtvPosition;
|
||||
private TextView txtvLength;
|
||||
private SeekBar sbPosition;
|
||||
private ChapterSeekBar sbPosition;
|
||||
private ImageButton butRev;
|
||||
private TextView txtvRev;
|
||||
private ImageButton butPlay;
|
||||
|
@ -86,6 +89,8 @@ public class AudioPlayerFragment extends Fragment implements
|
|||
private ImageButton butSkip;
|
||||
private Toolbar toolbar;
|
||||
private ProgressBar progressIndicator;
|
||||
private CardView cardViewSeek;
|
||||
private TextView txtvSeek;
|
||||
|
||||
private PlaybackController controller;
|
||||
private Disposable disposable;
|
||||
|
@ -122,6 +127,8 @@ public class AudioPlayerFragment extends Fragment implements
|
|||
txtvFF = root.findViewById(R.id.txtvFF);
|
||||
butSkip = root.findViewById(R.id.butSkip);
|
||||
progressIndicator = root.findViewById(R.id.progLoading);
|
||||
cardViewSeek = root.findViewById(R.id.cardViewSeek);
|
||||
txtvSeek = root.findViewById(R.id.txtvSeek);
|
||||
|
||||
setupLengthTextView();
|
||||
setupControlButtons();
|
||||
|
@ -168,12 +175,33 @@ public class AudioPlayerFragment extends Fragment implements
|
|||
return root;
|
||||
}
|
||||
|
||||
public void setHasChapters(boolean hasChapters) {
|
||||
private void setHasChapters(boolean hasChapters) {
|
||||
this.hasChapters = hasChapters;
|
||||
tabLayoutMediator.detach();
|
||||
tabLayoutMediator.attach();
|
||||
}
|
||||
|
||||
private void setChapterDividers(Playable media) {
|
||||
|
||||
if (media == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
float[] dividerPos = null;
|
||||
|
||||
if (hasChapters) {
|
||||
List<Chapter> chapters = media.getChapters();
|
||||
dividerPos = new float[chapters.size()];
|
||||
float duration = media.getDuration();
|
||||
|
||||
for (int i = 0; i < chapters.size(); i++) {
|
||||
dividerPos[i] = chapters.get(i).getStart() / duration;
|
||||
}
|
||||
}
|
||||
|
||||
sbPosition.setDividerPos(dividerPos);
|
||||
}
|
||||
|
||||
public View getExternalPlayerHolder() {
|
||||
return getView().findViewById(R.id.playerFragment);
|
||||
}
|
||||
|
@ -211,16 +239,25 @@ public class AudioPlayerFragment extends Fragment implements
|
|||
IntentUtils.sendLocalBroadcast(getActivity(), PlaybackService.ACTION_SKIP_CURRENT_EPISODE));
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onUnreadItemsUpdate(UnreadItemsUpdateEvent event) {
|
||||
if (controller == null) {
|
||||
return;
|
||||
}
|
||||
updatePosition(new PlaybackPositionEvent(controller.getPosition(),
|
||||
controller.getDuration()));
|
||||
}
|
||||
|
||||
private void setupLengthTextView() {
|
||||
SharedPreferences prefs = getContext().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
|
||||
showTimeLeft = prefs.getBoolean(PREF_SHOW_TIME_LEFT, false);
|
||||
showTimeLeft = UserPreferences.shouldShowRemainingTime();
|
||||
txtvLength.setOnClickListener(v -> {
|
||||
if (controller == null) {
|
||||
return;
|
||||
}
|
||||
showTimeLeft = !showTimeLeft;
|
||||
prefs.edit().putBoolean(PREF_SHOW_TIME_LEFT, showTimeLeft).apply();
|
||||
updatePosition(new PlaybackPositionEvent(controller.getPosition(), controller.getDuration()));
|
||||
UserPreferences.setShowRemainTimeSetting(showTimeLeft);
|
||||
updatePosition(new PlaybackPositionEvent(controller.getPosition(),
|
||||
controller.getDuration()));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -285,26 +322,21 @@ public class AudioPlayerFragment extends Fragment implements
|
|||
disposable = Maybe.create(emitter -> {
|
||||
Playable media = controller.getMedia();
|
||||
if (media != null) {
|
||||
ChapterUtils.loadChapters(media, getContext());
|
||||
emitter.onSuccess(media);
|
||||
} else {
|
||||
emitter.onComplete();
|
||||
}
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(media -> updateUi((Playable) media),
|
||||
error -> Log.e(TAG, Log.getStackTraceString(error)),
|
||||
() -> updateUi(null));
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(media -> updateUi((Playable) media),
|
||||
error -> Log.e(TAG, Log.getStackTraceString(error)),
|
||||
() -> updateUi(null));
|
||||
}
|
||||
|
||||
private PlaybackController newPlaybackController() {
|
||||
return new PlaybackController(getActivity()) {
|
||||
|
||||
@Override
|
||||
public void setupGUI() {
|
||||
AudioPlayerFragment.this.loadMediaInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBufferStart() {
|
||||
progressIndicator.setVisibility(View.VISIBLE);
|
||||
|
@ -352,9 +384,8 @@ public class AudioPlayerFragment extends Fragment implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean loadMediaInfo() {
|
||||
public void loadMediaInfo() {
|
||||
AudioPlayerFragment.this.loadMediaInfo();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -383,8 +414,15 @@ public class AudioPlayerFragment extends Fragment implements
|
|||
if (controller == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (media != null && media.getChapters() != null) {
|
||||
setHasChapters(media.getChapters().size() > 0);
|
||||
} else {
|
||||
setHasChapters(false);
|
||||
}
|
||||
updatePosition(new PlaybackPositionEvent(controller.getPosition(), controller.getDuration()));
|
||||
updatePlaybackSpeedButton(media);
|
||||
setChapterDividers(media);
|
||||
setupOptionsMenu(media);
|
||||
}
|
||||
|
||||
|
@ -433,6 +471,7 @@ public class AudioPlayerFragment extends Fragment implements
|
|||
return;
|
||||
}
|
||||
txtvPosition.setText(Converter.getDurationStringLong(currentPosition));
|
||||
showTimeLeft = UserPreferences.shouldShowRemainingTime();
|
||||
if (showTimeLeft) {
|
||||
txtvLength.setText("-" + Converter.getDurationStringLong(remainingTime));
|
||||
} else {
|
||||
|
@ -454,22 +493,22 @@ public class AudioPlayerFragment extends Fragment implements
|
|||
}
|
||||
if (fromUser) {
|
||||
float prog = progress / ((float) seekBar.getMax());
|
||||
int duration = controller.getDuration();
|
||||
TimeSpeedConverter converter = new TimeSpeedConverter(controller.getCurrentPlaybackSpeedMultiplier());
|
||||
int position = converter.convert((int) (prog * duration));
|
||||
txtvPosition.setText(Converter.getDurationStringLong(position));
|
||||
|
||||
if (showTimeLeft && prog != 0) {
|
||||
int timeLeft = converter.convert(duration - (int) (prog * duration));
|
||||
String length = "-" + Converter.getDurationStringLong(timeLeft);
|
||||
txtvLength.setText(length);
|
||||
}
|
||||
int position = converter.convert((int) (prog * controller.getDuration()));
|
||||
txtvSeek.setText(Converter.getDurationStringLong(position));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
// interrupt position Observer, restart later
|
||||
cardViewSeek.setScaleX(.8f);
|
||||
cardViewSeek.setScaleY(.8f);
|
||||
cardViewSeek.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.alpha(1f).scaleX(1f).scaleY(1f)
|
||||
.setDuration(200)
|
||||
.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -478,6 +517,13 @@ public class AudioPlayerFragment extends Fragment implements
|
|||
float prog = seekBar.getProgress() / ((float) seekBar.getMax());
|
||||
controller.seekTo((int) (prog * controller.getDuration()));
|
||||
}
|
||||
cardViewSeek.setScaleX(1f);
|
||||
cardViewSeek.setScaleY(1f);
|
||||
cardViewSeek.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.alpha(0f).scaleX(.8f).scaleY(.8f)
|
||||
.setDuration(200)
|
||||
.start();
|
||||
}
|
||||
|
||||
public void setupOptionsMenu(Playable media) {
|
||||
|
|
|
@ -8,9 +8,9 @@ import android.view.ViewGroup;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.adapter.ChaptersListAdapter;
|
||||
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
|
||||
|
@ -45,7 +45,8 @@ public class ChaptersFragment extends Fragment {
|
|||
RecyclerView recyclerView = root.findViewById(R.id.recyclerView);
|
||||
layoutManager = new LinearLayoutManager(getActivity());
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build());
|
||||
recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(),
|
||||
layoutManager.getOrientation()));
|
||||
|
||||
adapter = new ChaptersListAdapter(getActivity(), pos -> {
|
||||
if (controller.getStatus() != PlayerStatus.PLAYING) {
|
||||
|
@ -71,13 +72,7 @@ public class ChaptersFragment extends Fragment {
|
|||
super.onStart();
|
||||
controller = new PlaybackController(getActivity()) {
|
||||
@Override
|
||||
public boolean loadMediaInfo() {
|
||||
ChaptersFragment.this.loadMediaInfo();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupGUI() {
|
||||
public void loadMediaInfo() {
|
||||
ChaptersFragment.this.loadMediaInfo();
|
||||
}
|
||||
|
||||
|
@ -123,7 +118,7 @@ public class ChaptersFragment extends Fragment {
|
|||
disposable = Maybe.create(emitter -> {
|
||||
Playable media = controller.getMedia();
|
||||
if (media != null) {
|
||||
media.loadChapterMarks(getContext());
|
||||
ChapterUtils.loadChapters(media, getContext());
|
||||
emitter.onSuccess(media);
|
||||
} else {
|
||||
emitter.onComplete();
|
||||
|
@ -142,7 +137,6 @@ public class ChaptersFragment extends Fragment {
|
|||
return;
|
||||
}
|
||||
adapter.setMedia(media);
|
||||
((AudioPlayerFragment) getParentFragment()).setHasChapters(adapter.getItemCount() > 0);
|
||||
int positionOfCurrentChapter = getCurrentChapter(media);
|
||||
updateChapterSelection(positionOfCurrentChapter);
|
||||
}
|
||||
|
|
|
@ -13,11 +13,9 @@ import android.view.ViewGroup;
|
|||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.resource.bitmap.FitCenter;
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||
|
@ -25,9 +23,11 @@ import com.bumptech.glide.RequestBuilder;
|
|||
import com.bumptech.glide.request.RequestOptions;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.feed.util.ImageResourceUtils;
|
||||
import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
||||
import de.danoeh.antennapod.core.util.ChapterUtils;
|
||||
import de.danoeh.antennapod.core.util.DateUtils;
|
||||
import de.danoeh.antennapod.core.util.EmbeddedChapterImage;
|
||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
|
@ -35,6 +35,7 @@ import io.reactivex.Maybe;
|
|||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
@ -93,7 +94,12 @@ public class CoverFragment extends Fragment {
|
|||
}
|
||||
|
||||
private void displayMediaInfo(@NonNull Playable media) {
|
||||
txtvPodcastTitle.setText(media.getFeedTitle());
|
||||
String pubDateStr = DateUtils.formatAbbrev(getActivity(), ((FeedMedia) media).getPubDate());
|
||||
txtvPodcastTitle.setText(StringUtils.stripToEmpty(media.getFeedTitle())
|
||||
+ "\u00A0"
|
||||
+ "・"
|
||||
+ "\u00A0"
|
||||
+ StringUtils.replace(StringUtils.stripToEmpty(pubDateStr), " ", "\u00A0"));
|
||||
txtvEpisodeTitle.setText(media.getEpisodeTitle());
|
||||
displayedChapterIndex = -2; // Force refresh
|
||||
displayCoverImage(media.getPosition());
|
||||
|
@ -111,13 +117,7 @@ public class CoverFragment extends Fragment {
|
|||
super.onStart();
|
||||
controller = new PlaybackController(getActivity()) {
|
||||
@Override
|
||||
public boolean loadMediaInfo() {
|
||||
CoverFragment.this.loadMediaInfo();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupGUI() {
|
||||
public void loadMediaInfo() {
|
||||
CoverFragment.this.loadMediaInfo();
|
||||
}
|
||||
};
|
||||
|
@ -151,23 +151,25 @@ public class CoverFragment extends Fragment {
|
|||
if (chapter != displayedChapterIndex) {
|
||||
displayedChapterIndex = chapter;
|
||||
|
||||
RequestOptions options = new RequestOptions()
|
||||
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
|
||||
.dontAnimate()
|
||||
.transforms(new FitCenter(),
|
||||
new RoundedCorners((int) (16 * getResources().getDisplayMetrics().density)));
|
||||
|
||||
RequestBuilder<Drawable> cover = Glide.with(this)
|
||||
.load(ImageResourceUtils.getImageLocation(media))
|
||||
.apply(new RequestOptions()
|
||||
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
|
||||
.dontAnimate()
|
||||
.transforms(new FitCenter(),
|
||||
new RoundedCorners((int) (16 * getResources().getDisplayMetrics().density))));
|
||||
.load(media.getImageLocation())
|
||||
.error(Glide.with(this)
|
||||
.load(ImageResourceUtils.getFallbackImageLocation(media))
|
||||
.apply(options))
|
||||
.apply(options);
|
||||
|
||||
if (chapter == -1 || TextUtils.isEmpty(media.getChapters().get(chapter).getImageUrl())) {
|
||||
cover.into(imgvCover);
|
||||
} else {
|
||||
Glide.with(this)
|
||||
.load(EmbeddedChapterImage.getModelFor(media, chapter))
|
||||
.apply(new RequestOptions()
|
||||
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
|
||||
.dontAnimate()
|
||||
.transforms(new FitCenter(),
|
||||
new RoundedCorners((int) (16 * getResources().getDisplayMetrics().density))))
|
||||
.apply(options)
|
||||
.thumbnail(cover)
|
||||
.error(cover)
|
||||
.into(imgvCover);
|
||||
|
@ -208,7 +210,7 @@ public class CoverFragment extends Fragment {
|
|||
imgvCover.setLayoutParams(params);
|
||||
}
|
||||
} else {
|
||||
double percentageHeight = ratio * 0.8;
|
||||
double percentageHeight = ratio * 0.6;
|
||||
mainContainer.setOrientation(LinearLayout.HORIZONTAL);
|
||||
if (newConfig.screenHeightDp > 0) {
|
||||
params.height = (int) (convertDpToPixel(newConfig.screenHeightDp) * percentageHeight);
|
||||
|
|
|
@ -28,16 +28,17 @@ public class DownloadsFragment extends PagedToolbarFragment {
|
|||
public static final String TAG = "DownloadsFragment";
|
||||
|
||||
public static final String ARG_SELECTED_TAB = "selected_tab";
|
||||
private static final String PREF_LAST_TAB_POSITION = "tab_position";
|
||||
private static final String KEY_UP_ARROW = "up_arrow";
|
||||
|
||||
public static final int POS_RUNNING = 0;
|
||||
private static final int POS_COMPLETED = 1;
|
||||
public static final int POS_LOG = 2;
|
||||
private static final int TOTAL_COUNT = 3;
|
||||
|
||||
private static final String PREF_LAST_TAB_POSITION = "tab_position";
|
||||
|
||||
private ViewPager2 viewPager;
|
||||
private TabLayout tabLayout;
|
||||
private boolean displayUpArrow;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
|
@ -48,7 +49,11 @@ public class DownloadsFragment extends PagedToolbarFragment {
|
|||
Toolbar toolbar = root.findViewById(R.id.toolbar);
|
||||
toolbar.setTitle(R.string.downloads_label);
|
||||
toolbar.inflateMenu(R.menu.downloads);
|
||||
((MainActivity) getActivity()).setupToolbarToggle(toolbar);
|
||||
displayUpArrow = getParentFragmentManager().getBackStackEntryCount() != 0;
|
||||
if (savedInstanceState != null) {
|
||||
displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW);
|
||||
}
|
||||
((MainActivity) getActivity()).setupToolbarToggle(toolbar, displayUpArrow);
|
||||
|
||||
viewPager = root.findViewById(R.id.viewpager);
|
||||
viewPager.setAdapter(new DownloadsPagerAdapter(this));
|
||||
|
@ -81,6 +86,12 @@ public class DownloadsFragment extends PagedToolbarFragment {
|
|||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
outState.putBoolean(KEY_UP_ARROW, displayUpArrow);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
|
|
@ -24,6 +24,7 @@ public class EpisodesFragment extends PagedToolbarFragment {
|
|||
|
||||
public static final String TAG = "EpisodesFragment";
|
||||
private static final String PREF_LAST_TAB_POSITION = "tab_position";
|
||||
private static final String KEY_UP_ARROW = "up_arrow";
|
||||
|
||||
private static final int POS_NEW_EPISODES = 0;
|
||||
private static final int POS_ALL_EPISODES = 1;
|
||||
|
@ -31,6 +32,7 @@ public class EpisodesFragment extends PagedToolbarFragment {
|
|||
private static final int TOTAL_COUNT = 3;
|
||||
|
||||
private TabLayout tabLayout;
|
||||
private boolean displayUpArrow;
|
||||
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
@ -44,7 +46,11 @@ public class EpisodesFragment extends PagedToolbarFragment {
|
|||
toolbar.setTitle(R.string.episodes_label);
|
||||
toolbar.inflateMenu(R.menu.episodes);
|
||||
MenuItemUtils.setupSearchItem(toolbar.getMenu(), (MainActivity) getActivity(), 0, "");
|
||||
((MainActivity) getActivity()).setupToolbarToggle(toolbar);
|
||||
displayUpArrow = getParentFragmentManager().getBackStackEntryCount() != 0;
|
||||
if (savedInstanceState != null) {
|
||||
displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW);
|
||||
}
|
||||
((MainActivity) getActivity()).setupToolbarToggle(toolbar, displayUpArrow);
|
||||
|
||||
ViewPager2 viewPager = rootView.findViewById(R.id.viewpager);
|
||||
viewPager.setAdapter(new EpisodesPagerAdapter(this));
|
||||
|
@ -88,6 +94,12 @@ public class EpisodesFragment extends PagedToolbarFragment {
|
|||
editor.apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
outState.putBoolean(KEY_UP_ARROW, displayUpArrow);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
static class EpisodesPagerAdapter extends FragmentStateAdapter {
|
||||
|
||||
EpisodesPagerAdapter(@NonNull Fragment fragment) {
|
||||
|
|
|
@ -383,6 +383,14 @@ public abstract class EpisodesListFragment extends Fragment {
|
|||
@NonNull
|
||||
protected abstract List<FeedItem> loadData();
|
||||
|
||||
/**
|
||||
* Load a new page of data as defined by {@link #page} and {@link #EPISODES_PER_PAGE}.
|
||||
* If the number of items returned is less than {@link #EPISODES_PER_PAGE},
|
||||
* it will be assumed that the underlying data is exhausted
|
||||
* and this method will not be called again.
|
||||
*
|
||||
* @return The items from the next page of data
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract List<FeedItem> loadMoreData();
|
||||
}
|
||||
|
|
|
@ -108,12 +108,7 @@ public class ExternalPlayerFragment extends Fragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean loadMediaInfo() {
|
||||
return ExternalPlayerFragment.this.loadMediaInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupGUI() {
|
||||
public void loadMediaInfo() {
|
||||
ExternalPlayerFragment.this.loadMediaInfo();
|
||||
}
|
||||
|
||||
|
@ -170,11 +165,11 @@ public class ExternalPlayerFragment extends Fragment {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean loadMediaInfo() {
|
||||
private void loadMediaInfo() {
|
||||
Log.d(TAG, "Loading media info");
|
||||
if (controller == null) {
|
||||
Log.w(TAG, "loadMediaInfo was called while PlaybackController was null!");
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposable != null) {
|
||||
|
@ -186,7 +181,6 @@ public class ExternalPlayerFragment extends Fragment {
|
|||
.subscribe(this::updateUi,
|
||||
error -> Log.e(TAG, Log.getStackTraceString(error)),
|
||||
() -> ((MainActivity) getActivity()).setPlayerVisible(false));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateUi(Playable media) {
|
||||
|
@ -198,14 +192,19 @@ public class ExternalPlayerFragment extends Fragment {
|
|||
feedName.setText(media.getFeedTitle());
|
||||
onPositionObserverUpdate();
|
||||
|
||||
RequestOptions options = new RequestOptions()
|
||||
.placeholder(R.color.light_gray)
|
||||
.error(R.color.light_gray)
|
||||
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
|
||||
.fitCenter()
|
||||
.dontAnimate();
|
||||
|
||||
Glide.with(getActivity())
|
||||
.load(ImageResourceUtils.getImageLocation(media))
|
||||
.apply(new RequestOptions()
|
||||
.placeholder(R.color.light_gray)
|
||||
.error(R.color.light_gray)
|
||||
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
|
||||
.fitCenter()
|
||||
.dontAnimate())
|
||||
.load(ImageResourceUtils.getEpisodeListImageLocation(media))
|
||||
.error(Glide.with(getActivity())
|
||||
.load(ImageResourceUtils.getFallbackImageLocation(media))
|
||||
.apply(options))
|
||||
.apply(options)
|
||||
.into(imgvCover);
|
||||
|
||||
if (controller != null && controller.isPlayingVideoLocally()) {
|
||||
|
|
|
@ -45,7 +45,7 @@ import de.danoeh.antennapod.core.storage.DownloadRequestException;
|
|||
import de.danoeh.antennapod.core.storage.StatisticsItem;
|
||||
import de.danoeh.antennapod.core.util.Converter;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import de.danoeh.antennapod.core.util.ThemeUtils;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
import de.danoeh.antennapod.core.util.syndication.HtmlToPlainText;
|
||||
import de.danoeh.antennapod.fragment.preferences.StatisticsFragment;
|
||||
import de.danoeh.antennapod.menuhandler.FeedMenuHandler;
|
||||
|
@ -130,6 +130,8 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic
|
|||
protected void doTint(Context themedContext) {
|
||||
toolbar.getMenu().findItem(R.id.visit_website_item)
|
||||
.setIcon(ThemeUtils.getDrawableFromAttr(themedContext, R.attr.location_web_site));
|
||||
toolbar.getMenu().findItem(R.id.share_parent)
|
||||
.setIcon(ThemeUtils.getDrawableFromAttr(themedContext, R.attr.ic_share));
|
||||
}
|
||||
};
|
||||
iconTintManager.updateTint();
|
||||
|
@ -201,7 +203,7 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic
|
|||
Log.d(TAG, "Author is " + feed.getAuthor());
|
||||
Log.d(TAG, "URL is " + feed.getDownload_url());
|
||||
Glide.with(getContext())
|
||||
.load(feed.getImageLocation())
|
||||
.load(feed.getImageUrl())
|
||||
.apply(new RequestOptions()
|
||||
.placeholder(R.color.light_gray)
|
||||
.error(R.color.light_gray)
|
||||
|
@ -210,7 +212,7 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic
|
|||
.dontAnimate())
|
||||
.into(imgvCover);
|
||||
Glide.with(getContext())
|
||||
.load(feed.getImageLocation())
|
||||
.load(feed.getImageUrl())
|
||||
.apply(new RequestOptions()
|
||||
.placeholder(R.color.image_readability_tint)
|
||||
.error(R.color.image_readability_tint)
|
||||
|
@ -284,9 +286,13 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic
|
|||
}
|
||||
|
||||
private void refreshToolbarState() {
|
||||
boolean shareLinkVisible = feed != null && feed.getLink() != null;
|
||||
boolean downloadUrlVisible = feed != null && !feed.isLocalFeed();
|
||||
|
||||
toolbar.getMenu().findItem(R.id.reconnect_local_folder).setVisible(feed != null && feed.isLocalFeed());
|
||||
toolbar.getMenu().findItem(R.id.share_download_url_item).setVisible(feed != null && !feed.isLocalFeed());
|
||||
toolbar.getMenu().findItem(R.id.share_link_item).setVisible(feed != null && feed.getLink() != null);
|
||||
toolbar.getMenu().findItem(R.id.share_download_url_item).setVisible(downloadUrlVisible);
|
||||
toolbar.getMenu().findItem(R.id.share_link_item).setVisible(shareLinkVisible);
|
||||
toolbar.getMenu().findItem(R.id.share_parent).setVisible(downloadUrlVisible || shareLinkVisible);
|
||||
toolbar.getMenu().findItem(R.id.visit_website_item).setVisible(feed != null && feed.getLink() != null
|
||||
&& IntentUtils.isCallable(getContext(), new Intent(Intent.ACTION_VIEW, Uri.parse(feed.getLink()))));
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ import android.widget.AdapterView;
|
|||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -57,8 +56,7 @@ import de.danoeh.antennapod.core.storage.DownloadRequestException;
|
|||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||
import de.danoeh.antennapod.core.util.FeedItemPermutors;
|
||||
import de.danoeh.antennapod.core.util.FeedItemUtil;
|
||||
import de.danoeh.antennapod.core.util.Optional;
|
||||
import de.danoeh.antennapod.core.util.ThemeUtils;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
import de.danoeh.antennapod.core.util.gui.MoreContentListFooterUtil;
|
||||
import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment;
|
||||
import de.danoeh.antennapod.dialog.FilterDialog;
|
||||
|
@ -89,6 +87,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
|
|||
Toolbar.OnMenuItemClickListener {
|
||||
private static final String TAG = "ItemlistFragment";
|
||||
private static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id";
|
||||
private static final String KEY_UP_ARROW = "up_arrow";
|
||||
|
||||
private FeedItemListAdapter adapter;
|
||||
private MoreContentListFooterUtil nextPageLoader;
|
||||
|
@ -106,6 +105,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
|
|||
private View header;
|
||||
private Toolbar toolbar;
|
||||
private ToolbarIconTintManager iconTintManager;
|
||||
private boolean displayUpArrow;
|
||||
|
||||
private long feedID;
|
||||
private Feed feed;
|
||||
|
@ -146,7 +146,11 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
|
|||
toolbar = root.findViewById(R.id.toolbar);
|
||||
toolbar.inflateMenu(R.menu.feedlist);
|
||||
toolbar.setOnMenuItemClickListener(this);
|
||||
((MainActivity) getActivity()).setupToolbarToggle(toolbar);
|
||||
displayUpArrow = getParentFragmentManager().getBackStackEntryCount() != 0;
|
||||
if (savedInstanceState != null) {
|
||||
displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW);
|
||||
}
|
||||
((MainActivity) getActivity()).setupToolbarToggle(toolbar, displayUpArrow);
|
||||
refreshToolbarState();
|
||||
|
||||
recyclerView = root.findViewById(R.id.recyclerView);
|
||||
|
@ -231,6 +235,12 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
|
|||
adapter = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
outState.putBoolean(KEY_UP_ARROW, displayUpArrow);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = new MenuItemUtils.UpdateRefreshMenuItemChecker() {
|
||||
@Override
|
||||
public boolean isRefreshing() {
|
||||
|
@ -451,10 +461,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
|
|||
if (feed.getItemFilter() != null) {
|
||||
FeedItemFilter filter = feed.getItemFilter();
|
||||
if (filter.getValues().length > 0) {
|
||||
if (feed.hasLastUpdateFailed()) {
|
||||
RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) txtvInformation.getLayoutParams();
|
||||
p.addRule(RelativeLayout.BELOW, R.id.txtvFailure);
|
||||
}
|
||||
txtvInformation.setText("{md-info-outline} " + this.getString(R.string.filtered_label));
|
||||
Iconify.addIcons(txtvInformation);
|
||||
txtvInformation.setOnClickListener((l) -> {
|
||||
|
@ -514,7 +520,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
|
|||
|
||||
private void loadFeedImage() {
|
||||
Glide.with(getActivity())
|
||||
.load(feed.getImageLocation())
|
||||
.load(feed.getImageUrl())
|
||||
.apply(new RequestOptions()
|
||||
.placeholder(R.color.image_readability_tint)
|
||||
.error(R.color.image_readability_tint)
|
||||
|
@ -524,7 +530,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
|
|||
.into(imgvBackground);
|
||||
|
||||
Glide.with(getActivity())
|
||||
.load(feed.getImageLocation())
|
||||
.load(feed.getImageUrl())
|
||||
.apply(new RequestOptions()
|
||||
.placeholder(R.color.light_gray)
|
||||
.error(R.color.light_gray)
|
||||
|
@ -542,27 +548,32 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
|
|||
disposable = Observable.fromCallable(this::loadData)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(result -> {
|
||||
feed = result.orElse(null);
|
||||
refreshHeaderView();
|
||||
displayList();
|
||||
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
|
||||
.subscribe(
|
||||
result -> {
|
||||
feed = result;
|
||||
refreshHeaderView();
|
||||
displayList();
|
||||
}, error -> {
|
||||
feed = null;
|
||||
refreshHeaderView();
|
||||
displayList();
|
||||
Log.e(TAG, Log.getStackTraceString(error));
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Optional<Feed> loadData() {
|
||||
Feed feed = DBReader.getFeed(feedID);
|
||||
if (feed != null && feed.getItemFilter() != null) {
|
||||
DBReader.loadAdditionalFeedItemListData(feed.getItems());
|
||||
FeedItemFilter filter = feed.getItemFilter();
|
||||
feed.setItems(filter.filter(feed.getItems()));
|
||||
@Nullable
|
||||
private Feed loadData() {
|
||||
Feed feed = DBReader.getFeed(feedID, true);
|
||||
if (feed == null) {
|
||||
return null;
|
||||
}
|
||||
if (feed != null && feed.getSortOrder() != null) {
|
||||
DBReader.loadAdditionalFeedItemListData(feed.getItems());
|
||||
if (feed.getSortOrder() != null) {
|
||||
List<FeedItem> feedItems = feed.getItems();
|
||||
FeedItemPermutors.getPermutor(feed.getSortOrder()).reorder(feedItems);
|
||||
feed.setItems(feedItems);
|
||||
}
|
||||
return Optional.ofNullable(feed);
|
||||
return feed;
|
||||
}
|
||||
|
||||
private static class FeedItemListAdapter extends EpisodeItemListAdapter {
|
||||
|
|
|
@ -164,6 +164,7 @@ public class FeedSettingsFragment extends Fragment {
|
|||
setupEpisodeFilterPreference();
|
||||
setupPlaybackSpeedPreference();
|
||||
setupFeedAutoSkipPreference();
|
||||
setupEpisodeNotificationPreference();
|
||||
setupTags();
|
||||
|
||||
updateAutoDeleteSummary();
|
||||
|
@ -198,7 +199,7 @@ public class FeedSettingsFragment extends Fragment {
|
|||
protected void onConfirmed(int skipIntro, int skipEnding) {
|
||||
feedPreferences.setFeedSkipIntro(skipIntro);
|
||||
feedPreferences.setFeedSkipEnding(skipEnding);
|
||||
feed.savePreferences();
|
||||
DBWriter.setFeedPreferences(feedPreferences);
|
||||
EventBus.getDefault().post(
|
||||
new SkipIntroEndingChangedEvent(feedPreferences.getFeedSkipIntro(),
|
||||
feedPreferences.getFeedSkipEnding(),
|
||||
|
@ -226,7 +227,7 @@ public class FeedSettingsFragment extends Fragment {
|
|||
feedPlaybackSpeedPreference.setEntries(entries);
|
||||
feedPlaybackSpeedPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
feedPreferences.setFeedPlaybackSpeed(Float.parseFloat((String) newValue));
|
||||
feed.savePreferences();
|
||||
DBWriter.setFeedPreferences(feedPreferences);
|
||||
updatePlaybackSpeedPreference();
|
||||
EventBus.getDefault().post(
|
||||
new SpeedPresetChangedEvent(feedPreferences.getFeedPlaybackSpeed(), feed.getId()));
|
||||
|
@ -240,7 +241,7 @@ public class FeedSettingsFragment extends Fragment {
|
|||
@Override
|
||||
protected void onConfirmed(FeedFilter filter) {
|
||||
feedPreferences.setFilter(filter);
|
||||
feed.savePreferences();
|
||||
DBWriter.setFeedPreferences(feedPreferences);
|
||||
}
|
||||
}.show();
|
||||
return false;
|
||||
|
@ -256,7 +257,7 @@ public class FeedSettingsFragment extends Fragment {
|
|||
protected void onConfirmed(String username, String password) {
|
||||
feedPreferences.setUsername(username);
|
||||
feedPreferences.setPassword(password);
|
||||
feed.savePreferences();
|
||||
DBWriter.setFeedPreferences(feedPreferences);
|
||||
}
|
||||
}.show();
|
||||
return false;
|
||||
|
@ -276,7 +277,7 @@ public class FeedSettingsFragment extends Fragment {
|
|||
feedPreferences.setAutoDeleteAction(FeedPreferences.AutoDeleteAction.NO);
|
||||
break;
|
||||
}
|
||||
feed.savePreferences();
|
||||
DBWriter.setFeedPreferences(feedPreferences);
|
||||
updateAutoDeleteSummary();
|
||||
return false;
|
||||
});
|
||||
|
@ -322,7 +323,7 @@ public class FeedSettingsFragment extends Fragment {
|
|||
feedPreferences.setVolumeAdaptionSetting(VolumeAdaptionSetting.HEAVY_REDUCTION);
|
||||
break;
|
||||
}
|
||||
feed.savePreferences();
|
||||
DBWriter.setFeedPreferences(feedPreferences);
|
||||
updateVolumeReductionValue();
|
||||
EventBus.getDefault().post(
|
||||
new VolumeAdaptionChangedEvent(feedPreferences.getVolumeAdaptionSetting(), feed.getId()));
|
||||
|
@ -353,7 +354,7 @@ public class FeedSettingsFragment extends Fragment {
|
|||
pref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
boolean checked = newValue == Boolean.TRUE;
|
||||
feedPreferences.setKeepUpdated(checked);
|
||||
feed.savePreferences();
|
||||
DBWriter.setFeedPreferences(feedPreferences);
|
||||
pref.setChecked(checked);
|
||||
return false;
|
||||
});
|
||||
|
@ -384,7 +385,7 @@ public class FeedSettingsFragment extends Fragment {
|
|||
boolean checked = newValue == Boolean.TRUE;
|
||||
|
||||
feedPreferences.setAutoDownload(checked);
|
||||
feed.savePreferences();
|
||||
DBWriter.setFeedPreferences(feedPreferences);
|
||||
updateAutoDownloadEnabled();
|
||||
ApplyToEpisodesDialog dialog = new ApplyToEpisodesDialog(getActivity(), checked);
|
||||
dialog.createNewDialog().show();
|
||||
|
@ -412,7 +413,7 @@ public class FeedSettingsFragment extends Fragment {
|
|||
feedPreferences.getTags().clear();
|
||||
feedPreferences.getTags().addAll(new HashSet<>(Arrays.asList(
|
||||
foldersString.split(FeedPreferences.TAG_SEPARATOR))));
|
||||
feed.savePreferences();
|
||||
DBWriter.setFeedPreferences(feedPreferences);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel_label, null)
|
||||
.show();
|
||||
|
@ -420,6 +421,19 @@ public class FeedSettingsFragment extends Fragment {
|
|||
});
|
||||
}
|
||||
|
||||
private void setupEpisodeNotificationPreference() {
|
||||
SwitchPreferenceCompat pref = findPreference("episodeNotification");
|
||||
|
||||
pref.setChecked(feedPreferences.getShowEpisodeNotification());
|
||||
pref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
boolean checked = newValue == Boolean.TRUE;
|
||||
feedPreferences.setShowEpisodeNotification(checked);
|
||||
DBWriter.setFeedPreferences(feedPreferences);
|
||||
pref.setChecked(checked);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private class ApplyToEpisodesDialog extends ConfirmationDialog {
|
||||
private final boolean autoDownload;
|
||||
|
||||
|
|
|
@ -10,6 +10,9 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
import de.danoeh.antennapod.core.util.playback.Timeline;
|
||||
import de.danoeh.antennapod.view.ShownotesWebView;
|
||||
|
@ -82,7 +85,15 @@ public class ItemDescriptionFragment extends Fragment {
|
|||
webViewLoader.dispose();
|
||||
}
|
||||
webViewLoader = Maybe.<String>create(emitter -> {
|
||||
Timeline timeline = new Timeline(getActivity(), controller.getMedia());
|
||||
Playable media = controller.getMedia();
|
||||
if (media instanceof FeedMedia) {
|
||||
FeedMedia feedMedia = ((FeedMedia) media);
|
||||
if (feedMedia.getItem() == null) {
|
||||
feedMedia.setItem(DBReader.getFeedItem(feedMedia.getItemId()));
|
||||
}
|
||||
DBReader.loadDescriptionOfFeedItem(feedMedia.getItem());
|
||||
}
|
||||
Timeline timeline = new Timeline(getActivity(), media.getDescription(), media.getDuration());
|
||||
emitter.onSuccess(timeline.processShownotes());
|
||||
}).subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
@ -140,14 +151,8 @@ public class ItemDescriptionFragment extends Fragment {
|
|||
super.onStart();
|
||||
controller = new PlaybackController(getActivity()) {
|
||||
@Override
|
||||
public boolean loadMediaInfo() {
|
||||
public void loadMediaInfo() {
|
||||
load();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupGUI() {
|
||||
ItemDescriptionFragment.this.load();
|
||||
}
|
||||
};
|
||||
controller.init();
|
||||
|
|
|
@ -57,7 +57,7 @@ import de.danoeh.antennapod.core.storage.DBReader;
|
|||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||
import de.danoeh.antennapod.core.util.Converter;
|
||||
import de.danoeh.antennapod.core.util.DateUtils;
|
||||
import de.danoeh.antennapod.core.util.ThemeUtils;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
import de.danoeh.antennapod.core.util.playback.Timeline;
|
||||
import de.danoeh.antennapod.view.ShownotesWebView;
|
||||
|
@ -238,7 +238,12 @@ public class ItemFragment extends Fragment {
|
|||
public void onStart() {
|
||||
super.onStart();
|
||||
EventBus.getDefault().register(this);
|
||||
controller = new PlaybackController(getActivity());
|
||||
controller = new PlaybackController(getActivity()) {
|
||||
@Override
|
||||
public void loadMediaInfo() {
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
controller.init();
|
||||
}
|
||||
|
||||
|
@ -291,14 +296,19 @@ public class ItemFragment extends Fragment {
|
|||
txtvPublished.setContentDescription(DateUtils.formatForAccessibility(getContext(), item.getPubDate()));
|
||||
}
|
||||
|
||||
RequestOptions options = new RequestOptions()
|
||||
.error(R.color.light_gray)
|
||||
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
|
||||
.transforms(new FitCenter(),
|
||||
new RoundedCorners((int) (4 * getResources().getDisplayMetrics().density)))
|
||||
.dontAnimate();
|
||||
|
||||
Glide.with(getActivity())
|
||||
.load(ImageResourceUtils.getImageLocation(item))
|
||||
.apply(new RequestOptions()
|
||||
.error(R.color.light_gray)
|
||||
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
|
||||
.transforms(new FitCenter(),
|
||||
new RoundedCorners((int) (4 * getResources().getDisplayMetrics().density)))
|
||||
.dontAnimate())
|
||||
.load(item.getImageLocation())
|
||||
.error(Glide.with(getActivity())
|
||||
.load(ImageResourceUtils.getFallbackImageLocation(item))
|
||||
.apply(options))
|
||||
.apply(options)
|
||||
.into(imgvCover);
|
||||
updateButtons();
|
||||
}
|
||||
|
@ -429,7 +439,9 @@ public class ItemFragment extends Fragment {
|
|||
FeedItem feedItem = DBReader.getFeedItem(itemId);
|
||||
Context context = getContext();
|
||||
if (feedItem != null && context != null) {
|
||||
Timeline t = new Timeline(context, feedItem);
|
||||
int duration = feedItem.getMedia() != null ? feedItem.getMedia().getDuration() : Integer.MAX_VALUE;
|
||||
DBReader.loadDescriptionOfFeedItem(feedItem);
|
||||
Timeline t = new Timeline(context, feedItem.getDescription(), duration);
|
||||
webviewData = t.processShownotes();
|
||||
}
|
||||
return feedItem;
|
||||
|
|
|
@ -424,7 +424,7 @@ public class NavDrawerFragment extends Fragment implements SharedPreferences.OnS
|
|||
flatItemList = result.second;
|
||||
updateSelection(); // Selected item might be a feed
|
||||
navAdapter.notifyDataSetChanged();
|
||||
progressBar.setVisibility(View.GONE);
|
||||
progressBar.setVisibility(View.GONE); // Stays hidden once there is something in the list
|
||||
}, error -> {
|
||||
Log.e(TAG, Log.getStackTraceString(error));
|
||||
progressBar.setVisibility(View.GONE);
|
||||
|
|
|
@ -41,6 +41,7 @@ import java.util.List;
|
|||
|
||||
public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuItemClickListener {
|
||||
public static final String TAG = "PlaybackHistoryFragment";
|
||||
private static final String KEY_UP_ARROW = "up_arrow";
|
||||
|
||||
private List<FeedItem> playbackHistory;
|
||||
private PlaybackHistoryListAdapter adapter;
|
||||
|
@ -49,6 +50,7 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI
|
|||
private EmptyViewHandler emptyView;
|
||||
private ProgressBar progressBar;
|
||||
private Toolbar toolbar;
|
||||
private boolean displayUpArrow;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
@ -63,7 +65,11 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI
|
|||
toolbar = root.findViewById(R.id.toolbar);
|
||||
toolbar.setTitle(R.string.playback_history_label);
|
||||
toolbar.setOnMenuItemClickListener(this);
|
||||
((MainActivity) getActivity()).setupToolbarToggle(toolbar);
|
||||
displayUpArrow = getParentFragmentManager().getBackStackEntryCount() != 0;
|
||||
if (savedInstanceState != null) {
|
||||
displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW);
|
||||
}
|
||||
((MainActivity) getActivity()).setupToolbarToggle(toolbar, displayUpArrow);
|
||||
toolbar.inflateMenu(R.menu.playback_history);
|
||||
refreshToolbarState();
|
||||
|
||||
|
@ -98,6 +104,12 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
outState.putBoolean(KEY_UP_ARROW, displayUpArrow);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(FeedItemEvent event) {
|
||||
Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]");
|
||||
|
|
|
@ -12,6 +12,7 @@ import android.view.ViewGroup;
|
|||
import android.widget.CheckBox;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
@ -67,6 +68,7 @@ import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_REM
|
|||
*/
|
||||
public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickListener {
|
||||
public static final String TAG = "QueueFragment";
|
||||
private static final String KEY_UP_ARROW = "up_arrow";
|
||||
|
||||
private TextView infoBar;
|
||||
private EpisodeItemListRecyclerView recyclerView;
|
||||
|
@ -74,6 +76,7 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
|
|||
private EmptyViewHandler emptyView;
|
||||
private ProgressBar progLoading;
|
||||
private Toolbar toolbar;
|
||||
private boolean displayUpArrow;
|
||||
|
||||
private List<FeedItem> queue;
|
||||
|
||||
|
@ -420,7 +423,11 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
|
|||
View root = inflater.inflate(R.layout.queue_fragment, container, false);
|
||||
toolbar = root.findViewById(R.id.toolbar);
|
||||
toolbar.setOnMenuItemClickListener(this);
|
||||
((MainActivity) getActivity()).setupToolbarToggle(toolbar);
|
||||
displayUpArrow = getParentFragmentManager().getBackStackEntryCount() != 0;
|
||||
if (savedInstanceState != null) {
|
||||
displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW);
|
||||
}
|
||||
((MainActivity) getActivity()).setupToolbarToggle(toolbar, displayUpArrow);
|
||||
toolbar.inflateMenu(R.menu.queue);
|
||||
MenuItemUtils.setupSearchItem(toolbar.getMenu(), (MainActivity) getActivity(), 0, "");
|
||||
refreshToolbarState();
|
||||
|
@ -530,6 +537,12 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi
|
|||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
outState.putBoolean(KEY_UP_ARROW, displayUpArrow);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
private void onFragmentLoaded(final boolean restoreScrollPosition) {
|
||||
if (queue != null && queue.size() > 0) {
|
||||
if (recyclerAdapter == null) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import android.os.Bundle;
|
|||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.widget.ProgressBar;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
@ -67,6 +68,8 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem
|
|||
public static final String TAG = "SubscriptionFragment";
|
||||
private static final String PREFS = "SubscriptionFragment";
|
||||
private static final String PREF_NUM_COLUMNS = "columns";
|
||||
private static final String KEY_UP_ARROW = "up_arrow";
|
||||
|
||||
private static final int MIN_NUM_COLUMNS = 2;
|
||||
private static final int[] COLUMN_CHECKBOX_IDS = {
|
||||
R.id.subscription_num_columns_2,
|
||||
|
@ -85,6 +88,7 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem
|
|||
|
||||
private int mPosition = -1;
|
||||
private boolean isUpdatingFeeds = false;
|
||||
private boolean displayUpArrow;
|
||||
|
||||
private Disposable disposable;
|
||||
private SharedPreferences prefs;
|
||||
|
@ -103,7 +107,11 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem
|
|||
View root = inflater.inflate(R.layout.fragment_subscriptions, container, false);
|
||||
toolbar = root.findViewById(R.id.toolbar);
|
||||
toolbar.setOnMenuItemClickListener(this);
|
||||
((MainActivity) getActivity()).setupToolbarToggle(toolbar);
|
||||
displayUpArrow = getParentFragmentManager().getBackStackEntryCount() != 0;
|
||||
if (savedInstanceState != null) {
|
||||
displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW);
|
||||
}
|
||||
((MainActivity) getActivity()).setupToolbarToggle(toolbar, displayUpArrow);
|
||||
toolbar.inflateMenu(R.menu.subscriptions);
|
||||
for (int i = 0; i < COLUMN_CHECKBOX_IDS.length; i++) {
|
||||
// Do this in Java to localize numbers
|
||||
|
@ -130,6 +138,12 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem
|
|||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
outState.putBoolean(KEY_UP_ARROW, displayUpArrow);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
private void refreshToolbarState() {
|
||||
int columns = prefs.getInt(PREF_NUM_COLUMNS, getDefaultNumOfColumns());
|
||||
toolbar.getMenu().findItem(COLUMN_CHECKBOX_IDS[columns - MIN_NUM_COLUMNS]).setChecked(true);
|
||||
|
@ -218,16 +232,19 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem
|
|||
disposable.dispose();
|
||||
}
|
||||
emptyView.hide();
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
disposable = Observable.fromCallable(DBReader::getNavDrawerData)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(result -> {
|
||||
navDrawerData = result;
|
||||
subscriptionAdapter.notifyDataSetChanged();
|
||||
emptyView.updateVisibility();
|
||||
progressBar.setVisibility(View.GONE);
|
||||
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
|
||||
.subscribe(
|
||||
result -> {
|
||||
navDrawerData = result;
|
||||
subscriptionAdapter.notifyDataSetChanged();
|
||||
emptyView.updateVisibility();
|
||||
progressBar.setVisibility(View.GONE); // Keep hidden to avoid flickering while refreshing
|
||||
}, error -> {
|
||||
Log.e(TAG, Log.getStackTraceString(error));
|
||||
progressBar.setVisibility(View.GONE);
|
||||
});
|
||||
|
||||
if (UserPreferences.getSubscriptionsFilter().isEnabled()) {
|
||||
feedsFilteredMsg.setText("{md-info-outline} " + getString(R.string.subscriptions_are_filtered));
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
package de.danoeh.antennapod.fragment.gpodnet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
@ -13,9 +10,7 @@ import android.widget.Button;
|
|||
import android.widget.GridView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
|
||||
|
@ -25,6 +20,12 @@ import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
|
|||
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Displays a list of GPodnetPodcast-Objects in a GridView
|
||||
|
@ -36,6 +37,7 @@ public abstract class PodcastListFragment extends Fragment {
|
|||
private ProgressBar progressBar;
|
||||
private TextView txtvError;
|
||||
private Button butRetry;
|
||||
private Disposable disposable;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
|
@ -64,60 +66,44 @@ public abstract class PodcastListFragment extends Fragment {
|
|||
protected abstract List<GpodnetPodcast> loadPodcastData(GpodnetService service) throws GpodnetServiceException;
|
||||
|
||||
final void loadData() {
|
||||
AsyncTask<Void, Void, List<GpodnetPodcast>> loaderTask = new AsyncTask<Void, Void, List<GpodnetPodcast>>() {
|
||||
volatile Exception exception = null;
|
||||
|
||||
@Override
|
||||
protected List<GpodnetPodcast> doInBackground(Void... params) {
|
||||
try {
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
gridView.setVisibility(View.GONE);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
txtvError.setVisibility(View.GONE);
|
||||
butRetry.setVisibility(View.GONE);
|
||||
disposable = Observable.fromCallable(
|
||||
() -> {
|
||||
GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(),
|
||||
GpodnetPreferences.getHostname());
|
||||
GpodnetPreferences.getHosturl());
|
||||
return loadPodcastData(service);
|
||||
} catch (GpodnetServiceException e) {
|
||||
exception = e;
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
podcasts -> {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
butRetry.setVisibility(View.GONE);
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<GpodnetPodcast> gpodnetPodcasts) {
|
||||
super.onPostExecute(gpodnetPodcasts);
|
||||
final Context context = getActivity();
|
||||
if (context != null && gpodnetPodcasts != null && gpodnetPodcasts.size() > 0) {
|
||||
PodcastListAdapter listAdapter = new PodcastListAdapter(context, 0, gpodnetPodcasts);
|
||||
gridView.setAdapter(listAdapter);
|
||||
listAdapter.notifyDataSetChanged();
|
||||
|
||||
progressBar.setVisibility(View.GONE);
|
||||
gridView.setVisibility(View.VISIBLE);
|
||||
txtvError.setVisibility(View.GONE);
|
||||
butRetry.setVisibility(View.GONE);
|
||||
} else if (context != null && gpodnetPodcasts != null) {
|
||||
gridView.setVisibility(View.GONE);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
txtvError.setText(getString(R.string.search_status_no_results));
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
butRetry.setVisibility(View.GONE);
|
||||
} else if (context != null) {
|
||||
gridView.setVisibility(View.GONE);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
txtvError.setText(getString(R.string.error_msg_prefix) + exception.getMessage());
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
butRetry.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
gridView.setVisibility(View.GONE);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
txtvError.setVisibility(View.GONE);
|
||||
butRetry.setVisibility(View.GONE);
|
||||
}
|
||||
};
|
||||
|
||||
loaderTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
if (podcasts.size() > 0) {
|
||||
PodcastListAdapter listAdapter = new PodcastListAdapter(getContext(), 0, podcasts);
|
||||
gridView.setAdapter(listAdapter);
|
||||
listAdapter.notifyDataSetChanged();
|
||||
gridView.setVisibility(View.VISIBLE);
|
||||
txtvError.setVisibility(View.GONE);
|
||||
} else {
|
||||
gridView.setVisibility(View.GONE);
|
||||
txtvError.setText(getString(R.string.search_status_no_results));
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}, error -> {
|
||||
gridView.setVisibility(View.GONE);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
txtvError.setText(getString(R.string.error_msg_prefix) + error.getMessage());
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
butRetry.setVisibility(View.VISIBLE);
|
||||
Log.e(TAG, Log.getStackTraceString(error));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +1,34 @@
|
|||
package de.danoeh.antennapod.fragment.gpodnet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.ListFragment;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.adapter.gpodnet.TagListAdapter;
|
||||
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetTag;
|
||||
|
||||
import java.util.List;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class TagListFragment extends ListFragment {
|
||||
private static final int COUNT = 50;
|
||||
private static final String TAG = "TagListFragment";
|
||||
private Disposable disposable;
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
getListView().setOnItemClickListener((parent, view1, position, id) -> {
|
||||
GpodnetTag tag = (GpodnetTag) getListAdapter().getItem(position);
|
||||
MainActivity activity = (MainActivity) getActivity();
|
||||
activity.loadChildFragment(TagFragment.newInstance(tag));
|
||||
((MainActivity) getActivity()).loadChildFragment(TagFragment.newInstance(tag));
|
||||
});
|
||||
|
||||
startLoadTask();
|
||||
|
@ -35,59 +37,36 @@ public class TagListFragment extends ListFragment {
|
|||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
cancelLoadTask();
|
||||
}
|
||||
|
||||
private AsyncTask<Void, Void, List<GpodnetTag>> loadTask;
|
||||
|
||||
private void cancelLoadTask() {
|
||||
if (loadTask != null && !loadTask.isCancelled()) {
|
||||
loadTask.cancel(true);
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void startLoadTask() {
|
||||
cancelLoadTask();
|
||||
loadTask = new AsyncTask<Void, Void, List<GpodnetTag>>() {
|
||||
private Exception exception;
|
||||
|
||||
@Override
|
||||
protected List<GpodnetTag> doInBackground(Void... params) {
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
setListShown(false);
|
||||
disposable = Observable.fromCallable(
|
||||
() -> {
|
||||
GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(),
|
||||
GpodnetPreferences.getHostname());
|
||||
try {
|
||||
return service.getTopTags(COUNT);
|
||||
} catch (GpodnetServiceException e) {
|
||||
e.printStackTrace();
|
||||
exception = e;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
setListShown(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<GpodnetTag> gpodnetTags) {
|
||||
super.onPostExecute(gpodnetTags);
|
||||
final Context context = getActivity();
|
||||
if (context != null) {
|
||||
if (gpodnetTags != null) {
|
||||
setListAdapter(new TagListAdapter(context, android.R.layout.simple_list_item_1, gpodnetTags));
|
||||
} else if (exception != null) {
|
||||
GpodnetPreferences.getHosturl());
|
||||
return service.getTopTags(COUNT);
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
tags -> {
|
||||
setListAdapter(new TagListAdapter(getContext(), android.R.layout.simple_list_item_1, tags));
|
||||
setListShown(true);
|
||||
}, error -> {
|
||||
TextView txtvError = new TextView(getActivity());
|
||||
txtvError.setText(exception.getMessage());
|
||||
txtvError.setText(error.getMessage());
|
||||
getListView().setEmptyView(txtvError);
|
||||
}
|
||||
setListShown(true);
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
loadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
setListShown(true);
|
||||
Log.e(TAG, Log.getStackTraceString(error));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -174,7 +174,9 @@ public class AutoDownloadPreferencesFragment extends PreferenceFragmentCompat {
|
|||
String[] entries = new String[values.length];
|
||||
for (int x = 0; x < values.length; x++) {
|
||||
int v = Integer.parseInt(values[x]);
|
||||
if (v == UserPreferences.EPISODE_CLEANUP_QUEUE) {
|
||||
if (v == UserPreferences.EPISODE_CLEANUP_EXCEPT_FAVORITE) {
|
||||
entries[x] = res.getString(R.string.episode_cleanup_except_favorite_removal);
|
||||
} else if (v == UserPreferences.EPISODE_CLEANUP_QUEUE) {
|
||||
entries[x] = res.getString(R.string.episode_cleanup_queue_removal);
|
||||
} else if (v == UserPreferences.EPISODE_CLEANUP_NULL){
|
||||
entries[x] = res.getString(R.string.episode_cleanup_never);
|
||||
|
|
|
@ -0,0 +1,307 @@
|
|||
package de.danoeh.antennapod.fragment.preferences;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ViewFlipper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
|
||||
import de.danoeh.antennapod.core.sync.SyncService;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetDevice;
|
||||
import de.danoeh.antennapod.core.util.FileNameGenerator;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Guides the user through the authentication process.
|
||||
*/
|
||||
public class GpodderAuthenticationFragment extends DialogFragment {
|
||||
public static final String TAG = "GpodnetAuthActivity";
|
||||
|
||||
private ViewFlipper viewFlipper;
|
||||
|
||||
private static final int STEP_DEFAULT = -1;
|
||||
private static final int STEP_HOSTNAME = 0;
|
||||
private static final int STEP_LOGIN = 1;
|
||||
private static final int STEP_DEVICE = 2;
|
||||
private static final int STEP_FINISH = 3;
|
||||
|
||||
private int currentStep = -1;
|
||||
|
||||
private GpodnetService service;
|
||||
private volatile String username;
|
||||
private volatile String password;
|
||||
private volatile GpodnetDevice selectedDevice;
|
||||
private List<GpodnetDevice> devices;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(getContext());
|
||||
dialog.setTitle(GpodnetService.DEFAULT_BASE_HOST);
|
||||
dialog.setNegativeButton(R.string.cancel_label, null);
|
||||
dialog.setCancelable(false);
|
||||
this.setCancelable(false);
|
||||
|
||||
View root = View.inflate(getContext(), R.layout.gpodnetauth_dialog, null);
|
||||
viewFlipper = root.findViewById(R.id.viewflipper);
|
||||
advance();
|
||||
dialog.setView(root);
|
||||
|
||||
return dialog.create();
|
||||
}
|
||||
|
||||
private void setupHostView(View view) {
|
||||
final Button selectHost = view.findViewById(R.id.chooseHostButton);
|
||||
final RadioGroup serverRadioGroup = view.findViewById(R.id.serverRadioGroup);
|
||||
final EditText serverUrlText = view.findViewById(R.id.serverUrlText);
|
||||
|
||||
if (!GpodnetService.DEFAULT_BASE_HOST.equals(GpodnetPreferences.getHosturl())) {
|
||||
serverUrlText.setText(GpodnetPreferences.getHosturl());
|
||||
}
|
||||
final TextInputLayout serverUrlTextInput = view.findViewById(R.id.serverUrlTextInput);
|
||||
serverRadioGroup.setOnCheckedChangeListener((group, checkedId) -> {
|
||||
serverUrlTextInput.setVisibility(checkedId == R.id.customServerRadio ? View.VISIBLE : View.GONE);
|
||||
});
|
||||
selectHost.setOnClickListener(v -> {
|
||||
if (serverRadioGroup.getCheckedRadioButtonId() == R.id.customServerRadio) {
|
||||
GpodnetPreferences.setHosturl(serverUrlText.getText().toString());
|
||||
} else {
|
||||
GpodnetPreferences.setHosturl(GpodnetService.DEFAULT_BASE_HOST);
|
||||
}
|
||||
service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetPreferences.getHosturl());
|
||||
getDialog().setTitle(GpodnetPreferences.getHosturl());
|
||||
advance();
|
||||
});
|
||||
}
|
||||
|
||||
private void setupLoginView(View view) {
|
||||
final EditText username = view.findViewById(R.id.etxtUsername);
|
||||
final EditText password = view.findViewById(R.id.etxtPassword);
|
||||
final Button login = view.findViewById(R.id.butLogin);
|
||||
final TextView txtvError = view.findViewById(R.id.credentialsError);
|
||||
final ProgressBar progressBar = view.findViewById(R.id.progBarLogin);
|
||||
final TextView createAccount = view.findViewById(R.id.createAccountButton);
|
||||
final TextView createAccountWarning = view.findViewById(R.id.createAccountWarning);
|
||||
|
||||
createAccount.setPaintFlags(createAccount.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||
createAccount.setOnClickListener(v -> IntentUtils.openInBrowser(getContext(), "https://gpodder.net/register/"));
|
||||
|
||||
if (GpodnetPreferences.getHosturl().startsWith("http://")) {
|
||||
createAccountWarning.setVisibility(View.VISIBLE);
|
||||
}
|
||||
password.setOnEditorActionListener((v, actionID, event) ->
|
||||
actionID == EditorInfo.IME_ACTION_GO && login.performClick());
|
||||
|
||||
login.setOnClickListener(v -> {
|
||||
final String usernameStr = username.getText().toString();
|
||||
final String passwordStr = password.getText().toString();
|
||||
|
||||
if (usernameHasUnwantedChars(usernameStr)) {
|
||||
txtvError.setText(R.string.gpodnetsync_username_characters_error);
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
|
||||
login.setEnabled(false);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
txtvError.setVisibility(View.GONE);
|
||||
InputMethodManager inputManager = (InputMethodManager) getContext()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
inputManager.hideSoftInputFromWindow(login.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
|
||||
Completable.fromAction(() -> {
|
||||
service.authenticate(usernameStr, passwordStr);
|
||||
devices = service.getDevices();
|
||||
GpodderAuthenticationFragment.this.username = usernameStr;
|
||||
GpodderAuthenticationFragment.this.password = passwordStr;
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(() -> {
|
||||
login.setEnabled(true);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
advance();
|
||||
}, error -> {
|
||||
login.setEnabled(true);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
txtvError.setText(error.getCause().getMessage());
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void setupDeviceView(View view) {
|
||||
final EditText deviceName = view.findViewById(R.id.deviceName);
|
||||
final LinearLayout devicesContainer = view.findViewById(R.id.devicesContainer);
|
||||
deviceName.setText(generateDeviceName());
|
||||
|
||||
MaterialButton createDeviceButton = view.findViewById(R.id.createDeviceButton);
|
||||
createDeviceButton.setOnClickListener(v -> createDevice(view));
|
||||
|
||||
for (GpodnetDevice device : devices) {
|
||||
View row = View.inflate(getContext(), R.layout.gpodnetauth_device_row, null);
|
||||
Button selectDeviceButton = row.findViewById(R.id.selectDeviceButton);
|
||||
selectDeviceButton.setOnClickListener(v -> {
|
||||
selectedDevice = device;
|
||||
advance();
|
||||
});
|
||||
selectDeviceButton.setText(device.getCaption());
|
||||
devicesContainer.addView(row);
|
||||
}
|
||||
}
|
||||
|
||||
private void createDevice(View view) {
|
||||
final EditText deviceName = view.findViewById(R.id.deviceName);
|
||||
final TextView txtvError = view.findViewById(R.id.deviceSelectError);
|
||||
final ProgressBar progBarCreateDevice = view.findViewById(R.id.progbarCreateDevice);
|
||||
|
||||
String deviceNameStr = deviceName.getText().toString();
|
||||
if (isDeviceInList(deviceNameStr)) {
|
||||
return;
|
||||
}
|
||||
progBarCreateDevice.setVisibility(View.VISIBLE);
|
||||
txtvError.setVisibility(View.GONE);
|
||||
deviceName.setEnabled(false);
|
||||
|
||||
Observable.fromCallable(() -> {
|
||||
String deviceId = generateDeviceId(deviceNameStr);
|
||||
service.configureDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE);
|
||||
return new GpodnetDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0);
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(device -> {
|
||||
progBarCreateDevice.setVisibility(View.GONE);
|
||||
selectedDevice = device;
|
||||
advance();
|
||||
}, error -> {
|
||||
deviceName.setEnabled(true);
|
||||
progBarCreateDevice.setVisibility(View.GONE);
|
||||
txtvError.setText(error.getMessage());
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
});
|
||||
}
|
||||
|
||||
private String generateDeviceName() {
|
||||
String baseName = getString(R.string.gpodnetauth_device_name_default, Build.MODEL);
|
||||
String name = baseName;
|
||||
int num = 1;
|
||||
while (isDeviceInList(name)) {
|
||||
name = baseName + " (" + num + ")";
|
||||
num++;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private String generateDeviceId(String name) {
|
||||
// devices names must be of a certain form:
|
||||
// https://gpoddernet.readthedocs.org/en/latest/api/reference/general.html#devices
|
||||
return FileNameGenerator.generateFileName(name).replaceAll("\\W", "_").toLowerCase(Locale.US);
|
||||
}
|
||||
|
||||
private boolean isDeviceInList(String name) {
|
||||
if (devices == null) {
|
||||
return false;
|
||||
}
|
||||
String id = generateDeviceId(name);
|
||||
for (GpodnetDevice device : devices) {
|
||||
if (device.getId().equals(id) || device.getCaption().equals(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private GpodnetDevice findDevice(String id) {
|
||||
if (devices == null) {
|
||||
return null;
|
||||
}
|
||||
for (GpodnetDevice device : devices) {
|
||||
if (device.getId().equals(id)) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void setupFinishView(View view) {
|
||||
final Button sync = view.findViewById(R.id.butSyncNow);
|
||||
|
||||
sync.setOnClickListener(v -> {
|
||||
dismiss();
|
||||
SyncService.sync(getContext());
|
||||
});
|
||||
}
|
||||
|
||||
private void writeLoginCredentials() {
|
||||
GpodnetPreferences.setUsername(username);
|
||||
GpodnetPreferences.setPassword(password);
|
||||
GpodnetPreferences.setDeviceID(selectedDevice.getId());
|
||||
}
|
||||
|
||||
private void advance() {
|
||||
if (currentStep < STEP_FINISH) {
|
||||
|
||||
View view = viewFlipper.getChildAt(currentStep + 1);
|
||||
if (currentStep == STEP_DEFAULT) {
|
||||
setupHostView(view);
|
||||
} else if (currentStep == STEP_HOSTNAME) {
|
||||
setupLoginView(view);
|
||||
} else if (currentStep == STEP_LOGIN) {
|
||||
if (username == null || password == null) {
|
||||
throw new IllegalStateException("Username and password must not be null here");
|
||||
} else {
|
||||
setupDeviceView(view);
|
||||
}
|
||||
} else if (currentStep == STEP_DEVICE) {
|
||||
if (selectedDevice == null) {
|
||||
throw new IllegalStateException("Device must not be null here");
|
||||
} else {
|
||||
writeLoginCredentials();
|
||||
setupFinishView(view);
|
||||
}
|
||||
}
|
||||
if (currentStep != STEP_DEFAULT) {
|
||||
viewFlipper.showNext();
|
||||
}
|
||||
currentStep++;
|
||||
} else {
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean usernameHasUnwantedChars(String username) {
|
||||
Pattern special = Pattern.compile("[!@#$%&*()+=|<>?{}\\[\\]~]");
|
||||
Matcher containsUnwantedChars = special.matcher(username);
|
||||
return containsUnwantedChars.find();
|
||||
}
|
||||
}
|
|
@ -14,19 +14,16 @@ import de.danoeh.antennapod.core.event.SyncServiceEvent;
|
|||
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
||||
import de.danoeh.antennapod.core.sync.SyncService;
|
||||
import de.danoeh.antennapod.dialog.AuthenticationDialog;
|
||||
import de.danoeh.antennapod.dialog.GpodnetSetHostnameDialog;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
|
||||
public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
|
||||
private static final String PREF_GPODNET_LOGIN = "pref_gpodnet_authenticate";
|
||||
private static final String PREF_GPODNET_SETLOGIN_INFORMATION = "pref_gpodnet_setlogin_information";
|
||||
private static final String PREF_GPODNET_SYNC = "pref_gpodnet_sync";
|
||||
private static final String PREF_GPODNET_FORCE_FULL_SYNC = "pref_gpodnet_force_full_sync";
|
||||
private static final String PREF_GPODNET_LOGOUT = "pref_gpodnet_logout";
|
||||
private static final String PREF_GPODNET_HOSTNAME = "pref_gpodnet_hostname";
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
|
@ -51,6 +48,7 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
|
|||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
|
||||
public void syncStatusChanged(SyncServiceEvent event) {
|
||||
updateGpodnetPreferenceScreen();
|
||||
if (!GpodnetPreferences.loggedIn()) {
|
||||
return;
|
||||
}
|
||||
|
@ -66,6 +64,10 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
|
|||
private void setupGpodderScreen() {
|
||||
final Activity activity = getActivity();
|
||||
|
||||
findPreference(PREF_GPODNET_LOGIN).setOnPreferenceClickListener(preference -> {
|
||||
new GpodderAuthenticationFragment().show(getChildFragmentManager(), GpodderAuthenticationFragment.TAG);
|
||||
return true;
|
||||
});
|
||||
findPreference(PREF_GPODNET_SETLOGIN_INFORMATION)
|
||||
.setOnPreferenceClickListener(preference -> {
|
||||
AuthenticationDialog dialog = new AuthenticationDialog(activity,
|
||||
|
@ -94,11 +96,6 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
|
|||
updateGpodnetPreferenceScreen();
|
||||
return true;
|
||||
});
|
||||
findPreference(PREF_GPODNET_HOSTNAME).setOnPreferenceClickListener(preference -> {
|
||||
GpodnetSetHostnameDialog.createDialog(activity).setOnDismissListener(
|
||||
dialog -> updateGpodnetPreferenceScreen());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void updateGpodnetPreferenceScreen() {
|
||||
|
@ -119,7 +116,6 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
|
|||
} else {
|
||||
findPreference(PREF_GPODNET_LOGOUT).setSummary(null);
|
||||
}
|
||||
findPreference(PREF_GPODNET_HOSTNAME).setSummary(GpodnetPreferences.getHostname());
|
||||
}
|
||||
|
||||
private void updateLastGpodnetSyncReport(boolean successful, long lastTime) {
|
||||
|
|
|
@ -71,13 +71,13 @@ public class NetworkPreferencesFragment extends PreferenceFragmentCompat {
|
|||
Context context = getActivity().getApplicationContext();
|
||||
String val;
|
||||
long interval = UserPreferences.getUpdateInterval();
|
||||
if(interval > 0) {
|
||||
if (interval > 0) {
|
||||
int hours = (int) TimeUnit.MILLISECONDS.toHours(interval);
|
||||
String hoursStr = context.getResources().getQuantityString(R.plurals.time_hours_quantified, hours, hours);
|
||||
val = String.format(context.getString(R.string.pref_autoUpdateIntervallOrTime_every), hoursStr);
|
||||
val = context.getResources().getQuantityString(
|
||||
R.plurals.pref_autoUpdateIntervallOrTime_every_hours, hours, hours);
|
||||
} else {
|
||||
int[] timeOfDay = UserPreferences.getUpdateTimeOfDay();
|
||||
if(timeOfDay.length == 2) {
|
||||
if (timeOfDay.length == 2) {
|
||||
Calendar cal = new GregorianCalendar();
|
||||
cal.set(Calendar.HOUR_OF_DAY, timeOfDay[0]);
|
||||
cal.set(Calendar.MINUTE, timeOfDay[1]);
|
||||
|
|
|
@ -9,11 +9,14 @@ import androidx.preference.PreferenceFragmentCompat;
|
|||
import android.widget.ListView;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.PreferenceActivity;
|
||||
import de.danoeh.antennapod.core.event.PlayerStatusEvent;
|
||||
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.dialog.SubscriptionsFilterDialog;
|
||||
import de.danoeh.antennapod.dialog.FeedSortDialog;
|
||||
import de.danoeh.antennapod.fragment.NavDrawerFragment;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -37,8 +40,17 @@ public class UserInterfacePreferencesFragment extends PreferenceFragmentCompat {
|
|||
(preference, newValue) -> {
|
||||
getActivity().recreate();
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
findPreference(UserPreferences.PREF_SHOW_TIME_LEFT)
|
||||
.setOnPreferenceChangeListener(
|
||||
(preference, newValue) -> {
|
||||
UserPreferences.setShowRemainTimeSetting((Boolean) newValue);
|
||||
EventBus.getDefault().post(new UnreadItemsUpdateEvent());
|
||||
EventBus.getDefault().post(new PlayerStatusEvent());
|
||||
return true;
|
||||
});
|
||||
|
||||
findPreference(UserPreferences.PREF_HIDDEN_DRAWER_ITEMS)
|
||||
.setOnPreferenceClickListener(preference -> {
|
||||
showDrawerPreferencesDialog();
|
||||
|
|
|
@ -9,7 +9,7 @@ import androidx.appcompat.widget.SearchView;
|
|||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.util.ThemeUtils;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
import de.danoeh.antennapod.fragment.SearchFragment;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
|
|
@ -2,6 +2,7 @@ package de.danoeh.antennapod.preferences;
|
|||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.view.KeyEvent;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import de.danoeh.antennapod.BuildConfig;
|
||||
|
@ -92,5 +93,16 @@ public class PreferenceUpgrader {
|
|||
if (oldVersion < 1080100) {
|
||||
prefs.edit().putString(UserPreferences.PREF_VIDEO_BEHAVIOR, "pip").apply();
|
||||
}
|
||||
if (oldVersion < 2010300) {
|
||||
// Migrate hardware button preferences
|
||||
if (prefs.getBoolean("prefHardwareForwardButtonSkips", false)) {
|
||||
prefs.edit().putString(UserPreferences.PREF_HARDWARE_FORWARD_BUTTON,
|
||||
String.valueOf(KeyEvent.KEYCODE_MEDIA_NEXT)).apply();
|
||||
}
|
||||
if (prefs.getBoolean("prefHardwarePreviousButtonRestarts", false)) {
|
||||
prefs.edit().putString(UserPreferences.PREF_HARDWARE_PREVIOUS_BUTTON,
|
||||
String.valueOf(KeyEvent.KEYCODE_MEDIA_PREVIOUS)).apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
package de.danoeh.antennapod.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.util.AttributeSet;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
|
||||
public class ChapterSeekBar extends androidx.appcompat.widget.AppCompatSeekBar {
|
||||
|
||||
private float top;
|
||||
private float width;
|
||||
private float bottom;
|
||||
private float density;
|
||||
private float progressPrimary;
|
||||
private float progressSecondary;
|
||||
private float[] dividerPos;
|
||||
private final Paint paintBackground = new Paint();
|
||||
private final Paint paintProgressPrimary = new Paint();
|
||||
private final Paint paintProgressSecondary = new Paint();
|
||||
|
||||
public ChapterSeekBar(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public ChapterSeekBar(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public ChapterSeekBar(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
setBackground(null); // Removes the thumb shadow
|
||||
dividerPos = null;
|
||||
density = context.getResources().getDisplayMetrics().density;
|
||||
paintBackground.setColor(ThemeUtils.getColorFromAttr(getContext(),
|
||||
de.danoeh.antennapod.core.R.attr.currently_playing_background));
|
||||
paintBackground.setAlpha(128);
|
||||
paintProgressPrimary.setColor(ThemeUtils.getColorFromAttr(getContext(),
|
||||
de.danoeh.antennapod.core.R.attr.colorPrimary));
|
||||
paintProgressSecondary.setColor(ThemeUtils.getColorFromAttr(getContext(),
|
||||
de.danoeh.antennapod.core.R.attr.seek_background));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the relative positions of the chapter dividers.
|
||||
* @param dividerPos of the chapter dividers relative to the duration of the media.
|
||||
*/
|
||||
public void setDividerPos(final float[] dividerPos) {
|
||||
if (dividerPos != null) {
|
||||
this.dividerPos = new float[dividerPos.length + 2];
|
||||
this.dividerPos[0] = 0;
|
||||
System.arraycopy(dividerPos, 0, this.dividerPos, 1, dividerPos.length);
|
||||
this.dividerPos[this.dividerPos.length - 1] = 1;
|
||||
} else {
|
||||
this.dividerPos = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void onDraw(Canvas canvas) {
|
||||
top = getTop() + density * 7.5f;
|
||||
bottom = getBottom() - density * 7.5f;
|
||||
width = (float) (getRight() - getPaddingRight() - getLeft() - getPaddingLeft());
|
||||
progressSecondary = getSecondaryProgress() / (float) getMax() * width;
|
||||
progressPrimary = getProgress() / (float) getMax() * width;
|
||||
|
||||
if (dividerPos == null) {
|
||||
drawProgress(canvas);
|
||||
} else {
|
||||
drawProgressChapters(canvas);
|
||||
}
|
||||
drawThumb(canvas);
|
||||
}
|
||||
|
||||
private void drawProgress(Canvas canvas) {
|
||||
final int saveCount = canvas.save();
|
||||
canvas.translate(getPaddingLeft(), getPaddingTop());
|
||||
canvas.drawRect(0, top, width, bottom, paintBackground);
|
||||
canvas.drawRect(0, top, progressSecondary, bottom, paintProgressSecondary);
|
||||
canvas.drawRect(0, top, progressPrimary, bottom, paintProgressPrimary);
|
||||
canvas.restoreToCount(saveCount);
|
||||
}
|
||||
|
||||
private void drawProgressChapters(Canvas canvas) {
|
||||
final int saveCount = canvas.save();
|
||||
int currChapter = 1;
|
||||
float chapterMargin = density * 0.6f;
|
||||
float topExpanded = getTop() + density * 7;
|
||||
float bottomExpanded = getBottom() - density * 7;
|
||||
|
||||
canvas.translate(getPaddingLeft(), getPaddingTop());
|
||||
|
||||
for (int i = 1; i < dividerPos.length; i++) {
|
||||
float right = dividerPos[i] * width - chapterMargin;
|
||||
float left = dividerPos[i - 1] * width + chapterMargin;
|
||||
float rightCurr = dividerPos[currChapter] * width - chapterMargin;
|
||||
float leftCurr = dividerPos[currChapter - 1] * width + chapterMargin;
|
||||
|
||||
canvas.drawRect(left, top, right, bottom, paintBackground);
|
||||
|
||||
if (right < progressPrimary) {
|
||||
currChapter = i + 1;
|
||||
canvas.drawRect(left, top, right, bottom, paintProgressPrimary);
|
||||
} else if (isPressed()) {
|
||||
canvas.drawRect(leftCurr, topExpanded, rightCurr, bottomExpanded, paintBackground);
|
||||
canvas.drawRect(leftCurr, topExpanded, progressPrimary, bottomExpanded, paintProgressPrimary);
|
||||
} else {
|
||||
if (progressSecondary > leftCurr) {
|
||||
canvas.drawRect(leftCurr, top, progressSecondary, bottom, paintProgressSecondary);
|
||||
}
|
||||
canvas.drawRect(leftCurr, top, progressPrimary, bottom, paintProgressPrimary);
|
||||
}
|
||||
}
|
||||
canvas.restoreToCount(saveCount);
|
||||
}
|
||||
|
||||
private void drawThumb(Canvas canvas) {
|
||||
final int saveCount = canvas.save();
|
||||
canvas.translate(getPaddingLeft() - getThumbOffset(), getPaddingTop());
|
||||
getThumb().draw(canvas);
|
||||
canvas.restoreToCount(saveCount);
|
||||
}
|
||||
}
|
|
@ -6,9 +6,9 @@ import android.content.res.Configuration;
|
|||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import androidx.appcompat.view.ContextThemeWrapper;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
|
||||
import de.danoeh.antennapod.R;
|
||||
import io.reactivex.annotations.Nullable;
|
||||
|
||||
|
@ -39,7 +39,7 @@ public class EpisodeItemListRecyclerView extends RecyclerView {
|
|||
layoutManager.setRecycleChildrenOnDetach(true);
|
||||
setLayoutManager(layoutManager);
|
||||
setHasFixedSize(true);
|
||||
addItemDecoration(new HorizontalDividerItemDecoration.Builder(getContext()).build());
|
||||
addItemDecoration(new DividerItemDecoration(getContext(), layoutManager.getOrientation()));
|
||||
setClipToPadding(false);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ public class DownloadItemViewHolder extends RecyclerView.ViewHolder {
|
|||
public final TextView type;
|
||||
public final TextView date;
|
||||
public final TextView reason;
|
||||
public final TextView tapForDetails;
|
||||
|
||||
public DownloadItemViewHolder(Context context, ViewGroup parent) {
|
||||
super(LayoutInflater.from(context).inflate(R.layout.downloadlog_item, parent, false));
|
||||
|
@ -27,6 +28,7 @@ public class DownloadItemViewHolder extends RecyclerView.ViewHolder {
|
|||
type = itemView.findViewById(R.id.txtvType);
|
||||
icon = itemView.findViewById(R.id.txtvIcon);
|
||||
reason = itemView.findViewById(R.id.txtvReason);
|
||||
tapForDetails = itemView.findViewById(R.id.txtvTapForDetails);
|
||||
secondaryActionButton = itemView.findViewById(R.id.secondaryActionButton);
|
||||
secondaryActionIcon = itemView.findViewById(R.id.secondaryActionIcon);
|
||||
title = itemView.findViewById(R.id.txtvTitle);
|
||||
|
|
|
@ -13,9 +13,7 @@ import android.widget.TextView;
|
|||
|
||||
import androidx.cardview.widget.CardView;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.joanzapata.iconify.Iconify;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.adapter.CoverLoader;
|
||||
|
@ -25,13 +23,15 @@ import de.danoeh.antennapod.core.feed.FeedItem;
|
|||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.feed.MediaType;
|
||||
import de.danoeh.antennapod.core.feed.util.ImageResourceUtils;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadRequest;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||
import de.danoeh.antennapod.core.util.Converter;
|
||||
import de.danoeh.antennapod.core.util.DateUtils;
|
||||
import de.danoeh.antennapod.core.util.NetworkUtils;
|
||||
import de.danoeh.antennapod.core.util.ThemeUtils;
|
||||
import de.danoeh.antennapod.view.CircularProgressBar;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
import de.danoeh.antennapod.ui.common.CircularProgressBar;
|
||||
|
||||
/**
|
||||
* Holds the view which shows FeedItems.
|
||||
|
@ -121,8 +121,8 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder {
|
|||
|
||||
if (coverHolder.getVisibility() == View.VISIBLE) {
|
||||
new CoverLoader(activity)
|
||||
.withUri(ImageResourceUtils.getImageLocation(item))
|
||||
.withFallbackUri(item.getFeed().getImageLocation())
|
||||
.withUri(ImageResourceUtils.getEpisodeListImageLocation(item))
|
||||
.withFallbackUri(item.getFeed().getImageUrl())
|
||||
.withPlaceholderView(placeholder)
|
||||
.withCoverView(cover)
|
||||
.load();
|
||||
|
@ -132,9 +132,6 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder {
|
|||
private void bind(FeedMedia media) {
|
||||
isVideo.setVisibility(media.getMediaType() == MediaType.VIDEO ? View.VISIBLE : View.GONE);
|
||||
duration.setVisibility(media.getDuration() > 0 ? View.VISIBLE : View.GONE);
|
||||
duration.setText(Converter.getDurationStringLong(media.getDuration()));
|
||||
duration.setContentDescription(activity.getString(R.string.chapter_duration,
|
||||
Converter.getDurationStringLocalized(activity, media.getDuration())));
|
||||
|
||||
if (media.isCurrentlyPlaying()) {
|
||||
itemView.setBackgroundColor(ThemeUtils.getColorFromAttr(activity, R.attr.currently_playing_background));
|
||||
|
@ -152,6 +149,9 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder {
|
|||
secondaryActionProgress.setPercentage(0, item); // Animate X% -> 0%
|
||||
}
|
||||
|
||||
duration.setText(Converter.getDurationStringLong(media.getDuration()));
|
||||
duration.setContentDescription(activity.getString(R.string.chapter_duration,
|
||||
Converter.getDurationStringLocalized(activity, media.getDuration())));
|
||||
if (item.getState() == FeedItem.State.PLAYING || item.getState() == FeedItem.State.IN_PROGRESS) {
|
||||
int progress = (int) (100.0 * media.getPosition() / media.getDuration());
|
||||
progressBar.setProgress(progress);
|
||||
|
@ -160,6 +160,11 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder {
|
|||
Converter.getDurationStringLocalized(activity, media.getPosition())));
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
position.setVisibility(View.VISIBLE);
|
||||
if (UserPreferences.shouldShowRemainingTime()) {
|
||||
duration.setText("-" + Converter.getDurationStringLong(media.getDuration() - media.getPosition()));
|
||||
duration.setContentDescription(activity.getString(R.string.chapter_duration,
|
||||
Converter.getDurationStringLocalized(activity, (media.getDuration() - media.getPosition()))));
|
||||
}
|
||||
} else {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
position.setVisibility(View.GONE);
|
||||
|
@ -186,6 +191,22 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder {
|
|||
}
|
||||
}
|
||||
|
||||
private void updateDuration(PlaybackPositionEvent event) {
|
||||
int currentPosition = event.getPosition();
|
||||
int timeDuration = event.getDuration();
|
||||
int remainingTime = event.getDuration() - event.getPosition();
|
||||
Log.d(TAG, "currentPosition " + Converter.getDurationStringLong(currentPosition));
|
||||
if (currentPosition == PlaybackService.INVALID_TIME || timeDuration == PlaybackService.INVALID_TIME) {
|
||||
Log.w(TAG, "Could not react to position observer update because of invalid time");
|
||||
return;
|
||||
}
|
||||
if (UserPreferences.shouldShowRemainingTime()) {
|
||||
duration.setText("-" + Converter.getDurationStringLong(remainingTime));
|
||||
} else {
|
||||
duration.setText(Converter.getDurationStringLong(timeDuration));
|
||||
}
|
||||
}
|
||||
|
||||
public FeedItem getFeedItem() {
|
||||
return item;
|
||||
}
|
||||
|
@ -197,7 +218,7 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder {
|
|||
public void notifyPlaybackPositionUpdated(PlaybackPositionEvent event) {
|
||||
progressBar.setProgress((int) (100.0 * event.getPosition() / event.getDuration()));
|
||||
position.setText(Converter.getDurationStringLong(event.getPosition()));
|
||||
duration.setText(Converter.getDurationStringLong(event.getDuration()));
|
||||
updateDuration(event);
|
||||
duration.setVisibility(View.VISIBLE); // Even if the duration was previously unknown, it is now known
|
||||
}
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@ AntennaPod je správce a přehrávač podcastů, co vám umožňuje okamžitý p
|
|||
Stahujte, streamujte nebo si vytvořte frontu epizod a užijte si poslech tak, jak ho máte rádi s nastavitelnou rychlostí přehrávání, podporou kapitol a s časovačem vypnutí.
|
||||
Ušetři si námahu, baterku i mobilní data s pomocí robustní automatické kontroly nad stahováním epizod (urči časy, intervaly a WIFI sítě) a mazáním epizod (na základě oblíbenosti a nastavení zpoždění).
|
||||
|
||||
Vytvořeno nadšenci do podcastů, AntennaPod je otevřený software (OSS), zdarma a bez reklam.
|
||||
Vytvořeno nadšenci do podcastů, AntennaPod je software s otevřeným zdrojovým kódem, zdarma a bez reklam.
|
||||
|
||||
<b>Importujte, zorganizujte a přehrávejte</b>
|
||||
<b>Importujte, organizujte a přehrávejte</b>
|
||||
• Ovládejte přehrávání odkudkoli: z widgetu na domovské obrazovce, z oznámení nebo pomocí tlačítek na sluchátkách včetně Bluetooth
|
||||
• Přidejte a importujte podcasty přes iTunes anebo gPodder.net, OPML soubory a RSS anebo Atom odkazy
|
||||
• Užijte si poslech s nastavitelnou rychlostí přehrávání, podporou kapitol, zapamatování poslední pozice přehrávání a pokročilým časovačem vypnutí (restart zatřesením, snížení hlasitosti)
|
||||
|
@ -21,7 +21,7 @@ Vytvořeno nadšenci do podcastů, AntennaPod je otevřený software (OSS), zdar
|
|||
• Přizpůsobte si aplikaci svému prostředí pomocí světlého nebo tmavého motivu
|
||||
• Zálohujte své sbírky pomocí služby gPodder.net nebo exportem OPML souborů
|
||||
|
||||
<b>Přidejte se do komunity AntennaPodu!</b>
|
||||
<b>Přidejte se ke komunitě AntennaPod!</b>
|
||||
AntennaPod je aktivně vyvíjen dobrovolníky. Můžete přispět také svým kódem nebo komentáři!
|
||||
|
||||
Naše přátelská členská základna vám ráda zodpoví jakékoli dotazy. Zveme vás k diskuzi o AntennaPodu nebo i podcastech obecně.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
AntennaPod ist ein Podcast-Manager und -Player, der dir unmittelbar Zugriff auf Millionen von freien und bezahlten Podcasts ermöglicht. Angefangen von unabhängigen Podcastern zu großen Rundfunkanstalten oder Hörfunksendern wie BBC, NPR und CNN. Abonniere, importiere und exportiere deine Feeds mühelos mit Hilfe des iTunes-Verzeichnisses, OPML-Dateien oder einfachen RSS-URLs.
|
||||
AntennaPod ist ein Podcast-Manager und -Player, mit dem Du direkten Zugriff auf Millionen von freien und kostenpflichtigen Podcasts hast. Angefangen von unabhängigen Podcastern zu großen Rundfunkanstalten oder Hörfunksendern wie BBC, NPR und CNN. Abonniere, importiere und exportiere deine Feeds mühelos mit Hilfe des iTunes-Podcast-Verzeichnisses, OPML-Dateien oder RSS-URLs.
|
||||
Downloade, streame oder sortiere Episoden in der Abspielliste und genieße sie mit einstellbarer Abspielgeschwindigkeit, Unterstützung von Kapiteln und Schlummerfunktion.
|
||||
Reduziere Aufwand, Stromverbrauch und Datenverbrauch durch leistungsfähige Kontrolle der Downloads (bestimmte Uhrzeiten, Intervalle, WiFi-Netze) und des Löschens (basierend auf deinen Favoriten und weiteren Einstellungen).
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 9.5 KiB |
|
@ -24,8 +24,8 @@ Dodawaj i importuj kanały z iTunes i gPodder.net, plików OPML oraz z adresów
|
|||
<b>Dołącz do społeczności AntennaPod</b>
|
||||
AntennaPod jest ciągle rozwijane przez ochotników. Ty też możesz pomóc, kodem lub komentarzem!
|
||||
|
||||
Chcesz zgłosić błąd lub brakuje Ci jakiejś funkcji, a może programujesz? Odwiedź nasz GitHub:
|
||||
https://www.github.com/AntennaPod/AntennaPod
|
||||
Życzliwi użytkownicy forum chętnie odpowiedzą na twoje pytania. Zapraszamy do rozmów o funkcjach programu i generalnie o podcastingu.
|
||||
https://forum.antennapod.org/
|
||||
|
||||
Chcesz pomóc tłumaczyć AntennaPod - możesz to zrobić na Transifex:
|
||||
https://www.transifex.com/antennapod/antennapod
|
|
@ -0,0 +1,31 @@
|
|||
AntennaPod je správca a prehrávač podcastov, ktorý vám sprostredkuje okamžitý prístup k miliónom bezplatným a spoplatneným podcastom - od nezávislých podcastérov až k vydavateľstvám ako BBC, NPR a CNN. Pridávajte, importujte a exportujte ich zdroje bez problémov pomocou databázy podcastov iTunes, OPML súborov alebo odkazov na RSS.
|
||||
Stiahnite, streamujte alebo plánujte epizódy a užívajte si ich ako sa vám páči s nastaviteľnou rýchlosťou prehrávania, podporou kapitol a časovačom vypnutia.
|
||||
Ušetrite si námahu, baterku a mobilné dáta pomocou výkonnej automatickej kontroly sťahovania epizód (určite čas, intervaly a WiFi siete) a mazania epizód (založené na obľúbených epizódach a nastavení oneskorenia).
|
||||
|
||||
AntennaPod je spravovaný podcastovými entuziastami a je slobodný v každom slova zmysle: otvorený zdrojový kód, zadarmo, bez reklamy.
|
||||
|
||||
<b>Importovať, spravovať a prehrať</b>
|
||||
• Ovládajte prehrávanie odkiaľkoľvek: domovská obrazovka, systémové upozornenia a handsfree a ovládanie cez bluetooth
|
||||
• Pridať a importovať zdroje pomocou priečinkov iTunes a gPodder.net, OPML súborov a odkazov RSS alebo Atom
|
||||
• Užívajte si počúvanie s nastaviteľnou rýchlosťou prehrávania, podporou kapitol, zapamätanou pozíciou prehrávania a pokročilým nastavením vypnutia (zatrasením resetuj, stíš hlasitosť)
|
||||
Počúvajte heslom chránené zdroje a epizódy
|
||||
|
||||
<b>Sledovať, zdielať a oceniť</b>
|
||||
• Sledujte najlepšie z najlepších pomocou označenia ako obľúbené
|
||||
• Nájdite epizódu v histórií prehrávaní alebo v nadpisoch a popisoch
|
||||
• Zdielajte epizódy a zdroje pomocou pokročilých nastavení sociálnych sietí a e-mailu, služieb gPodder.net a cez export do OPML súboru
|
||||
|
||||
<b>Spravovať systém</b>
|
||||
• Spravujte automatické sťahovanie: vyberte zdroje, vylúčte mobilné siete, vyberte konkrétne WiFi siete, vyžadujte nabíjanie telefónu a nastavte časy a intervaly
|
||||
• Spravujte úložisko nastavením počtu uložených epizód, inteligentným mazaním a zvolením umiestnenia súborov
|
||||
• Prispôsobte si vzhľad na svetlý alebo tmavý
|
||||
• Zálohujte si odbery pomocou gPodder.net a OPML exportu
|
||||
|
||||
<b>Pridať sa do komunity AntennaPod!</b>
|
||||
AntennaPod aktívne vyvíjajú dobrovoľníci. Tiež môžete prispieť kódom alebo komentárom.
|
||||
|
||||
Na našom fóre môžete v priateľskej atmosfére diskutovať a nájsť odpovede na vaše otázky o nových funkciách alebo všeobecne o podcastingu.
|
||||
https://forum.antennapod.org/
|
||||
|
||||
Transifex je miesto, kde môžete pomôcť s prekladom:
|
||||
https://www.transifex.com/antennapod/antennapod
|
|
@ -0,0 +1 @@
|
|||
Ľahko použíteľný, flexibilný a open source správca a prehrávač podcastov
|
|
@ -0,0 +1 @@
|
|||
AntennaPod
|
|
@ -1,7 +1,12 @@
|
|||
- A long-standing wish of many: playing local files! In the 'Add podcast' screen simply tap 'Add local folder' and select a location on your phone! (@ByteHamster, @igoralmeida & @damoasda)
|
||||
- Pick a country for the 'Discover' screen (@tonytamsf)
|
||||
- Keyboard shortcuts (@asdoi)
|
||||
- Search the PodcastIndex.org database (@edwinhere)
|
||||
- Pull to refresh (@asdoi)
|
||||
- Playback speed & filter dialogs (@ByteHamster & @bws9000)
|
||||
- Smooth sleep timer volume (@olivoto)
|
||||
NEW
|
||||
- Optional notifications for new episodes (@connectety)
|
||||
- Use PodcastIndex for main search (@tonytamsf)
|
||||
- Sleep timer extend buttons (@max-wittig)
|
||||
- Optional rewind, forward & skip buttons on widget (@tonytamsf)
|
||||
- 'When not favorited' as Episode Cleanup (@spacecowboy)
|
||||
|
||||
IMPROVED
|
||||
- More actions for hardware buttons (@timakro)
|
||||
- Android Auto & chapter support (@tonytamsf, @ByteHamster)
|
||||
- Fixed stuck notification (@a1291762)
|
||||
- Player screen usability for visually impaired (@ByteHamster)
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
android:id="@+id/widget_config_preview"
|
||||
layout="@layout/player_widget"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:layout_height="96dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="16dp" />
|
||||
</FrameLayout>
|
||||
|
@ -68,13 +68,38 @@
|
|||
android:max="100"
|
||||
android:progress="100" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/ckRewind"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Rewind" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/ckFastForward"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Forward" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/ckSkip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Skip" />
|
||||
</LinearLayout>
|
||||
<Button
|
||||
android:id="@+id/butConfirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/widget_create_button" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -51,6 +51,34 @@
|
|||
app:tint="?android:attr/windowBackground"
|
||||
android:importantForAccessibility="no"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/cardViewSeek"
|
||||
android:alpha="0"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@+id/pager"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginBottom="12dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardBackgroundColor="?attr/seek_background"
|
||||
app:cardElevation="0dp"
|
||||
tools:alpha="1">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvSeek"
|
||||
android:gravity="center"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingRight="24dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="24sp"
|
||||
tools:text="1:06:29" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/playtime_layout"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -60,7 +88,7 @@
|
|||
android:layoutDirection="ltr"
|
||||
android:orientation="vertical">
|
||||
|
||||
<SeekBar
|
||||
<de.danoeh.antennapod.view.ChapterSeekBar
|
||||
android:id="@+id/sbPosition"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -128,7 +156,7 @@
|
|||
android:scaleType="fitCenter"
|
||||
tools:srcCompat="@drawable/ic_av_play_white_24dp"/>
|
||||
|
||||
<de.danoeh.antennapod.view.CircularProgressBar
|
||||
<de.danoeh.antennapod.ui.common.CircularProgressBar
|
||||
android:layout_width="@dimen/audioplayer_playercontrols_length_big"
|
||||
android:layout_height="@dimen/audioplayer_playercontrols_length_big"
|
||||
android:layout_marginLeft="16dp"
|
||||
|
@ -136,7 +164,8 @@
|
|||
android:layout_marginRight="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_centerVertical="true"/>
|
||||
android:layout_centerVertical="true"
|
||||
app:foregroundColor="?attr/action_icon_color"/>
|
||||
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyle"
|
||||
|
@ -177,7 +206,7 @@
|
|||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:clickable="false"/>
|
||||
|
||||
<de.danoeh.antennapod.view.PlaybackSpeedIndicatorView
|
||||
<de.danoeh.antennapod.ui.common.PlaybackSpeedIndicatorView
|
||||
android:id="@+id/butPlaybackSpeed"
|
||||
android:layout_width="@dimen/audioplayer_playercontrols_length"
|
||||
android:layout_height="@dimen/audioplayer_playercontrols_length"
|
||||
|
@ -186,7 +215,8 @@
|
|||
android:layout_centerVertical="true"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/playback_speed"
|
||||
tools:srcCompat="@drawable/ic_playback_speed_white"/>
|
||||
tools:srcCompat="@drawable/ic_playback_speed_white"
|
||||
app:foregroundColor="?attr/action_icon_color"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvPlaybackSpeed"
|
||||
|
|
|
@ -1,30 +1,59 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etxtUsername"
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_margin="16dp"
|
||||
android:hint="@string/username_label"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:cursorVisible="true"/>
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etxtPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_margin="16dp"
|
||||
android:inputType="textPassword"
|
||||
android:hint="@string/password_label"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:cursorVisible="true"/>
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/usernameEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/username_label"
|
||||
android:lines="1"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/passwordEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/password_label"
|
||||
android:inputType="textPassword"
|
||||
android:lines="1"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.joanzapata.iconify.widget.IconTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/showPasswordButton"
|
||||
android:text="{fa-eye}"
|
||||
android:padding="8dp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:alpha="0.6"
|
||||
android:textSize="20sp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginStart="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
|
@ -10,7 +10,7 @@
|
|||
android:padding="8dp"
|
||||
android:gravity="center">
|
||||
|
||||
<de.danoeh.antennapod.view.SquareImageView
|
||||
<de.danoeh.antennapod.ui.common.SquareImageView
|
||||
android:id="@+id/imgvCover"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="200dp"
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/authentication_notification_title"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
android:textColor="?android:attr/textColorPrimary"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/authentication_notification_msg"
|
||||
android:textColor="?android:attr/textColorSecondary"/>
|
||||
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/etxtUsername"
|
||||
android:hint="@string/username_label"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:cursorVisible="true"/>
|
||||
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/etxtPassword"
|
||||
android:hint="@string/password_label"
|
||||
android:inputType="textPassword"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:cursorVisible="true"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="48dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="end">
|
||||
|
||||
<Button
|
||||
android:id="@+id/butCancel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/cancel_label"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/butConfirm"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/confirm_label"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"/>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -83,6 +83,14 @@
|
|||
android:textColor="?android:attr/textColorSecondary"
|
||||
tools:text="@string/design_time_downloaded_log_failure_reason"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvTapForDetails"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:text="@string/download_error_tap_for_details"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include layout="@layout/secondary_action"/>
|
||||
|
|
|
@ -94,17 +94,6 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvInformation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="2dp"
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:visibility="gone"
|
||||
android:gravity="center"
|
||||
tools:visibility="visible"
|
||||
tools:text="(i) Information"/>
|
||||
|
||||
<com.joanzapata.iconify.widget.IconTextView
|
||||
android:id="@+id/txtvFailure"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -117,4 +106,15 @@
|
|||
android:text="@string/refresh_failed_msg"
|
||||
tools:visibility="visible"
|
||||
tools:text="(!) Last refresh failed"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvInformation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="2dp"
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:visibility="gone"
|
||||
android:gravity="center"
|
||||
tools:visibility="visible"
|
||||
tools:text="(i) Information"/>
|
||||
</LinearLayout>
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
android:layout_marginRight="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="·"
|
||||
android:importantForAccessibility="no"
|
||||
tools:background="@android:color/holo_blue_light"/>
|
||||
|
||||
<TextView
|
||||
|
@ -163,6 +164,7 @@
|
|||
android:layout_marginRight="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="·"
|
||||
android:importantForAccessibility="no"
|
||||
tools:background="@android:color/holo_blue_light"/>
|
||||
|
||||
<TextView
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue