Merge branch 'develop' into feat/simple-adjust-volume-per-feed
This commit is contained in:
commit
5bf7216064
@ -1,39 +1,103 @@
|
||||
version: 2
|
||||
|
||||
jobs:
|
||||
build:
|
||||
test-debug:
|
||||
docker:
|
||||
- image: circleci/android:api-28
|
||||
|
||||
working_directory: ~/AntennaPod
|
||||
|
||||
environment:
|
||||
GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx1536m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError"'
|
||||
_JAVA_OPTIONS: "-Xms256m -Xmx1280m"
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-android-{{ checksum "build.gradle" }}
|
||||
# fallback to using the latest cache if no exact match is found
|
||||
- v1-android-
|
||||
|
||||
- run:
|
||||
# To build release, we need to create a temporary keystore that can be used to sign the app
|
||||
command: |
|
||||
keytool -noprompt -genkey -v -keystore "app/keystore" -alias alias -storepass password -keypass password -keyalg RSA -validity 10 -dname "CN=antennapod.org, OU=dummy, O=dummy, L=dummy, S=dummy, C=US"
|
||||
./gradlew assembleRelease :core:testPlayReleaseUnitTest -PdisablePreDex
|
||||
no_output_timeout: 1800
|
||||
|
||||
- store_artifacts:
|
||||
path: app/build/outputs/apk
|
||||
destination: apks
|
||||
|
||||
name: Build debug
|
||||
command: ./gradlew assembleDebug -PdisablePreDex
|
||||
- run:
|
||||
name: Execute debug unit tests
|
||||
command: ./gradlew :core:testPlayDebugUnitTest -PdisablePreDex
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.android
|
||||
- ~/.gradle
|
||||
- ~/android
|
||||
key: v1-android-{{ checksum "build.gradle" }}
|
||||
|
||||
test-release:
|
||||
docker:
|
||||
- image: circleci/android:api-28
|
||||
working_directory: ~/AntennaPod
|
||||
environment:
|
||||
GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx1536m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError"'
|
||||
_JAVA_OPTIONS: "-Xms256m -Xmx1280m"
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-android-{{ checksum "build.gradle" }}
|
||||
- v1-android-
|
||||
- run:
|
||||
name: Create temporary release keystore
|
||||
command: keytool -noprompt -genkey -v -keystore "app/keystore" -alias alias -storepass password -keypass password -keyalg RSA -validity 10 -dname "CN=antennapod.org, OU=dummy, O=dummy, L=dummy, S=dummy, C=US"
|
||||
- run:
|
||||
name: Build release
|
||||
command: ./gradlew assembleRelease -PdisablePreDex
|
||||
- run:
|
||||
name: Execute release unit tests
|
||||
command: ./gradlew :core:testPlayReleaseUnitTest -PdisablePreDex
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.android
|
||||
- ~/.gradle
|
||||
- ~/android
|
||||
key: v1-android-{{ checksum "build.gradle" }}
|
||||
|
||||
build-androidtest:
|
||||
docker:
|
||||
- image: circleci/android:api-28
|
||||
working_directory: ~/AntennaPod
|
||||
environment:
|
||||
GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx1536m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError"'
|
||||
_JAVA_OPTIONS: "-Xms256m -Xmx1280m"
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-android-{{ checksum "build.gradle" }}
|
||||
- v1-android-
|
||||
- run:
|
||||
name: Build integration tests
|
||||
command: ./gradlew :app:assemblePlayDebugAndroidTest -PdisablePreDex
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.android
|
||||
- ~/.gradle
|
||||
- ~/android
|
||||
key: v1-android-{{ checksum "build.gradle" }}
|
||||
|
||||
checkstyle:
|
||||
docker:
|
||||
- image: circleci/android:api-28
|
||||
working_directory: ~/AntennaPod
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Checkstyle
|
||||
command: ./gradlew checkstyle
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
unit-tests:
|
||||
jobs:
|
||||
- test-debug
|
||||
- test-release
|
||||
- build-androidtest
|
||||
|
||||
static-analysis:
|
||||
jobs:
|
||||
- checkstyle
|
||||
|
2
.tx/.gitignore
vendored
Normal file
2
.tx/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
antennapod.description
|
||||
antennapod.shortdescription
|
@ -1,43 +0,0 @@
|
||||
O AntennaPod é um gestor de podcasts que lhe permite aceder a milhões de podcasts, gratuitos ou pagos, a partir de diversas fontes tais como as estações BBC, NPR e CNN. A adição, importação e exportação de fontes é muito fácil através da base de dados iTunes ou gPodder, ficheiros OPML ou fontes RSS. Poupe tempo, economize bateria e dados móveis através dos mecanismos de controlo de descargas de episódios (possibilidade de especificar intervalos ou horas para as descargas e redes Wifi) e de eliminação de episódios (de acordo com as suas preferências).<br>
|
||||
Mas ainda mais importante: pode descarregar, emitir ou colocar episódios na lista de reprodução ao seu gosto, pode utilizar velocidades variáveis de reprodução, tem suporte a capítulos e um temporizador. Pode também mostrar o seu apreço aos criadores dos episódios através do serviço Flattr.
|
||||
|
||||
Criado por entusiastas de podcasts, o AntennaPod é livre em todos os sentidos da palavra: open source, gratuito e sem publicidade.
|
||||
|
||||
<b>Funcionalidades:</b><br>
|
||||
Importação, organização e reprodução<br>
|
||||
• Adicione e importe fontes existentes nos diretórios iTunes e gPodder.net, ficheiros OPML e ligações ATOM e RSS<br>
|
||||
• Gestão de podcasts através do widget, barra de notificações e controlos de auriculares ou auscultadores<br>
|
||||
• Velocidade variável de reprodução, suporte a capítulos (MP3, VorbisComment e Podlove), memorização da posição de reprodução e um temporizador avançado (agite para repor, baixar e aumentar o volume)<br>
|
||||
• Acesso a fontes e episódios protegidos por palavra-passe<br>
|
||||
• Possibilidade de subscrever fontes paginadas (www.podlove.org/paged-feeds)
|
||||
|
||||
Monitorização, partilha e suporte<br>
|
||||
• Monitorize os seus podcasts preferidos marcando-os como favoritos<br>
|
||||
• Localize um episódio através do histórico de reprodução ou através de uma pesquisa (títulos e notas)<br>
|
||||
• Partilhe episódios e fontes nas redes sociais, por e-mail, no diretório gPodder.net ou através de ficheiros OPML<br>
|
||||
• Ajude os criadores de conteúdos através do serviço Flattr
|
||||
|
||||
Controlo do sistema<br>
|
||||
• Controle todas as descargas automáticas: escolha as fontes, exclua redes móveis, especifique as redes Wi-Fi, indique se o telefone deve estar a ser carregado e defina as horas ou intervalos das descargas<br>
|
||||
• Faça a gestão do armazenamento através da cache de episódios, da eliminação inteligente (de acordo com os seus favoritos e estado de reprodução) e selecionado a localização de armazenamento<br>
|
||||
• Utilize o AntennaPod no seu idioma (EN, DE, CS, NL, NB, JA, PT, ES, SV, CA, UK, FR, KO, TR, ZH)<br>
|
||||
• Adapte-se ao seu ambiente através dos temas claro ou escuro<br>
|
||||
• Salvaguarde as suas subscrições com a integração gPodder.net ou através da exportação OPML
|
||||
|
||||
<b>Integre a comunidade do AntennaPod!</b><br>
|
||||
O AntennaPod é desenvolvido por voluntários. Você também pode contribuir na programação ou reportando os erros encontrados!
|
||||
|
||||
O GitHub é o local certo para os pedidos de funcionalidades, relatórios de erros e contributos:<br>
|
||||
https://www.github.com/AntennaPod/AntennaPod
|
||||
|
||||
O nosso grupo Google é o local certo para partilhar ideias e agradecer aos nossos voluntários:<br>
|
||||
https://groups.google.com/forum/#!forum/antennapod
|
||||
|
||||
Tem alguma questão ou comentário a fazer?
|
||||
https://twitter.com/@AntennaPod
|
||||
|
||||
O Transifex é o local no qual pode ajudar a traduzir a aplicação:<br>
|
||||
https://www.transifex.com/antennapod/antennapod
|
||||
|
||||
Junte-se ao nosso programa Beta para obter as funcionalidades mais recentes:<br>
|
||||
https://www.github.com/AntennaPod/AntennaPod/wiki/Help-test-AntennaPod
|
@ -1 +0,0 @@
|
||||
Gestor e reprodutor de podcasts simples, flexível e open souce
|
@ -24,7 +24,7 @@ trans.gl = core/src/main/res/values-gl-rES/strings.xml
|
||||
trans.he_IL = core/src/main/res/values-iw-rIL/strings.xml
|
||||
trans.hi_IN = core/src/main/res/values-hi-rIN/strings.xml
|
||||
trans.hu = core/src/main/res/values-hu/strings.xml
|
||||
trans.id = core/src/main/res/values-id/strings.xml
|
||||
trans.id = core/src/main/res/values-in/strings.xml
|
||||
trans.it_IT = core/src/main/res/values-it/strings.xml
|
||||
trans.is = core/src/main/res/values-is-rIS/strings.xml
|
||||
trans.ja = core/src/main/res/values-ja/strings.xml
|
||||
@ -70,10 +70,13 @@ trans.el = app/src/main/play/listings/el-GR/full-description.txt
|
||||
trans.es = app/src/main/play/listings/es-ES/full-description.txt
|
||||
trans.et = app/src/main/play/listings/et/full-description.txt
|
||||
trans.fa = app/src/main/play/listings/fa/full-description.txt
|
||||
trans.fi = app/src/main/play/listings/fi-FI/full-description.txt
|
||||
trans.fr = app/src/main/play/listings/fr-FR/full-description.txt
|
||||
trans.gl = app/src/main/play/listings/gl-ES/full-description.txt
|
||||
trans.hi_IN = app/src/main/play/listings/hi-IN/full-description.txt
|
||||
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
|
||||
@ -107,10 +110,13 @@ trans.el = app/src/main/play/listings/el-GR/short-description.txt
|
||||
trans.es = app/src/main/play/listings/es-ES/short-description.txt
|
||||
trans.et = app/src/main/play/listings/et/short-description.txt
|
||||
trans.fa = app/src/main/play/listings/fa/short-description.txt
|
||||
trans.fi = app/src/main/play/listings/fi-FI/short-description.txt
|
||||
trans.fr = app/src/main/play/listings/fr-FR/short-description.txt
|
||||
trans.gl = app/src/main/play/listings/gl-ES/short-description.txt
|
||||
trans.hi_IN = app/src/main/play/listings/hi-IN/short-description.txt
|
||||
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
|
||||
|
11
CHANGELOG.md
11
CHANGELOG.md
@ -1,6 +1,17 @@
|
||||
Change Log
|
||||
==========
|
||||
|
||||
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
|
||||
|
34
CONTRIBUTORS
34
CONTRIBUTORS
@ -6,6 +6,7 @@ Alexander Terczka
|
||||
Alexei Bendebury
|
||||
Ali
|
||||
alifeflow
|
||||
alimemonzx
|
||||
amhokies
|
||||
Anders Bo Rasmussen
|
||||
Anderson Mesquita
|
||||
@ -13,8 +14,10 @@ Andrew Gaul
|
||||
Andrey Krutov
|
||||
Anthony Lieuallen
|
||||
axq
|
||||
bhlee
|
||||
Borjan Tchakaloff
|
||||
brad
|
||||
Brad Pitcher
|
||||
Burt Wiley Snyder
|
||||
ByteHamster
|
||||
Cameron Banga
|
||||
@ -23,6 +26,7 @@ Christopher Szucko
|
||||
Cj Malone
|
||||
Colin Willson
|
||||
Cédric Cabessa
|
||||
damoasda
|
||||
Danial Klimkin
|
||||
Daniel Oeh
|
||||
David Carver
|
||||
@ -48,9 +52,11 @@ Humberto Fraga
|
||||
InsidE
|
||||
James Falcon
|
||||
Jan Niehusmann
|
||||
Jan-Peter von Hunnius
|
||||
Jatin Kumar
|
||||
Jens Klingenberg
|
||||
Jens Müller
|
||||
Joe Stein
|
||||
Johan Liesén
|
||||
Kaligule
|
||||
Katrin Leinweber
|
||||
@ -93,6 +99,7 @@ qkolj
|
||||
Raghul
|
||||
Raghul Jagannathan
|
||||
recalculated
|
||||
rezanejati
|
||||
Ross Harrison
|
||||
Sam Lee
|
||||
Sam Whited
|
||||
@ -109,6 +116,7 @@ Simon Schubert
|
||||
Soso Tughushi
|
||||
Spencer Visick
|
||||
Stefan Mitrik
|
||||
Stephen Kitt
|
||||
Terence Eden
|
||||
Tim Butram
|
||||
Tobias Preuss
|
||||
@ -121,6 +129,7 @@ Udi Finkelstein
|
||||
VarunBarad
|
||||
volhol
|
||||
Volker Hollich
|
||||
Wagubi Brian
|
||||
WangYun
|
||||
William Seemann
|
||||
ydinath
|
||||
@ -136,47 +145,48 @@ Bulgarian: solusitor
|
||||
Catalan: dvd1985, exort12, javiercoll, lambdani, marcmetallextrem, xc70
|
||||
Catalan (Spain): 00c0c0, javiercoll
|
||||
Chinese: dillonbecker, RainSlide, xukeek, yangyang
|
||||
Chinese (China): bebeauties38, domingos86, dudeG, ErlichLiu, Felix2yu, gaohongyuan, Guaidaodl, Huck0, iconteral, jhxie, kavdx, kyleehee, linxiangyu, molisiye, owen8877, RainSlide, stellaxuyi, tupunco, wi24rd, wongsyrone, xukeek, yangyang, yiqiok, YogaGuru
|
||||
Chinese (China): bebeauties38, domingos86, dudeG, ErlichLiu, Felix2yu, gaohongyuan, Guaidaodl, Huck0, iconteral, JayYoung, jhxie, kavdx, kyleehee, linxiangyu, molisiye, owen8877, RainSlide, Sak94664, spice2wolf, stellaxuyi, tupunco, wi24rd, wongsyrone, xukeek, yangyang, yiqiok, YogaGuru
|
||||
Chinese (Taiwan): gugod, nigelinux, pggdt, ymhuang0808
|
||||
Czech (Czech Republic): elich, Hanzmeister, mcepl, petnek, svetlemodry
|
||||
Danish: CasperHN, jhertel
|
||||
Dutch: e2jk, glotzbach, rwv, Vistaus
|
||||
English: mfietz, sterylmreep
|
||||
Estonian: Eraser
|
||||
Finnish: danieloeh
|
||||
French: cactux, ChaoticMind, clombion, e2jk, lacouture, Matth78, mfietz, Poussinou, PRIMOKORN, repat, Sioul, sterylmreep, TacoTheDank, Tilwa, vcariven, whenrow
|
||||
Finnish: danieloeh, elguitar, Sahtor
|
||||
French: cactux, ChaoticMind, clombion, e2jk, lacouture, LouFex, Matth78, mfietz, Poussinou, PRIMOKORN, repat, Sioul, sterylmreep, TacoTheDank, Tilwa, vcariven, whenrow
|
||||
Galician: antiparvos, pikamoku, Raichely
|
||||
German: 112358, altegedanken, barilla, benedikt.g, bitsunited, Buggi, ceving, ChaoticMind, Chaquotay, dab0015, dadosch, DerSilly, die_otto, DJaeger, elkangaroo, enz, fidel, finsterwalder, Foso, GNi33, hightower5, HolgerJeromin, kalei, lohmann, LostInWeb, mfietz, nilso, repat, SAPlayer, schafia, Schroedingberg, sevenmaster, sucaml, Teaspoon, theonlytruth, weltenwort, Wyrrrd, ypid
|
||||
German: 112358, altegedanken, barilla, benedikt.g, bitsunited, Buggi, ceving, ChaoticMind, Chaquotay, dab0015, dadosch, DerSilly, die_otto, DJaeger, elkangaroo, enz, fidel, finsterwalder, Foso, GNi33, hightower5, HolgerJeromin, kalei, lohmann, LostInWeb, mfietz, moasda, nilso, repat, SAPlayer, schafia, Schroedingberg, sevenmaster, sucaml, Teaspoon, theonlytruth, weltenwort, Wyrrrd, ypid
|
||||
Greek: antonist, danieloeh, hua2016s, MSavoritias, pavlosv
|
||||
Hebrew (Israel): amir.dafnyman, E1i9, mongoose4004, pinkasey, rellieberman, Yaron, הלוי11
|
||||
Hindi (India): ankitiitb1069, Isaasu, nmabhinandan, purple.coder, siddhusengar
|
||||
Hungarian: glatz.balazs, lna91, marthynw, naren93, tszauer, ttyborg42
|
||||
Hungarian: glatz.balazs, hurrikan, lna91, marthynw, naren93, tszauer, ttyborg42
|
||||
Icelandic: marthjod
|
||||
Indonesian: jff, luke137, rezafaiza, silvanael16
|
||||
Indonesian: jff, levirs565, luke137, rezafaiza, silvanael16
|
||||
Italian: aalex70, allin, apanontin, Bonnee, giuseppep, Guybrush88, marco_pag, neonsoftware, sevenmaster, theloca95
|
||||
Italian (Italy): aalex70, allin, apanontin, Bonnee, buongiorgio, giuseppep, Guybrush88, m.chinni, neonsoftware, nixxo, sevenmaster, theloca95
|
||||
Italian (Italy): aalex70, allin, apanontin, Bonnee, buongiorgio, giuseppep, Guybrush88, m.chinni, neonsoftware, niccord, nixxo, sevenmaster, theloca95
|
||||
Japanese: Naofumi, RACER1, sh3llc4t, TranslatorG
|
||||
Kannada (India): thejeshgn
|
||||
Korean: changwoo, seungrye, skcha
|
||||
Korean: changwoo, libliboom, seungrye, skcha
|
||||
Korean (South Korea): changwoo, seungrye
|
||||
Lithuanian: naglis
|
||||
Macedonian: krisfremen
|
||||
Malayalam: rashivkp
|
||||
Malayalam: rashivkp, rubenroy
|
||||
Norwegian: timbast
|
||||
Norwegian Bokmål: corkie, danieloeh, heraldo
|
||||
Norwegian Bokmål (Norway): corkie, heraldo, kongk, timbast
|
||||
Persian: ahangarha, F7D
|
||||
Persian: ahangarha, F7D, sinamoghaddas
|
||||
Polish: Iwangelion, maniexx, mateossh, mfloryan
|
||||
Polish (Poland): d6210809, hiro2020, Iwangelion, lomapur, mandlus, maniexx, Mephistofeles, shark103, tyle
|
||||
Portuguese: andersonvom, domingos86, emansije, smarquespt
|
||||
Portuguese (Brazil): alexupits, alysonborges, andersonvom, arua, caioau, carlo_valente, castrors, deandreamatias, edman, Firmino, jackmiras, Junin, lipefire, lluccia, lucasmotacr, mbaltar, rogervezaro, RubeensVinicius, SamWilliam, silvanael16
|
||||
Romanian (Romania): corneliu.e, fuzzmz, ralienpp
|
||||
Russian (Russia): astra1, btimofeev, Duke_Raven, GaynullinDima, MegMasters98, mercutiy, null, overmind88, s.chebotar, shams4real, skvheadless, un_logic, whereisthetea, zhenya97
|
||||
Russian (Russia): astra1, btimofeev, Duke_Raven, gammja, GaynullinDima, MegMasters98, mercutiy, null, overmind88, PtilopsisLeucotis, s.chebotar, shams4real, skvheadless, un_logic, Vladryyu, whereisthetea, zhenya97
|
||||
Slovenian (Slovenia): panter23
|
||||
Spanish: AleksSyntek, andersonvom, coperfix, deandreamatias, domingos86, dvd1985, Fitoschido, frandavid100, hard_ware, javiercoll, Juanmuto, lambdani, LatinSuD, leogrignafini, palopezv, TacoTheDank, tres.14159, wakutiteo
|
||||
Spanish (Spain): andersonvom, dvd1985, e2jk, frandavid100, hard_ware, palopezv, Raichely, TacoTheDank
|
||||
Swahili: kmtra
|
||||
Swahili (Kenya): BonfaceKilz
|
||||
Swedish (Sweden): albin.brantin, Bio, bpnilsson, ChaoticMind, jony08, nilso, SharpMelon, TwoD
|
||||
Swedish (Sweden): albin.brantin, Bio, bpnilsson, ChaoticMind, jony08, nilso, SharpMelon, TiloWiklund, TwoD
|
||||
Telugu: Isaasu, veeven
|
||||
Turkish: basarancaner, brsata, Erdy, golcuk, overbite, Slsdem
|
||||
Ukrainian (Ukraine): older, paul_sm, sergiyr, zhenya97
|
||||
|
@ -1,10 +1,9 @@
|
||||
plugins {
|
||||
id('com.github.triplet.play') version '2.2.0'
|
||||
id('com.android.application')
|
||||
id('com.getkeepsafe.dexcount')
|
||||
id('com.github.triplet.play') version '2.3.0' apply false
|
||||
}
|
||||
|
||||
apply plugin: "com.android.application"
|
||||
apply plugin: 'com.getkeepsafe.dexcount'
|
||||
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
android {
|
||||
@ -19,8 +18,8 @@ android {
|
||||
// "1.2.3-SNAPSHOT" -> 1020300
|
||||
// "1.2.3-RC4" -> 1020304
|
||||
// "1.2.3" -> 1020395
|
||||
versionCode 1070296
|
||||
versionName "1.7.2b"
|
||||
versionCode 1070396
|
||||
versionName "1.7.3b"
|
||||
testApplicationId "de.test.antennapod"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
generatedDensities = []
|
||||
@ -173,9 +172,9 @@ dependencies {
|
||||
implementation "com.github.AntennaPod:AntennaPod-AudioPlayer:$audioPlayerVersion"
|
||||
|
||||
implementation 'com.github.mfietz:fyydlin:v0.4.2'
|
||||
implementation 'com.github.ByteHamster:SearchPreference:v1.2.6'
|
||||
implementation "org.awaitility:awaitility:$awaitilityVersion"
|
||||
implementation 'com.github.ByteHamster:SearchPreference:v1.3.0'
|
||||
|
||||
androidTestImplementation "org.awaitility:awaitility:$awaitilityVersion"
|
||||
androidTestImplementation 'com.nanohttpd:nanohttpd-webserver:2.1.1'
|
||||
androidTestImplementation "com.jayway.android.robotium:robotium-solo:$robotiumSoloVersion"
|
||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||
@ -185,18 +184,12 @@ dependencies {
|
||||
androidTestImplementation 'com.android.support.test:rules:1.0.2'
|
||||
}
|
||||
|
||||
play {
|
||||
track = 'alpha'
|
||||
|
||||
if (project.hasProperty("antennaPodServiceAccountEmail")) {
|
||||
if (project.hasProperty("antennaPodServiceAccountEmail")) {
|
||||
apply plugin: 'com.github.triplet.play'
|
||||
play {
|
||||
track = 'alpha'
|
||||
serviceAccountEmail = antennaPodServiceAccountEmail
|
||||
} else {
|
||||
serviceAccountEmail = '522080222319-compute@developer.gserviceaccount.com'
|
||||
}
|
||||
if (project.hasProperty("antennaPodPk12File")) {
|
||||
serviceAccountCredentials = file(antennaPodPk12File)
|
||||
} else {
|
||||
serviceAccountCredentials = file('../serviceaccount-c3d7d0f61387.p12')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package de.test.antennapod;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.espresso.PerformException;
|
||||
@ -12,6 +13,10 @@ import android.support.test.espresso.util.HumanReadables;
|
||||
import android.support.test.espresso.util.TreeIterables;
|
||||
import android.view.View;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.storage.PodDBAdapter;
|
||||
import de.danoeh.antennapod.dialog.RatingDialog;
|
||||
import de.danoeh.antennapod.fragment.QueueFragment;
|
||||
import org.hamcrest.Matcher;
|
||||
|
||||
import java.io.File;
|
||||
@ -74,7 +79,7 @@ public class EspressoTestUtils {
|
||||
/**
|
||||
* Clear all app databases
|
||||
*/
|
||||
public static void clearAppData() {
|
||||
public static void clearPreferences() {
|
||||
File root = InstrumentationRegistry.getTargetContext().getFilesDir().getParentFile();
|
||||
String[] sharedPreferencesFileNames = new File(root, "shared_prefs").list();
|
||||
for (String fileName : sharedPreferencesFileNames) {
|
||||
@ -84,6 +89,31 @@ public class EspressoTestUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void makeNotFirstRun() {
|
||||
InstrumentationRegistry.getTargetContext().getSharedPreferences(MainActivity.PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putBoolean(MainActivity.PREF_IS_FIRST_LAUNCH, false)
|
||||
.commit();
|
||||
|
||||
RatingDialog.init(InstrumentationRegistry.getTargetContext());
|
||||
RatingDialog.saveRated();
|
||||
}
|
||||
|
||||
public static void setLastNavFragment(String tag) {
|
||||
InstrumentationRegistry.getTargetContext().getSharedPreferences(MainActivity.PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putString(MainActivity.PREF_LAST_FRAGMENT_TAG, tag)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void clearDatabase() {
|
||||
PodDBAdapter.init(InstrumentationRegistry.getTargetContext());
|
||||
PodDBAdapter.deleteDatabase();
|
||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||
adapter.open();
|
||||
adapter.close();
|
||||
}
|
||||
|
||||
public static void clickPreference(@StringRes int title) {
|
||||
onView(withId(R.id.list)).perform(
|
||||
RecyclerViewActions.actionOnItem(hasDescendant(withText(title)),
|
||||
|
@ -64,15 +64,11 @@ public class PlaybackServiceMediaPlayerTest {
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
assertionError = null;
|
||||
EspressoTestUtils.clearAppData();
|
||||
final Context context = InstrumentationRegistry.getTargetContext();
|
||||
EspressoTestUtils.clearPreferences();
|
||||
EspressoTestUtils.makeNotFirstRun();
|
||||
EspressoTestUtils.clearDatabase();
|
||||
|
||||
// create new database
|
||||
PodDBAdapter.init(context);
|
||||
PodDBAdapter.deleteDatabase();
|
||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||
adapter.open();
|
||||
adapter.close();
|
||||
final Context context = InstrumentationRegistry.getTargetContext();
|
||||
|
||||
httpServer = new HTTPBin();
|
||||
httpServer.start();
|
||||
|
@ -5,8 +5,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.espresso.intent.Intents;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.support.test.espresso.intent.rule.IntentsTestRule;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import com.robotium.solo.Solo;
|
||||
import com.robotium.solo.Timeout;
|
||||
@ -29,7 +28,6 @@ import static android.support.test.InstrumentationRegistry.getInstrumentation;
|
||||
import static android.support.test.espresso.Espresso.onView;
|
||||
import static android.support.test.espresso.action.ViewActions.click;
|
||||
import static android.support.test.espresso.contrib.ActivityResultMatchers.hasResultCode;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static de.test.antennapod.EspressoTestUtils.clickPreference;
|
||||
import static de.test.antennapod.EspressoTestUtils.openNavDrawer;
|
||||
@ -45,47 +43,28 @@ public class MainActivityTest {
|
||||
|
||||
private Solo solo;
|
||||
private UITestUtils uiTestUtils;
|
||||
private SharedPreferences prefs;
|
||||
|
||||
@Rule
|
||||
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false);
|
||||
public IntentsTestRule<MainActivity> mActivityRule = new IntentsTestRule<>(MainActivity.class, false, false);
|
||||
|
||||
@Before
|
||||
public void setUp() throws IOException {
|
||||
// override first launch preference
|
||||
// do this BEFORE calling getActivity()!
|
||||
EspressoTestUtils.clearAppData();
|
||||
prefs = InstrumentationRegistry.getContext()
|
||||
.getSharedPreferences(MainActivity.PREF_NAME, Context.MODE_PRIVATE);
|
||||
prefs.edit().putBoolean(MainActivity.PREF_IS_FIRST_LAUNCH, false).commit();
|
||||
EspressoTestUtils.clearPreferences();
|
||||
EspressoTestUtils.makeNotFirstRun();
|
||||
EspressoTestUtils.clearDatabase();
|
||||
|
||||
mActivityRule.launchActivity(new Intent());
|
||||
|
||||
Intents.init();
|
||||
Context context = mActivityRule.getActivity();
|
||||
uiTestUtils = new UITestUtils(context);
|
||||
uiTestUtils = new UITestUtils(InstrumentationRegistry.getTargetContext());
|
||||
uiTestUtils.setup();
|
||||
|
||||
// create new database
|
||||
PodDBAdapter.init(context);
|
||||
PodDBAdapter.deleteDatabase();
|
||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||
adapter.open();
|
||||
adapter.close();
|
||||
|
||||
RatingDialog.init(context);
|
||||
RatingDialog.saveRated();
|
||||
|
||||
solo = new Solo(getInstrumentation(), mActivityRule.getActivity());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
uiTestUtils.tearDown();
|
||||
solo.finishOpenedActivities();
|
||||
Intents.release();
|
||||
PodDBAdapter.deleteDatabase();
|
||||
prefs.edit().clear().commit();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -93,13 +72,10 @@ public class MainActivityTest {
|
||||
uiTestUtils.addHostedFeedData();
|
||||
final Feed feed = uiTestUtils.hostedFeeds.get(0);
|
||||
openNavDrawer();
|
||||
solo.clickOnText(solo.getString(R.string.add_feed_label));
|
||||
onView(withText(R.string.add_feed_label)).perform(click());
|
||||
solo.enterText(1, feed.getDownload_url());
|
||||
solo.clickOnButton(solo.getString(R.string.confirm_label));
|
||||
solo.waitForActivity(OnlineFeedViewActivity.class);
|
||||
solo.waitForView(R.id.butSubscribe);
|
||||
assertEquals(solo.getString(R.string.subscribe_label), solo.getButton(0).getText().toString());
|
||||
solo.clickOnButton(0);
|
||||
onView(withText(R.string.confirm_label)).perform(click());
|
||||
onView(withText(R.string.subscribe_label)).perform(click());
|
||||
assertTrue(solo.waitForText(solo.getString(R.string.open_podcast), 0, Timeout.getLargeTimeout(), false));
|
||||
}
|
||||
|
||||
@ -119,7 +95,6 @@ public class MainActivityTest {
|
||||
onView(withText(R.string.confirm_label)).perform(click());
|
||||
|
||||
solo.goBackToActivity(MainActivity.class.getSimpleName());
|
||||
solo.goBack(); // Close nav drawer
|
||||
solo.goBack();
|
||||
assertEquals(solo.getString(R.string.subscriptions_label), getActionbarTitle());
|
||||
}
|
||||
@ -132,7 +107,6 @@ public class MainActivityTest {
|
||||
clickPreference(R.string.pref_back_button_behavior_title);
|
||||
onView(withText(R.string.back_button_open_drawer)).perform(click());
|
||||
solo.goBackToActivity(MainActivity.class.getSimpleName());
|
||||
solo.goBack(); // Close nav drawer
|
||||
solo.goBack();
|
||||
assertTrue(((MainActivity)solo.getCurrentActivity()).isDrawerOpen());
|
||||
}
|
||||
@ -145,7 +119,6 @@ public class MainActivityTest {
|
||||
clickPreference(R.string.pref_back_button_behavior_title);
|
||||
onView(withText(R.string.back_button_double_tap)).perform(click());
|
||||
solo.goBackToActivity(MainActivity.class.getSimpleName());
|
||||
solo.goBack(); // Close nav drawer
|
||||
solo.goBack();
|
||||
solo.goBack();
|
||||
assertThat(mActivityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
@ -159,7 +132,6 @@ public class MainActivityTest {
|
||||
clickPreference(R.string.pref_back_button_behavior_title);
|
||||
onView(withText(R.string.back_button_show_prompt)).perform(click());
|
||||
solo.goBackToActivity(MainActivity.class.getSimpleName());
|
||||
solo.goBack(); // Close nav drawer
|
||||
solo.goBack();
|
||||
onView(withText(R.string.yes)).perform(click());
|
||||
Thread.sleep(100);
|
||||
@ -174,7 +146,6 @@ public class MainActivityTest {
|
||||
clickPreference(R.string.pref_back_button_behavior_title);
|
||||
onView(withText(R.string.back_button_default)).perform(click());
|
||||
solo.goBackToActivity(MainActivity.class.getSimpleName());
|
||||
solo.goBack(); // Close nav drawer
|
||||
solo.goBack();
|
||||
assertThat(mActivityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
}
|
||||
|
@ -4,13 +4,11 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.espresso.ViewInteraction;
|
||||
import android.support.test.espresso.contrib.DrawerActions;
|
||||
import android.support.test.espresso.intent.Intents;
|
||||
import android.support.test.filters.FlakyTest;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.support.test.espresso.intent.rule.IntentsTestRule;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.widget.ListView;
|
||||
import com.robotium.solo.Solo;
|
||||
import android.view.View;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.activity.PreferenceActivity;
|
||||
@ -23,6 +21,7 @@ import de.danoeh.antennapod.fragment.EpisodesFragment;
|
||||
import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
|
||||
import de.danoeh.antennapod.fragment.QueueFragment;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@ -34,20 +33,20 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static android.support.test.InstrumentationRegistry.getInstrumentation;
|
||||
import static android.support.test.espresso.Espresso.onView;
|
||||
import static android.support.test.espresso.action.ViewActions.click;
|
||||
import static android.support.test.espresso.action.ViewActions.longClick;
|
||||
import static android.support.test.espresso.action.ViewActions.scrollTo;
|
||||
import static android.support.test.espresso.intent.Intents.intended;
|
||||
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static de.test.antennapod.NthMatcher.first;
|
||||
import static de.test.antennapod.EspressoTestUtils.waitForView;
|
||||
import static de.test.antennapod.NthMatcher.first;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* User interface tests for MainActivity drawer
|
||||
@ -55,49 +54,24 @@ import static org.junit.Assert.fail;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class NavigationDrawerTest {
|
||||
|
||||
private Solo solo;
|
||||
private UITestUtils uiTestUtils;
|
||||
private SharedPreferences prefs;
|
||||
|
||||
@Rule
|
||||
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false);
|
||||
public IntentsTestRule<MainActivity> mActivityRule = new IntentsTestRule<>(MainActivity.class, false, false);
|
||||
|
||||
@Before
|
||||
public void setUp() throws IOException {
|
||||
// override first launch preference
|
||||
// do this BEFORE calling getActivity()!
|
||||
EspressoTestUtils.clearAppData();
|
||||
prefs = InstrumentationRegistry.getContext()
|
||||
.getSharedPreferences(MainActivity.PREF_NAME, Context.MODE_PRIVATE);
|
||||
prefs.edit().putBoolean(MainActivity.PREF_IS_FIRST_LAUNCH, false).commit();
|
||||
|
||||
mActivityRule.launchActivity(new Intent());
|
||||
|
||||
Intents.init();
|
||||
Context context = mActivityRule.getActivity();
|
||||
uiTestUtils = new UITestUtils(context);
|
||||
uiTestUtils = new UITestUtils(InstrumentationRegistry.getTargetContext());
|
||||
uiTestUtils.setup();
|
||||
|
||||
// create new database
|
||||
PodDBAdapter.init(context);
|
||||
PodDBAdapter.deleteDatabase();
|
||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||
adapter.open();
|
||||
adapter.close();
|
||||
|
||||
RatingDialog.init(context);
|
||||
RatingDialog.saveRated();
|
||||
|
||||
solo = new Solo(getInstrumentation(), mActivityRule.getActivity());
|
||||
EspressoTestUtils.clearPreferences();
|
||||
EspressoTestUtils.makeNotFirstRun();
|
||||
EspressoTestUtils.clearDatabase();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
uiTestUtils.tearDown();
|
||||
solo.finishOpenedActivities();
|
||||
Intents.release();
|
||||
PodDBAdapter.deleteDatabase();
|
||||
prefs.edit().clear().commit();
|
||||
}
|
||||
|
||||
private void openNavDrawer() {
|
||||
@ -105,69 +79,66 @@ public class NavigationDrawerTest {
|
||||
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open());
|
||||
}
|
||||
|
||||
private ViewInteraction onDrawerItem(Matcher<View> viewMatcher) {
|
||||
return onView(allOf(viewMatcher, withId(R.id.txtvTitle)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@FlakyTest
|
||||
public void testClickNavDrawer() throws Exception {
|
||||
uiTestUtils.addLocalFeedData(false);
|
||||
|
||||
setHiddenDrawerItems(new ArrayList<>());
|
||||
UserPreferences.setHiddenDrawerItems(new ArrayList<>());
|
||||
mActivityRule.launchActivity(new Intent());
|
||||
MainActivity activity = mActivityRule.getActivity();
|
||||
|
||||
// queue
|
||||
openNavDrawer();
|
||||
solo.clickOnText(solo.getString(R.string.queue_label));
|
||||
solo.waitForView(R.id.recyclerView);
|
||||
assertEquals(solo.getString(R.string.queue_label), getActionbarTitle());
|
||||
onDrawerItem(withText(R.string.queue_label)).perform(click());
|
||||
onView(isRoot()).perform(waitForView(withId(R.id.recyclerView), 1000));
|
||||
assertEquals(activity.getString(R.string.queue_label), activity.getSupportActionBar().getTitle());
|
||||
|
||||
// episodes
|
||||
openNavDrawer();
|
||||
solo.clickOnText(solo.getString(R.string.episodes_label));
|
||||
solo.waitForView(android.R.id.list);
|
||||
assertEquals(solo.getString(R.string.episodes_label), getActionbarTitle());
|
||||
onDrawerItem(withText(R.string.episodes_label)).perform(click());
|
||||
onView(isRoot()).perform(waitForView(withId(android.R.id.list), 1000));
|
||||
assertEquals(activity.getString(R.string.episodes_label), activity.getSupportActionBar().getTitle());
|
||||
|
||||
// Subscriptions
|
||||
openNavDrawer();
|
||||
solo.clickOnText(solo.getString(R.string.subscriptions_label));
|
||||
solo.waitForView(R.id.subscriptions_grid);
|
||||
assertEquals(solo.getString(R.string.subscriptions_label), getActionbarTitle());
|
||||
onDrawerItem(withText(R.string.subscriptions_label)).perform(click());
|
||||
onView(isRoot()).perform(waitForView(withId(R.id.subscriptions_grid), 1000));
|
||||
assertEquals(activity.getString(R.string.subscriptions_label), activity.getSupportActionBar().getTitle());
|
||||
|
||||
// downloads
|
||||
openNavDrawer();
|
||||
solo.clickOnText(solo.getString(R.string.downloads_label));
|
||||
solo.waitForView(android.R.id.list);
|
||||
assertEquals(solo.getString(R.string.downloads_label), getActionbarTitle());
|
||||
onDrawerItem(withText(R.string.downloads_label)).perform(click());
|
||||
onView(isRoot()).perform(waitForView(withId(android.R.id.list), 1000));
|
||||
assertEquals(activity.getString(R.string.downloads_label), activity.getSupportActionBar().getTitle());
|
||||
|
||||
// playback history
|
||||
openNavDrawer();
|
||||
solo.clickOnText(solo.getString(R.string.playback_history_label));
|
||||
solo.waitForView(android.R.id.list);
|
||||
assertEquals(solo.getString(R.string.playback_history_label), getActionbarTitle());
|
||||
onDrawerItem(withText(R.string.playback_history_label)).perform(click());
|
||||
onView(isRoot()).perform(waitForView(withId(android.R.id.list), 1000));
|
||||
assertEquals(activity.getString(R.string.playback_history_label), activity.getSupportActionBar().getTitle());
|
||||
|
||||
// add podcast
|
||||
openNavDrawer();
|
||||
solo.clickOnText(solo.getString(R.string.add_feed_label));
|
||||
solo.waitForView(R.id.txtvFeedurl);
|
||||
assertEquals(solo.getString(R.string.add_feed_label), getActionbarTitle());
|
||||
onDrawerItem(withText(R.string.add_feed_label)).perform(click());
|
||||
onView(isRoot()).perform(waitForView(withId(R.id.txtvFeedurl), 1000));
|
||||
assertEquals(activity.getString(R.string.add_feed_label), activity.getSupportActionBar().getTitle());
|
||||
|
||||
// podcasts
|
||||
ListView list = (ListView) solo.getView(R.id.nav_list);
|
||||
for (int i = 0; i < uiTestUtils.hostedFeeds.size(); i++) {
|
||||
Feed f = uiTestUtils.hostedFeeds.get(i);
|
||||
openNavDrawer();
|
||||
solo.scrollListToLine(list, i);
|
||||
solo.clickOnText(f.getTitle());
|
||||
solo.waitForView(android.R.id.list);
|
||||
assertEquals("", getActionbarTitle());
|
||||
onDrawerItem(withText(f.getTitle())).perform(scrollTo(), click());
|
||||
onView(isRoot()).perform(waitForView(withId(android.R.id.list), 1000));
|
||||
assertEquals("", activity.getSupportActionBar().getTitle());
|
||||
}
|
||||
}
|
||||
|
||||
private String getActionbarTitle() {
|
||||
return ((MainActivity) solo.getCurrentActivity()).getSupportActionBar().getTitle().toString();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@FlakyTest
|
||||
public void testGoToPreferences() {
|
||||
mActivityRule.launchActivity(new Intent());
|
||||
openNavDrawer();
|
||||
onView(withText(R.string.settings_label)).perform(click());
|
||||
intended(hasComponent(PreferenceActivity.class.getName()));
|
||||
@ -175,9 +146,10 @@ public class NavigationDrawerTest {
|
||||
|
||||
@Test
|
||||
public void testDrawerPreferencesHideSomeElements() {
|
||||
setHiddenDrawerItems(new ArrayList<>());
|
||||
UserPreferences.setHiddenDrawerItems(new ArrayList<>());
|
||||
mActivityRule.launchActivity(new Intent());
|
||||
openNavDrawer();
|
||||
onView(first(withText(R.string.queue_label))).perform(longClick());
|
||||
onDrawerItem(withText(R.string.queue_label)).perform(longClick());
|
||||
onView(withText(R.string.episodes_label)).perform(click());
|
||||
onView(withText(R.string.playback_history_label)).perform(click());
|
||||
onView(withText(R.string.confirm_label)).perform(click());
|
||||
@ -191,7 +163,8 @@ public class NavigationDrawerTest {
|
||||
@Test
|
||||
public void testDrawerPreferencesUnhideSomeElements() {
|
||||
List<String> hidden = Arrays.asList(PlaybackHistoryFragment.TAG, DownloadsFragment.TAG);
|
||||
setHiddenDrawerItems(hidden);
|
||||
UserPreferences.setHiddenDrawerItems(hidden);
|
||||
mActivityRule.launchActivity(new Intent());
|
||||
openNavDrawer();
|
||||
onView(first(withText(R.string.queue_label))).perform(longClick());
|
||||
|
||||
@ -208,7 +181,8 @@ public class NavigationDrawerTest {
|
||||
|
||||
@Test
|
||||
public void testDrawerPreferencesHideAllElements() {
|
||||
setHiddenDrawerItems(new ArrayList<>());
|
||||
UserPreferences.setHiddenDrawerItems(new ArrayList<>());
|
||||
mActivityRule.launchActivity(new Intent());
|
||||
String[] titles = mActivityRule.getActivity().getResources().getStringArray(R.array.nav_drawer_titles);
|
||||
|
||||
openNavDrawer();
|
||||
@ -227,7 +201,8 @@ public class NavigationDrawerTest {
|
||||
|
||||
@Test
|
||||
public void testDrawerPreferencesHideCurrentElement() {
|
||||
setHiddenDrawerItems(new ArrayList<>());
|
||||
UserPreferences.setHiddenDrawerItems(new ArrayList<>());
|
||||
mActivityRule.launchActivity(new Intent());
|
||||
openNavDrawer();
|
||||
onView(withText(R.string.downloads_label)).perform(click());
|
||||
openNavDrawer();
|
||||
@ -240,14 +215,4 @@ public class NavigationDrawerTest {
|
||||
assertEquals(1, hidden.size());
|
||||
assertTrue(hidden.contains(DownloadsFragment.TAG));
|
||||
}
|
||||
|
||||
private void setHiddenDrawerItems(List<String> items) {
|
||||
UserPreferences.setHiddenDrawerItems(items);
|
||||
try {
|
||||
mActivityRule.runOnUiThread(() -> mActivityRule.getActivity().updateNavDrawer());
|
||||
} catch (Throwable throwable) {
|
||||
throwable.printStackTrace();
|
||||
fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,12 +52,11 @@ public class PlaybackSonicTest {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
EspressoTestUtils.clearAppData();
|
||||
EspressoTestUtils.clearPreferences();
|
||||
EspressoTestUtils.makeNotFirstRun();
|
||||
EspressoTestUtils.clearDatabase();
|
||||
context = InstrumentationRegistry.getTargetContext();
|
||||
|
||||
PodDBAdapter.init(context);
|
||||
PodDBAdapter.deleteDatabase();
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs.edit()
|
||||
.clear()
|
||||
@ -71,11 +70,6 @@ public class PlaybackSonicTest {
|
||||
|
||||
uiTestUtils = new UITestUtils(context);
|
||||
uiTestUtils.setup();
|
||||
|
||||
// create database
|
||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||
adapter.open();
|
||||
adapter.close();
|
||||
}
|
||||
|
||||
@After
|
||||
@ -122,7 +116,7 @@ public class PlaybackSonicTest {
|
||||
solo.clickOnText(solo.getString(R.string.all_episodes_short_label));
|
||||
getInstrumentation().waitForIdleSync();
|
||||
|
||||
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(10);
|
||||
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10);
|
||||
assertTrue(solo.waitForView(solo.getView(R.id.butSecondaryAction)));
|
||||
|
||||
solo.clickOnView(solo.getView(R.id.butSecondaryAction));
|
||||
@ -241,7 +235,7 @@ public class PlaybackSonicTest {
|
||||
setContinuousPlaybackPreference(followQueue);
|
||||
uiTestUtils.addLocalFeedData(true);
|
||||
DBWriter.clearQueue().get();
|
||||
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(10);
|
||||
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10);
|
||||
|
||||
startLocalPlayback();
|
||||
long mediaId = episodes.get(0).getMedia().getId();
|
||||
|
@ -114,7 +114,7 @@ public class PlaybackTest {
|
||||
solo.waitForText(solo.getString(R.string.all_episodes_short_label));
|
||||
solo.clickOnText(solo.getString(R.string.all_episodes_short_label));
|
||||
|
||||
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(10);
|
||||
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10);
|
||||
assertTrue(solo.waitForView(solo.getView(R.id.butSecondaryAction)));
|
||||
|
||||
solo.clickOnView(solo.getView(R.id.butSecondaryAction));
|
||||
@ -231,7 +231,7 @@ public class PlaybackTest {
|
||||
setContinuousPlaybackPreference(followQueue);
|
||||
uiTestUtils.addLocalFeedData(true);
|
||||
DBWriter.clearQueue().get();
|
||||
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(10);
|
||||
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10);
|
||||
|
||||
startLocalPlayback();
|
||||
long mediaId = episodes.get(0).getMedia().getId();
|
||||
|
@ -49,7 +49,7 @@ public class PreferencesTest {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
EspressoTestUtils.clearAppData();
|
||||
EspressoTestUtils.clearPreferences();
|
||||
mActivityRule.launchActivity(new Intent());
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mActivityRule.getActivity());
|
||||
prefs.edit().putBoolean(UserPreferences.PREF_ENABLE_AUTODL, true).commit();
|
||||
|
@ -0,0 +1,58 @@
|
||||
package de.test.antennapod.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.support.test.espresso.Espresso;
|
||||
import android.support.test.espresso.intent.rule.IntentsTestRule;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.fragment.QueueFragment;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static android.support.test.espresso.Espresso.onView;
|
||||
import static android.support.test.espresso.action.ViewActions.click;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||
|
||||
/**
|
||||
* User interface tests for queue fragment
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class QueueFragmentTest {
|
||||
|
||||
@Rule
|
||||
public IntentsTestRule<MainActivity> mActivityRule = new IntentsTestRule<>(MainActivity.class, false, false);
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
EspressoTestUtils.clearPreferences();
|
||||
EspressoTestUtils.makeNotFirstRun();
|
||||
EspressoTestUtils.clearDatabase();
|
||||
EspressoTestUtils.setLastNavFragment(QueueFragment.TAG);
|
||||
mActivityRule.launchActivity(new Intent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLockEmptyQueue() {
|
||||
onView(withContentDescription(R.string.lock_queue)).perform(click());
|
||||
onView(withContentDescription(R.string.unlock_queue)).perform(click());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSortEmptyQueue() {
|
||||
Espresso.openContextualActionModeOverflowMenu();
|
||||
onView(withText(R.string.sort)).perform(click());
|
||||
onView(withText(R.string.random)).perform(click());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeepEmptyQueueSorted() {
|
||||
Espresso.openContextualActionModeOverflowMenu();
|
||||
onView(withText(R.string.sort)).perform(click());
|
||||
onView(withText(R.string.keep_sorted)).perform(click());
|
||||
}
|
||||
}
|
@ -59,7 +59,7 @@ class UITestUtils {
|
||||
|
||||
public void setup() throws IOException {
|
||||
destDir = new File(context.getFilesDir(), "test/UITestUtils");
|
||||
destDir.mkdir();
|
||||
destDir.mkdirs();
|
||||
hostedFeedDir = new File(destDir, "hostedFeeds");
|
||||
hostedFeedDir.mkdir();
|
||||
hostedMediaDir = new File(destDir, "hostedMediaDir");
|
||||
|
@ -72,20 +72,6 @@ public class HTTPBin extends NanoHTTPD {
|
||||
return servedFiles.size() - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the file with the given ID from the server.
|
||||
*
|
||||
* @return True if a file was removed, false otherwise
|
||||
*/
|
||||
public synchronized boolean removeFile(int id) {
|
||||
if (id < 0) throw new IllegalArgumentException("ID < 0");
|
||||
if (id >= servedFiles.size()) {
|
||||
return false;
|
||||
} else {
|
||||
return servedFiles.remove(id) != null;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized File accessFile(int id) {
|
||||
if (id < 0 || id >= servedFiles.size()) {
|
||||
return null;
|
||||
|
@ -7,212 +7,9 @@ import android.support.v7.app.AppCompatActivity;
|
||||
* network.
|
||||
*/
|
||||
public abstract class CastEnabledActivity extends AppCompatActivity {
|
||||
// implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
public static final String TAG = "CastEnabledActivity";
|
||||
|
||||
// protected CastManager castManager;
|
||||
// protected SwitchableMediaRouteActionProvider mediaRouteActionProvider;
|
||||
// private final CastButtonVisibilityManager castButtonVisibilityManager = new CastButtonVisibilityManager();
|
||||
//
|
||||
// @Override
|
||||
// protected void onCreate(Bundle savedInstanceState) {
|
||||
// super.onCreate(savedInstanceState);
|
||||
//
|
||||
// PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).
|
||||
// registerOnSharedPreferenceChangeListener(this);
|
||||
//
|
||||
// castManager = CastManager.getInstance();
|
||||
// castManager.addCastConsumer(castConsumer);
|
||||
// castButtonVisibilityManager.setPrefEnabled(UserPreferences.isCastEnabled());
|
||||
// onCastConnectionChanged(castManager.isConnected());
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected void onDestroy() {
|
||||
// PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
|
||||
// .unregisterOnSharedPreferenceChangeListener(this);
|
||||
// castManager.removeCastConsumer(castConsumer);
|
||||
// super.onDestroy();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// @CallSuper
|
||||
// public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// super.onCreateOptionsMenu(menu);
|
||||
// getMenuInflater().inflate(R.menu.cast_enabled, menu);
|
||||
// castButtonVisibilityManager.setMenu(menu);
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// @CallSuper
|
||||
// public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
// super.onPrepareOptionsMenu(menu);
|
||||
// mediaRouteActionProvider = castManager
|
||||
// .addMediaRouterButton(menu.findItem(R.id.media_route_menu_item));
|
||||
// mediaRouteActionProvider.setEnabled(castButtonVisibilityManager.shouldEnable());
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected void onResume() {
|
||||
// super.onResume();
|
||||
// castButtonVisibilityManager.setResumed(true);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected void onPause() {
|
||||
// super.onPause();
|
||||
// castButtonVisibilityManager.setResumed(false);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
// if (UserPreferences.PREF_CAST_ENABLED.equals(key)) {
|
||||
// boolean newValue = UserPreferences.isCastEnabled();
|
||||
// Log.d(TAG, "onSharedPreferenceChanged(), isCastEnabled set to " + newValue);
|
||||
// castButtonVisibilityManager.setPrefEnabled(newValue);
|
||||
// // PlaybackService has its own listener, so if it's active we don't have to take action here.
|
||||
// if (!newValue && !PlaybackService.isRunning) {
|
||||
// CastManager.getInstance().disconnect();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// CastConsumer castConsumer = new DefaultCastConsumer() {
|
||||
// @Override
|
||||
// public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) {
|
||||
// onCastConnectionChanged(true);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onDisconnected() {
|
||||
// onCastConnectionChanged(false);
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// private void onCastConnectionChanged(boolean connected) {
|
||||
// if (connected) {
|
||||
// castButtonVisibilityManager.onConnected();
|
||||
// setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE);
|
||||
// } else {
|
||||
// castButtonVisibilityManager.onDisconnected();
|
||||
// setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Should be called by any activity or fragment for which the cast button should be shown.
|
||||
// *
|
||||
// * @param showAsAction refer to {@link MenuItem#setShowAsAction(int)}
|
||||
// */
|
||||
public final void requestCastButton(int showAsAction) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
// private class CastButtonVisibilityManager {
|
||||
// private volatile boolean prefEnabled = false;
|
||||
// private volatile boolean viewRequested = false;
|
||||
// private volatile boolean resumed = false;
|
||||
// private volatile boolean connected = false;
|
||||
// private volatile int showAsAction = MenuItem.SHOW_AS_ACTION_IF_ROOM;
|
||||
// private Menu menu;
|
||||
//
|
||||
// public synchronized void setPrefEnabled(boolean newValue) {
|
||||
// if (prefEnabled != newValue && resumed && (viewRequested || connected)) {
|
||||
// if (newValue) {
|
||||
// castManager.incrementUiCounter();
|
||||
// } else {
|
||||
// castManager.decrementUiCounter();
|
||||
// }
|
||||
// }
|
||||
// prefEnabled = newValue;
|
||||
// if (mediaRouteActionProvider != null) {
|
||||
// mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public synchronized void setResumed(boolean newValue) {
|
||||
// if (resumed == newValue) {
|
||||
// Log.e(TAG, "resumed should never change to the same value");
|
||||
// return;
|
||||
// }
|
||||
// resumed = newValue;
|
||||
// if (prefEnabled && (viewRequested || connected)) {
|
||||
// if (resumed) {
|
||||
// castManager.incrementUiCounter();
|
||||
// } else {
|
||||
// castManager.decrementUiCounter();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public synchronized void setViewRequested(boolean newValue) {
|
||||
// if (viewRequested != newValue && resumed && prefEnabled && !connected) {
|
||||
// if (newValue) {
|
||||
// castManager.incrementUiCounter();
|
||||
// } else {
|
||||
// castManager.decrementUiCounter();
|
||||
// }
|
||||
// }
|
||||
// viewRequested = newValue;
|
||||
// if (mediaRouteActionProvider != null) {
|
||||
// mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public synchronized void setConnected(boolean newValue) {
|
||||
// if (connected != newValue && resumed && prefEnabled && !prefEnabled) {
|
||||
// if (newValue) {
|
||||
// castManager.incrementUiCounter();
|
||||
// } else {
|
||||
// castManager.decrementUiCounter();
|
||||
// }
|
||||
// }
|
||||
// connected = newValue;
|
||||
// if (mediaRouteActionProvider != null) {
|
||||
// mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public synchronized boolean shouldEnable() {
|
||||
// return prefEnabled && viewRequested;
|
||||
// }
|
||||
//
|
||||
// public void setMenu(Menu menu) {
|
||||
// setViewRequested(false);
|
||||
// showAsAction = MenuItem.SHOW_AS_ACTION_IF_ROOM;
|
||||
// this.menu = menu;
|
||||
// setShowAsAction();
|
||||
// }
|
||||
//
|
||||
// public void requestCastButton(int showAsAction) {
|
||||
// setViewRequested(true);
|
||||
// this.showAsAction = showAsAction;
|
||||
// setShowAsAction();
|
||||
// }
|
||||
//
|
||||
// public void onConnected() {
|
||||
// setConnected(true);
|
||||
// setShowAsAction();
|
||||
// }
|
||||
//
|
||||
// public void onDisconnected() {
|
||||
// setConnected(false);
|
||||
// setShowAsAction();
|
||||
// }
|
||||
//
|
||||
// private void setShowAsAction() {
|
||||
// if (menu == null) {
|
||||
// Log.d(TAG, "setShowAsAction() without a menu");
|
||||
// return;
|
||||
// }
|
||||
// MenuItem item = menu.findItem(R.id.media_route_menu_item);
|
||||
// if (item == null) {
|
||||
// Log.e(TAG, "setShowAsAction(), but cast button not inflated");
|
||||
// return;
|
||||
// }
|
||||
// MenuItemCompat.setShowAsAction(item, connected? MenuItem.SHOW_AS_ACTION_ALWAYS : showAsAction);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
@ -36,7 +36,7 @@
|
||||
android:usesCleartextTraffic="true"
|
||||
android:logo="@mipmap/ic_launcher">
|
||||
<meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
|
||||
android:resource="@drawable/ic_notification" />
|
||||
android:resource="@drawable/ic_antenna" />
|
||||
<meta-data
|
||||
android:name="com.google.android.backup.api_key"
|
||||
android:value="AEdPqrEAAAAI3a05VToCTlqBymJrbFGaKQMvF-bBAuLsOdavBA"/>
|
||||
@ -209,6 +209,13 @@
|
||||
android:name=".activity.OpmlFeedChooserActivity"
|
||||
android:label="@string/opml_import_label">
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.BugReportActivity"
|
||||
android:label="@string/bug_report_title">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="de.danoeh.antennapod.activity.PreferenceActivity"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activity.VideoplayerActivity"
|
||||
|
@ -9,6 +9,9 @@ import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
|
||||
@ -32,13 +35,7 @@ public class CrashReportWriter implements Thread.UncaughtExceptionHandler {
|
||||
PrintWriter out = null;
|
||||
try {
|
||||
out = new PrintWriter(new FileWriter(path));
|
||||
out.println("[ Environment ]");
|
||||
out.println("Android version: " + Build.VERSION.RELEASE);
|
||||
out.println("OS version: " + System.getProperty("os.version"));
|
||||
out.println("AntennaPod version: " + BuildConfig.VERSION_NAME);
|
||||
out.println("Model: " + Build.MODEL);
|
||||
out.println("Device: " + Build.DEVICE);
|
||||
out.println("Product: " + Build.PRODUCT);
|
||||
out.println(getSystemInfo());
|
||||
out.println();
|
||||
out.println("[ StackTrace ]");
|
||||
ex.printStackTrace(out);
|
||||
@ -49,4 +46,15 @@ public class CrashReportWriter implements Thread.UncaughtExceptionHandler {
|
||||
}
|
||||
defaultHandler.uncaughtException(thread, ex);
|
||||
}
|
||||
|
||||
public static String getSystemInfo() {
|
||||
return "[ Environment ]" +
|
||||
"\nAndroid version: " + Build.VERSION.RELEASE +
|
||||
"\nOS version: " + System.getProperty("os.version") +
|
||||
"\nAntennaPod version: " + BuildConfig.VERSION_NAME +
|
||||
"\nModel: " + Build.MODEL +
|
||||
"\nDevice: " + Build.DEVICE +
|
||||
"\nProduct: " + Build.PRODUCT +
|
||||
"\nTime: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date()) + "\n";
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -57,8 +58,7 @@ public class AboutActivity extends AppCompatActivity {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if (url.startsWith("http")) {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
startActivity(browserIntent);
|
||||
IntentUtils.openInBrowser(AboutActivity.this, url);
|
||||
return true;
|
||||
} else {
|
||||
url = url.replace("file:///android_asset/", "");
|
||||
|
@ -7,6 +7,8 @@ import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import de.danoeh.antennapod.core.feed.MediaType;
|
||||
@ -58,11 +60,13 @@ public class AudioplayerActivity extends MediaplayerInfoActivity {
|
||||
}
|
||||
if (controller == null) {
|
||||
butPlaybackSpeed.setVisibility(View.GONE);
|
||||
txtvPlaybackSpeed.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
updatePlaybackSpeedButtonText();
|
||||
ViewCompat.setAlpha(butPlaybackSpeed, controller.canSetPlaybackSpeed() ? 1.0f : 0.5f);
|
||||
butPlaybackSpeed.setVisibility(View.VISIBLE);
|
||||
txtvPlaybackSpeed.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -72,21 +76,15 @@ public class AudioplayerActivity extends MediaplayerInfoActivity {
|
||||
}
|
||||
if (controller == null) {
|
||||
butPlaybackSpeed.setVisibility(View.GONE);
|
||||
txtvPlaybackSpeed.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
float speed = 1.0f;
|
||||
if(controller.canSetPlaybackSpeed()) {
|
||||
try {
|
||||
// we can only retrieve the playback speed from the controller/playback service
|
||||
// once mediaplayer has been initialized
|
||||
speed = Float.parseFloat(UserPreferences.getPlaybackSpeed());
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e(TAG, Log.getStackTraceString(e));
|
||||
UserPreferences.setPlaybackSpeed(String.valueOf(speed));
|
||||
}
|
||||
speed = UserPreferences.getPlaybackSpeed();
|
||||
}
|
||||
String speedStr = new DecimalFormat("0.00x").format(speed);
|
||||
butPlaybackSpeed.setText(speedStr);
|
||||
String speedStr = new DecimalFormat("0.00").format(speed);
|
||||
txtvPlaybackSpeed.setText(speedStr);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -105,7 +103,9 @@ public class AudioplayerActivity extends MediaplayerInfoActivity {
|
||||
}
|
||||
if (controller.canSetPlaybackSpeed()) {
|
||||
String[] availableSpeeds = UserPreferences.getPlaybackSpeedArray();
|
||||
String currentSpeed = UserPreferences.getPlaybackSpeed();
|
||||
DecimalFormatSymbols format = new DecimalFormatSymbols(Locale.US);
|
||||
format.setDecimalSeparator('.');
|
||||
String currentSpeed = new DecimalFormat("0.00", format).format(UserPreferences.getPlaybackSpeed());
|
||||
|
||||
// Provide initial value in case the speed list has changed
|
||||
// out from under us
|
||||
@ -139,6 +139,7 @@ public class AudioplayerActivity extends MediaplayerInfoActivity {
|
||||
return true;
|
||||
});
|
||||
butPlaybackSpeed.setVisibility(View.VISIBLE);
|
||||
txtvPlaybackSpeed.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,55 @@
|
||||
package de.danoeh.antennapod.activity;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.widget.TextView;
|
||||
import de.danoeh.antennapod.CrashReportWriter;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Displays the 'crash report' screen
|
||||
*/
|
||||
public class BugReportActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(UserPreferences.getTheme());
|
||||
super.onCreate(savedInstanceState);
|
||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
setContentView(R.layout.bug_report);
|
||||
|
||||
TextView crashDetailsText = findViewById(R.id.crash_report_logs);
|
||||
|
||||
try {
|
||||
File crashFile = CrashReportWriter.getFile();
|
||||
String crashReportContent = IOUtils.toString(new FileInputStream(crashFile), Charset.forName("UTF-8"));
|
||||
crashDetailsText.setText(crashReportContent);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
crashDetailsText.setText("No crash report recorded\n" + CrashReportWriter.getSystemInfo());
|
||||
}
|
||||
|
||||
findViewById(R.id.btn_open_bug_tracker).setOnClickListener(v -> {
|
||||
IntentUtils.openInBrowser(BugReportActivity.this, "https://github.com/AntennaPod/AntennaPod/issues");
|
||||
});
|
||||
|
||||
findViewById(R.id.btn_copy_log).setOnClickListener(v -> {
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText(getString(R.string.bug_report_title), crashDetailsText.getText());
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Snackbar.make(findViewById(android.R.id.content), R.string.copied_to_clipboard, Snackbar.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
}
|
@ -49,6 +49,7 @@ public class CastplayerActivity extends MediaplayerInfoActivity {
|
||||
super.setupGUI();
|
||||
if (butPlaybackSpeed != null) {
|
||||
butPlaybackSpeed.setVisibility(View.GONE);
|
||||
txtvPlaybackSpeed.setVisibility(View.GONE);
|
||||
}
|
||||
// if (butCastDisconnect != null) {
|
||||
// butCastDisconnect.setOnClickListener(v -> castManager.disconnect());
|
||||
|
@ -2,6 +2,7 @@ package de.danoeh.antennapod.activity;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
@ -10,6 +11,7 @@ import android.database.DataSetObserver;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.Fragment;
|
||||
@ -32,9 +34,11 @@ import android.widget.Toast;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
|
||||
import de.danoeh.antennapod.preferences.PreferenceUpgrader;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -43,7 +47,6 @@ import de.danoeh.antennapod.adapter.NavListAdapter;
|
||||
import de.danoeh.antennapod.core.asynctask.FeedRemover;
|
||||
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
|
||||
import de.danoeh.antennapod.core.event.MessageEvent;
|
||||
import de.danoeh.antennapod.core.event.ProgressEvent;
|
||||
import de.danoeh.antennapod.core.event.QueueEvent;
|
||||
import de.danoeh.antennapod.core.feed.EventDistributor;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
@ -56,25 +59,22 @@ import de.danoeh.antennapod.core.util.FeedItemUtil;
|
||||
import de.danoeh.antennapod.core.util.Flavors;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import de.danoeh.antennapod.core.util.StorageUtils;
|
||||
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
|
||||
import de.danoeh.antennapod.dialog.RatingDialog;
|
||||
import de.danoeh.antennapod.dialog.RenameFeedDialog;
|
||||
import de.danoeh.antennapod.fragment.AddFeedFragment;
|
||||
import de.danoeh.antennapod.fragment.DownloadsFragment;
|
||||
import de.danoeh.antennapod.fragment.EpisodesFragment;
|
||||
import de.danoeh.antennapod.fragment.ExternalPlayerFragment;
|
||||
import de.danoeh.antennapod.fragment.ItemlistFragment;
|
||||
import de.danoeh.antennapod.fragment.FeedItemlistFragment;
|
||||
import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
|
||||
import de.danoeh.antennapod.fragment.QueueFragment;
|
||||
import de.danoeh.antennapod.fragment.SubscriptionFragment;
|
||||
import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
|
||||
import de.danoeh.antennapod.preferences.PreferenceUpgrader;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
/**
|
||||
* The activity that is shown when the user launches the app.
|
||||
@ -88,13 +88,13 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi
|
||||
|
||||
public static final String PREF_NAME = "MainActivityPrefs";
|
||||
public static final String PREF_IS_FIRST_LAUNCH = "prefMainActivityIsFirstLaunch";
|
||||
private static final String PREF_LAST_FRAGMENT_TAG = "prefMainActivityLastFragmentTag";
|
||||
public static final String PREF_LAST_FRAGMENT_TAG = "prefMainActivityLastFragmentTag";
|
||||
|
||||
public static final String EXTRA_NAV_TYPE = "nav_type";
|
||||
public static final String EXTRA_NAV_INDEX = "nav_index";
|
||||
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";
|
||||
private static final String EXTRA_FEED_ID = "fragment_feed_id";
|
||||
|
||||
private static final String SAVE_BACKSTACK_COUNT = "backstackCount";
|
||||
private static final String SAVE_TITLE = "title";
|
||||
@ -128,6 +128,14 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi
|
||||
|
||||
private long lastBackButtonPressTime = 0;
|
||||
|
||||
@NonNull
|
||||
public static Intent getIntentToOpenFeed(@NonNull Context context, long feedId) {
|
||||
Intent intent = new Intent(context.getApplicationContext(), MainActivity.class);
|
||||
intent.putExtra(MainActivity.EXTRA_FEED_ID, feedId);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(UserPreferences.getNoTitleTheme());
|
||||
@ -233,6 +241,7 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi
|
||||
private void checkFirstLaunch() {
|
||||
SharedPreferences prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE);
|
||||
if (prefs.getBoolean(PREF_IS_FIRST_LAUNCH, true)) {
|
||||
loadFragment(AddFeedFragment.TAG, null);
|
||||
new Handler().postDelayed(() -> drawerLayout.openDrawer(navDrawer), 1500);
|
||||
|
||||
// for backward compatibility, we only change defaults for fresh installs
|
||||
@ -240,7 +249,7 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi
|
||||
|
||||
SharedPreferences.Editor edit = prefs.edit();
|
||||
edit.putBoolean(PREF_IS_FIRST_LAUNCH, false);
|
||||
edit.commit();
|
||||
edit.apply();
|
||||
}
|
||||
}
|
||||
|
||||
@ -337,7 +346,7 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi
|
||||
}
|
||||
|
||||
public void loadFeedFragmentById(long feedId, Bundle args) {
|
||||
Fragment fragment = ItemlistFragment.newInstance(feedId);
|
||||
Fragment fragment = FeedItemlistFragment.newInstance(feedId);
|
||||
if(args != null) {
|
||||
fragment.setArguments(args);
|
||||
}
|
||||
@ -777,25 +786,6 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(ProgressEvent event) {
|
||||
Log.d(TAG, "onEvent(" + event + ")");
|
||||
switch(event.action) {
|
||||
case START:
|
||||
pd = new ProgressDialog(this);
|
||||
pd.setMessage(event.message);
|
||||
pd.setIndeterminate(true);
|
||||
pd.setCancelable(false);
|
||||
pd.show();
|
||||
break;
|
||||
case END:
|
||||
if(pd != null) {
|
||||
pd.dismiss();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(MessageEvent event) {
|
||||
Log.d(TAG, "onEvent(" + event + ")");
|
||||
|
@ -22,8 +22,6 @@ import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||
@ -35,16 +33,14 @@ import com.bumptech.glide.Glide;
|
||||
import com.joanzapata.iconify.IconDrawable;
|
||||
import com.joanzapata.iconify.fonts.FontAwesomeIcons;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
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;
|
||||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.util.Consumer;
|
||||
import de.danoeh.antennapod.core.util.Converter;
|
||||
@ -62,12 +58,15 @@ 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.SleepTimerDialog;
|
||||
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
|
||||
/**
|
||||
@ -79,9 +78,6 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
||||
private static final String PREFS = "MediaPlayerActivityPreferences";
|
||||
private static final String PREF_SHOW_TIME_LEFT = "showTimeLeft";
|
||||
private static final int REQUEST_CODE_STORAGE = 42;
|
||||
private static final float PLAYBACK_SPEED_STEP = 0.05f;
|
||||
private static final float DEFAULT_MIN_PLAYBACK_SPEED = 0.5f;
|
||||
private static final float DEFAULT_MAX_PLAYBACK_SPEED = 2.5f;
|
||||
|
||||
PlaybackController controller;
|
||||
|
||||
@ -202,6 +198,11 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
||||
};
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(PlaybackPositionEvent event) {
|
||||
onPositionObserverUpdate();
|
||||
}
|
||||
|
||||
private static TextView getTxtvFFFromActivity(MediaplayerActivity activity) {
|
||||
return activity.txtvFF;
|
||||
}
|
||||
@ -282,6 +283,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
||||
controller.init();
|
||||
loadMediaInfo();
|
||||
onPositionObserverUpdate();
|
||||
EventBus.getDefault().register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -294,6 +296,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
EventBus.getDefault().unregister(this);
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@ -330,6 +333,8 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
||||
Playable media = controller.getMedia();
|
||||
boolean isFeedMedia = media != null && (media instanceof FeedMedia);
|
||||
|
||||
menu.findItem(R.id.open_feed_item).setVisible(isFeedMedia); // FeedMedia implies it belongs to a Feed
|
||||
|
||||
boolean hasWebsiteLink = ( getWebsiteLinkWithFallback(media) != null );
|
||||
menu.findItem(R.id.visit_website_item).setVisible(hasWebsiteLink);
|
||||
|
||||
@ -365,7 +370,8 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
||||
menu.findItem(R.id.audio_controls).setIcon(new IconDrawable(this,
|
||||
FontAwesomeIcons.fa_sliders).color(textColor).actionBarSize());
|
||||
} else {
|
||||
menu.findItem(R.id.audio_controls).setVisible(false);
|
||||
menu.findItem(R.id.audio_controls).setIcon(new IconDrawable(this,
|
||||
FontAwesomeIcons.fa_sliders).color(0xffffffff).actionBarSize());
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -396,29 +402,24 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
||||
return true;
|
||||
} else {
|
||||
if (media != null) {
|
||||
final @Nullable FeedItem feedItem = getFeedItem(media); // some options option requires FeedItem
|
||||
switch (item.getItemId()) {
|
||||
case R.id.add_to_favorites_item:
|
||||
if(media instanceof FeedMedia) {
|
||||
FeedItem feedItem = ((FeedMedia)media).getItem();
|
||||
if(feedItem != null) {
|
||||
DBWriter.addFavoriteItem(feedItem);
|
||||
isFavorite = true;
|
||||
invalidateOptionsMenu();
|
||||
Toast.makeText(this, R.string.added_to_favorites, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
if (feedItem != null) {
|
||||
DBWriter.addFavoriteItem(feedItem);
|
||||
isFavorite = true;
|
||||
invalidateOptionsMenu();
|
||||
Toast.makeText(this, R.string.added_to_favorites, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
break;
|
||||
case R.id.remove_from_favorites_item:
|
||||
if(media instanceof FeedMedia) {
|
||||
FeedItem feedItem = ((FeedMedia)media).getItem();
|
||||
if(feedItem != null) {
|
||||
DBWriter.removeFavoriteItem(feedItem);
|
||||
isFavorite = false;
|
||||
invalidateOptionsMenu();
|
||||
Toast.makeText(this, R.string.removed_from_favorites, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
if (feedItem != null) {
|
||||
DBWriter.removeFavoriteItem(feedItem);
|
||||
isFavorite = false;
|
||||
invalidateOptionsMenu();
|
||||
Toast.makeText(this, R.string.removed_from_favorites, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
break;
|
||||
case R.id.disable_sleeptimer_item:
|
||||
@ -451,171 +452,37 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
||||
}
|
||||
break;
|
||||
case R.id.audio_controls:
|
||||
MaterialDialog dialog = new MaterialDialog.Builder(this)
|
||||
.title(R.string.audio_controls)
|
||||
.customView(R.layout.audio_controls, true)
|
||||
.neutralText(R.string.close_label)
|
||||
.onNeutral((dialog1, which) -> {
|
||||
final SeekBar left = (SeekBar) dialog1.findViewById(R.id.volume_left);
|
||||
final SeekBar right = (SeekBar) dialog1.findViewById(R.id.volume_right);
|
||||
UserPreferences.setVolume(left.getProgress(), right.getProgress());
|
||||
})
|
||||
.show();
|
||||
final SeekBar barPlaybackSpeed = (SeekBar) dialog.findViewById(R.id.playback_speed);
|
||||
final Button butDecSpeed = (Button) dialog.findViewById(R.id.butDecSpeed);
|
||||
butDecSpeed.setOnClickListener(v -> {
|
||||
if(controller != null && controller.canSetPlaybackSpeed()) {
|
||||
barPlaybackSpeed.setProgress(barPlaybackSpeed.getProgress() - 1);
|
||||
} else {
|
||||
VariableSpeedDialog.showGetPluginDialog(this);
|
||||
}
|
||||
});
|
||||
final Button butIncSpeed = (Button) dialog.findViewById(R.id.butIncSpeed);
|
||||
butIncSpeed.setOnClickListener(v -> {
|
||||
if(controller != null && controller.canSetPlaybackSpeed()) {
|
||||
barPlaybackSpeed.setProgress(barPlaybackSpeed.getProgress() + 1);
|
||||
} else {
|
||||
VariableSpeedDialog.showGetPluginDialog(this);
|
||||
}
|
||||
});
|
||||
|
||||
final TextView txtvPlaybackSpeed = (TextView) dialog.findViewById(R.id.txtvPlaybackSpeed);
|
||||
float currentSpeed = 1.0f;
|
||||
try {
|
||||
currentSpeed = Float.parseFloat(UserPreferences.getPlaybackSpeed());
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e(TAG, Log.getStackTraceString(e));
|
||||
UserPreferences.setPlaybackSpeed(String.valueOf(currentSpeed));
|
||||
boolean isPlayingVideo = controller.getMedia().getMediaType() == MediaType.VIDEO;
|
||||
PlaybackControlsDialog dialog = PlaybackControlsDialog.newInstance(isPlayingVideo);
|
||||
dialog.show(getSupportFragmentManager(), "playback_controls");
|
||||
break;
|
||||
case R.id.open_feed_item:
|
||||
if (feedItem != null) {
|
||||
Intent intent = MainActivity.getIntentToOpenFeed(this, feedItem.getFeedId());
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
String[] availableSpeeds = UserPreferences.getPlaybackSpeedArray();
|
||||
final float minPlaybackSpeed = availableSpeeds.length > 1 ?
|
||||
Float.valueOf(availableSpeeds[0]) : DEFAULT_MIN_PLAYBACK_SPEED;
|
||||
float maxPlaybackSpeed = availableSpeeds.length > 1 ?
|
||||
Float.valueOf(availableSpeeds[availableSpeeds.length - 1]) : DEFAULT_MAX_PLAYBACK_SPEED;
|
||||
int progressMax = (int) ((maxPlaybackSpeed - minPlaybackSpeed) / PLAYBACK_SPEED_STEP);
|
||||
barPlaybackSpeed.setMax(progressMax);
|
||||
|
||||
txtvPlaybackSpeed.setText(String.format("%.2fx", currentSpeed));
|
||||
barPlaybackSpeed.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
if(controller != null && controller.canSetPlaybackSpeed()) {
|
||||
float playbackSpeed = progress * PLAYBACK_SPEED_STEP + minPlaybackSpeed;
|
||||
controller.setPlaybackSpeed(playbackSpeed);
|
||||
String speedPref = String.format(Locale.US, "%.2f", playbackSpeed);
|
||||
UserPreferences.setPlaybackSpeed(speedPref);
|
||||
String speedStr = String.format("%.2fx", playbackSpeed);
|
||||
txtvPlaybackSpeed.setText(speedStr);
|
||||
} else if(fromUser) {
|
||||
float speed = Float.valueOf(UserPreferences.getPlaybackSpeed());
|
||||
barPlaybackSpeed.post(() -> barPlaybackSpeed.setProgress(
|
||||
(int) ((speed - minPlaybackSpeed) / PLAYBACK_SPEED_STEP)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
if(controller != null && !controller.canSetPlaybackSpeed()) {
|
||||
VariableSpeedDialog.showGetPluginDialog(MediaplayerActivity.this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
});
|
||||
barPlaybackSpeed.setProgress((int) ((currentSpeed - minPlaybackSpeed) / PLAYBACK_SPEED_STEP));
|
||||
|
||||
final SeekBar barLeftVolume = (SeekBar) dialog.findViewById(R.id.volume_left);
|
||||
barLeftVolume.setProgress(UserPreferences.getLeftVolumePercentage());
|
||||
final SeekBar barRightVolume = (SeekBar) dialog.findViewById(R.id.volume_right);
|
||||
barRightVolume.setProgress(UserPreferences.getRightVolumePercentage());
|
||||
final CheckBox stereoToMono = (CheckBox) dialog.findViewById(R.id.stereo_to_mono);
|
||||
stereoToMono.setChecked(UserPreferences.stereoToMono());
|
||||
if (controller != null && !controller.canDownmix()) {
|
||||
stereoToMono.setEnabled(false);
|
||||
String sonicOnly = getString(R.string.sonic_only);
|
||||
stereoToMono.setText(stereoToMono.getText() + " [" + sonicOnly + "]");
|
||||
}
|
||||
|
||||
if (UserPreferences.useExoplayer()) {
|
||||
barRightVolume.setEnabled(false);
|
||||
}
|
||||
|
||||
final CheckBox skipSilence = (CheckBox) dialog.findViewById(R.id.skipSilence);
|
||||
skipSilence.setChecked(UserPreferences.isSkipSilence());
|
||||
if (!UserPreferences.useExoplayer()) {
|
||||
skipSilence.setEnabled(false);
|
||||
String exoplayerOnly = getString(R.string.exoplayer_only);
|
||||
skipSilence.setText(skipSilence.getText() + " [" + exoplayerOnly + "]");
|
||||
}
|
||||
skipSilence.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
UserPreferences.setSkipSilence(isChecked);
|
||||
controller.setSkipSilence(isChecked);
|
||||
});
|
||||
|
||||
barLeftVolume.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
controller.setVolume(
|
||||
Converter.getVolumeFromPercentage(progress),
|
||||
Converter.getVolumeFromPercentage(barRightVolume.getProgress()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
});
|
||||
barRightVolume.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
controller.setVolume(
|
||||
Converter.getVolumeFromPercentage(barLeftVolume.getProgress()),
|
||||
Converter.getVolumeFromPercentage(progress));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
});
|
||||
stereoToMono.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
UserPreferences.stereoToMono(isChecked);
|
||||
if (controller != null) {
|
||||
controller.setDownmix(isChecked);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case R.id.visit_website_item:
|
||||
Uri uri = Uri.parse(getWebsiteLinkWithFallback(media));
|
||||
startActivity(new Intent(Intent.ACTION_VIEW, uri));
|
||||
IntentUtils.openInBrowser(MediaplayerActivity.this, getWebsiteLinkWithFallback(media));
|
||||
break;
|
||||
case R.id.share_link_item:
|
||||
if (media instanceof FeedMedia) {
|
||||
ShareUtils.shareFeedItemLink(this, ((FeedMedia) media).getItem());
|
||||
if (feedItem != null) {
|
||||
ShareUtils.shareFeedItemLink(this, feedItem);
|
||||
}
|
||||
break;
|
||||
case R.id.share_download_url_item:
|
||||
if (media instanceof FeedMedia) {
|
||||
ShareUtils.shareFeedItemDownloadLink(this, ((FeedMedia) media).getItem());
|
||||
if (feedItem != null) {
|
||||
ShareUtils.shareFeedItemDownloadLink(this, feedItem);
|
||||
}
|
||||
break;
|
||||
case R.id.share_link_with_position_item:
|
||||
if (media instanceof FeedMedia) {
|
||||
ShareUtils.shareFeedItemLink(this, ((FeedMedia) media).getItem(), true);
|
||||
if (feedItem != null) {
|
||||
ShareUtils.shareFeedItemLink(this, feedItem, true);
|
||||
}
|
||||
break;
|
||||
case R.id.share_download_url_with_position_item:
|
||||
if (media instanceof FeedMedia) {
|
||||
ShareUtils.shareFeedItemDownloadLink(this, ((FeedMedia) media).getItem(), true);
|
||||
if (feedItem != null) {
|
||||
ShareUtils.shareFeedItemDownloadLink(this, feedItem, true);
|
||||
}
|
||||
break;
|
||||
case R.id.share_file:
|
||||
@ -666,9 +533,10 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
||||
return;
|
||||
}
|
||||
|
||||
int currentPosition = TimeSpeedConverter.convert(controller.getPosition());
|
||||
int duration = TimeSpeedConverter.convert(controller.getDuration());
|
||||
int remainingTime = TimeSpeedConverter.convert(
|
||||
TimeSpeedConverter converter = new TimeSpeedConverter(controller.getCurrentPlaybackSpeedMultiplier());
|
||||
int currentPosition = converter.convert(controller.getPosition());
|
||||
int duration = converter.convert(controller.getDuration());
|
||||
int remainingTime = converter.convert(
|
||||
controller.getDuration() - controller.getPosition());
|
||||
Log.d(TAG, "currentPosition " + Converter.getDurationStringLong(currentPosition));
|
||||
if (currentPosition == PlaybackService.INVALID_TIME ||
|
||||
@ -780,7 +648,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
||||
}
|
||||
}
|
||||
|
||||
static public void showSkipPreference(Activity activity, SkipDirection direction) {
|
||||
public static void showSkipPreference(Activity activity, SkipDirection direction) {
|
||||
int checked = 0;
|
||||
int skipSecs = direction.getPrefSkipSeconds();
|
||||
final int[] values = activity.getResources().getIntArray(R.array.seek_delta_values);
|
||||
@ -824,14 +692,15 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
||||
return;
|
||||
}
|
||||
|
||||
TimeSpeedConverter converter = new TimeSpeedConverter(controller.getCurrentPlaybackSpeedMultiplier());
|
||||
String length;
|
||||
if (showTimeLeft) {
|
||||
int remainingTime = TimeSpeedConverter.convert(
|
||||
int remainingTime = converter.convert(
|
||||
media.getDuration() - media.getPosition());
|
||||
|
||||
length = "-" + Converter.getDurationStringLong(remainingTime);
|
||||
} else {
|
||||
int duration = TimeSpeedConverter.convert(media.getDuration());
|
||||
int duration = converter.convert(media.getDuration());
|
||||
length = Converter.getDurationStringLong(duration);
|
||||
}
|
||||
txtvLength.setText(length);
|
||||
@ -935,7 +804,8 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
||||
prog = controller.onSeekBarProgressChanged(seekBar, progress, fromUser, txtvPosition);
|
||||
if (showTimeLeft && prog != 0) {
|
||||
int duration = controller.getDuration();
|
||||
int timeLeft = TimeSpeedConverter.convert(duration - (int) (prog * duration));
|
||||
TimeSpeedConverter converter = new TimeSpeedConverter(controller.getCurrentPlaybackSpeedMultiplier());
|
||||
int timeLeft = converter.convert(duration - (int) (prog * duration));
|
||||
String length = "-" + Converter.getDurationStringLong(timeLeft);
|
||||
txtvLength.setText(length);
|
||||
}
|
||||
@ -956,11 +826,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
||||
}
|
||||
|
||||
private void checkFavorite() {
|
||||
Playable playable = controller.getMedia();
|
||||
if (!(playable instanceof FeedMedia)) {
|
||||
return;
|
||||
}
|
||||
FeedItem feedItem = ((FeedMedia) playable).getItem();
|
||||
FeedItem feedItem = getFeedItem(controller.getMedia());
|
||||
if (feedItem == null) {
|
||||
return;
|
||||
}
|
||||
@ -1015,4 +881,13 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static FeedItem getFeedItem(@Nullable Playable playable) {
|
||||
if ((playable != null) && (playable instanceof FeedMedia)) {
|
||||
return ((FeedMedia)playable).getItem();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import android.widget.AdapterView;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.viewpagerindicator.CirclePageIndicator;
|
||||
@ -92,7 +93,8 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem
|
||||
NavListAdapter.SUBSCRIPTION_LIST_TAG
|
||||
};
|
||||
|
||||
Button butPlaybackSpeed;
|
||||
ImageButton butPlaybackSpeed;
|
||||
TextView txtvPlaybackSpeed;
|
||||
ImageButton butCastDisconnect;
|
||||
private DrawerLayout drawerLayout;
|
||||
private NavListAdapter navAdapter;
|
||||
@ -120,7 +122,6 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem
|
||||
disposable.dispose();
|
||||
}
|
||||
EventDistributor.getInstance().unregister(contentUpdate);
|
||||
EventBus.getDefault().unregister(this);
|
||||
saveCurrentFragment();
|
||||
}
|
||||
|
||||
@ -173,7 +174,6 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
EventDistributor.getInstance().register(contentUpdate);
|
||||
EventBus.getDefault().register(this);
|
||||
loadData();
|
||||
}
|
||||
|
||||
@ -258,6 +258,7 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem
|
||||
});
|
||||
|
||||
butPlaybackSpeed = findViewById(R.id.butPlaybackSpeed);
|
||||
txtvPlaybackSpeed = findViewById(R.id.txtvPlaybackSpeed);
|
||||
butCastDisconnect = findViewById(R.id.butCastDisconnect);
|
||||
|
||||
pager = findViewById(R.id.pager);
|
||||
|
@ -32,8 +32,6 @@ import android.widget.TextView;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
|
||||
import de.danoeh.antennapod.core.feed.VolumeReductionSetting;
|
||||
import de.danoeh.antennapod.core.glide.FastBlurTransformation;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
@ -55,7 +53,9 @@ import de.danoeh.antennapod.core.feed.EventDistributor;
|
||||
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.VolumeReductionSetting;
|
||||
import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
||||
import de.danoeh.antennapod.core.glide.FastBlurTransformation;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadRequest;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadStatus;
|
||||
@ -443,11 +443,9 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
|
||||
|
||||
subscribeButton.setOnClickListener(v -> {
|
||||
if(feedInFeedlist(feed)) {
|
||||
Intent intent = new Intent(OnlineFeedViewActivity.this, MainActivity.class);
|
||||
// feed.getId() is always 0, we have to retrieve the id from the feed list from
|
||||
// the database
|
||||
intent.putExtra(MainActivity.EXTRA_FEED_ID, getFeedId(feed));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
Intent intent = MainActivity.getIntentToOpenFeed(this, getFeedId(feed));
|
||||
startActivity(intent);
|
||||
} else {
|
||||
Feed f = new Feed(selectedDownloadUrl, null, feed.getTitle());
|
||||
|
@ -1,6 +1,7 @@
|
||||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
@ -23,10 +24,12 @@ import android.widget.TextView;
|
||||
import com.joanzapata.iconify.Iconify;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.adapter.actionbutton.ItemActionButton;
|
||||
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.storage.DownloadRequester;
|
||||
@ -50,6 +53,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR
|
||||
private final boolean showOnlyNewEpisodes;
|
||||
|
||||
private FeedItem selectedItem;
|
||||
private Holder currentlyPlayingItem = null;
|
||||
|
||||
private final int playingBackGroundColor;
|
||||
private final int normalBackGroundColor;
|
||||
@ -165,8 +169,9 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR
|
||||
holder.progress.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
if(media.isCurrentlyPlaying()) {
|
||||
if (media.isCurrentlyPlaying()) {
|
||||
holder.container.setBackgroundColor(playingBackGroundColor);
|
||||
currentlyPlayingItem = holder;
|
||||
} else {
|
||||
holder.container.setBackgroundColor(normalBackGroundColor);
|
||||
}
|
||||
@ -196,6 +201,22 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR
|
||||
.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull Holder holder, int pos, List<Object> payload) {
|
||||
onBindViewHolder(holder, pos);
|
||||
|
||||
if (holder == currentlyPlayingItem && payload.size() == 1 && payload.get(0) instanceof PlaybackPositionEvent) {
|
||||
PlaybackPositionEvent event = (PlaybackPositionEvent) payload.get(0);
|
||||
holder.progress.setProgress((int) (100.0 * event.getPosition() / event.getDuration()));
|
||||
}
|
||||
}
|
||||
|
||||
public void notifyCurrentlyPlayingItemChanged(PlaybackPositionEvent event) {
|
||||
if (currentlyPlayingItem != null && currentlyPlayingItem.getAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
notifyItemChanged(currentlyPlayingItem.getAdapterPosition(), event);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FeedItem getSelectedItem() {
|
||||
return selectedItem;
|
||||
@ -262,7 +283,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR
|
||||
FeedItem item = itemAccess.getItem(getAdapterPosition());
|
||||
|
||||
MenuInflater inflater = mainActivityRef.get().getMenuInflater();
|
||||
inflater.inflate(R.menu.allepisodes_context, menu);
|
||||
inflater.inflate(R.menu.feeditemlist_context, menu);
|
||||
|
||||
if (item != null) {
|
||||
menu.setHeaderTitle(item.getTitle());
|
||||
@ -277,9 +298,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR
|
||||
item1.setVisible(visible);
|
||||
}
|
||||
};
|
||||
FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, null);
|
||||
|
||||
contextMenuInterface.setItemVisibility(R.id.remove_new_flag_item, item.isNew());
|
||||
FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -100,14 +100,6 @@ public class DownloadedEpisodesListAdapter extends BaseAdapter {
|
||||
String pubDateStr = DateUtils.formatAbbrev(context, item.getPubDate());
|
||||
holder.pubDate.setText(pubDateStr);
|
||||
|
||||
FeedItem.State state = item.getState();
|
||||
if (state == FeedItem.State.PLAYING && PlaybackService.isRunning) {
|
||||
holder.butSecondary.setEnabled(false);
|
||||
holder.butSecondary.setAlpha(0.5f);
|
||||
} else {
|
||||
holder.butSecondary.setEnabled(true);
|
||||
holder.butSecondary.setAlpha(1.0f);
|
||||
}
|
||||
holder.butSecondary.setFocusable(false);
|
||||
holder.butSecondary.setTag(item);
|
||||
holder.butSecondary.setOnClickListener(secondaryActionListener);
|
||||
|
@ -1,7 +1,6 @@
|
||||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@ -15,20 +14,15 @@ 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.Converter;
|
||||
import de.danoeh.antennapod.core.util.ThemeUtils;
|
||||
|
||||
public class DownloadlistAdapter extends BaseAdapter {
|
||||
|
||||
private static final int SELECTION_NONE = -1;
|
||||
|
||||
private int selectedItemIndex;
|
||||
private final ItemAccess itemAccess;
|
||||
private final Context context;
|
||||
|
||||
public DownloadlistAdapter(Context context,
|
||||
ItemAccess itemAccess) {
|
||||
super();
|
||||
this.selectedItemIndex = SELECTION_NONE;
|
||||
this.context = context;
|
||||
this.itemAccess = itemAccess;
|
||||
}
|
||||
@ -74,13 +68,6 @@ public class DownloadlistAdapter extends BaseAdapter {
|
||||
holder = (Holder) convertView.getTag();
|
||||
}
|
||||
|
||||
if (position == selectedItemIndex) {
|
||||
convertView.setBackgroundColor(ContextCompat.getColor(convertView.getContext(),
|
||||
ThemeUtils.getSelectionBackgroundColor()));
|
||||
} else {
|
||||
convertView.setBackgroundResource(0);
|
||||
}
|
||||
|
||||
holder.title.setText(request.getTitle());
|
||||
|
||||
holder.progbar.setIndeterminate(request.getSoFar() <= 0);
|
||||
|
@ -13,15 +13,18 @@ import android.widget.BaseAdapter;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.adapter.actionbutton.ItemActionButton;
|
||||
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.storage.DownloadRequester;
|
||||
import de.danoeh.antennapod.core.util.Converter;
|
||||
import de.danoeh.antennapod.core.util.DateUtils;
|
||||
import de.danoeh.antennapod.core.util.LongList;
|
||||
import de.danoeh.antennapod.core.util.ThemeUtils;
|
||||
@ -34,15 +37,13 @@ public class FeedItemlistAdapter extends BaseAdapter {
|
||||
private final ItemAccess itemAccess;
|
||||
private final Context context;
|
||||
private final boolean showFeedtitle;
|
||||
private final int selectedItemIndex;
|
||||
/** true if played items should be made partially transparent */
|
||||
private final boolean makePlayedItemsTransparent;
|
||||
|
||||
private static final int SELECTION_NONE = -1;
|
||||
|
||||
private final int playingBackGroundColor;
|
||||
private final int normalBackGroundColor;
|
||||
|
||||
private int currentlyPlayingItem = -1;
|
||||
|
||||
public FeedItemlistAdapter(Context context,
|
||||
ItemAccess itemAccess,
|
||||
boolean showFeedtitle,
|
||||
@ -51,7 +52,6 @@ public class FeedItemlistAdapter extends BaseAdapter {
|
||||
this.context = context;
|
||||
this.itemAccess = itemAccess;
|
||||
this.showFeedtitle = showFeedtitle;
|
||||
this.selectedItemIndex = SELECTION_NONE;
|
||||
this.makePlayedItemsTransparent = makePlayedItemsTransparent;
|
||||
|
||||
playingBackGroundColor = ThemeUtils.getColorFromAttr(context, R.attr.currently_playing_background);
|
||||
@ -112,12 +112,6 @@ public class FeedItemlistAdapter extends BaseAdapter {
|
||||
|
||||
if (!(getItemViewType(position) == Adapter.IGNORE_ITEM_VIEW_TYPE)) {
|
||||
convertView.setVisibility(View.VISIBLE);
|
||||
if (position == selectedItemIndex) {
|
||||
convertView.setBackgroundColor(ContextCompat.getColor(convertView.getContext(),
|
||||
ThemeUtils.getSelectionBackgroundColor()));
|
||||
} else {
|
||||
convertView.setBackgroundResource(0);
|
||||
}
|
||||
|
||||
StringBuilder buffer = new StringBuilder(item.getTitle());
|
||||
if (showFeedtitle) {
|
||||
@ -187,8 +181,9 @@ public class FeedItemlistAdapter extends BaseAdapter {
|
||||
}
|
||||
typeDrawables.recycle();
|
||||
|
||||
if(media.isCurrentlyPlaying()) {
|
||||
if (media.isCurrentlyPlaying()) {
|
||||
holder.container.setBackgroundColor(playingBackGroundColor);
|
||||
currentlyPlayingItem = position;
|
||||
} else {
|
||||
holder.container.setBackgroundColor(normalBackGroundColor);
|
||||
}
|
||||
@ -206,6 +201,19 @@ public class FeedItemlistAdapter extends BaseAdapter {
|
||||
return convertView;
|
||||
}
|
||||
|
||||
public void notifyCurrentlyPlayingItemChanged(PlaybackPositionEvent event, ListView listView) {
|
||||
if (currentlyPlayingItem != -1 && currentlyPlayingItem < getCount()) {
|
||||
View view = listView.getChildAt(currentlyPlayingItem
|
||||
- listView.getFirstVisiblePosition() + listView.getHeaderViewsCount());
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
Holder holder = (Holder) view.getTag();
|
||||
holder.episodeProgress.setProgress((int) (100.0 * event.getPosition() / event.getDuration()));
|
||||
holder.lenSize.setText(Converter.getDurationStringLong(event.getDuration() - event.getPosition()));
|
||||
}
|
||||
}
|
||||
|
||||
static class Holder {
|
||||
LinearLayout container;
|
||||
TextView title;
|
||||
|
@ -1,6 +1,7 @@
|
||||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@ -51,6 +52,17 @@ public class FeedItemlistDescriptionAdapter extends ArrayAdapter<FeedItem> {
|
||||
.replaceAll("\\s+", " ")
|
||||
.trim();
|
||||
holder.description.setText(description);
|
||||
|
||||
final int MAX_LINES_COLLAPSED = 3;
|
||||
holder.description.setMaxLines(MAX_LINES_COLLAPSED);
|
||||
holder.description.setOnClickListener(v -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
|
||||
&& holder.description.getMaxLines() > MAX_LINES_COLLAPSED) {
|
||||
holder.description.setMaxLines(MAX_LINES_COLLAPSED);
|
||||
} else {
|
||||
holder.description.setMaxLines(2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
return convertView;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.view.MotionEventCompat;
|
||||
@ -25,9 +25,11 @@ import android.widget.TextView;
|
||||
|
||||
import com.joanzapata.iconify.Iconify;
|
||||
|
||||
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
@ -58,6 +60,7 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap
|
||||
private boolean locked;
|
||||
|
||||
private FeedItem selectedItem;
|
||||
private ViewHolder currentlyPlayingItem = null;
|
||||
|
||||
private final int playingBackGroundColor;
|
||||
private final int normalBackGroundColor;
|
||||
@ -94,6 +97,18 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int pos, List<Object> payload) {
|
||||
onBindViewHolder(holder, pos);
|
||||
|
||||
if (holder == currentlyPlayingItem && payload.size() == 1 && payload.get(0) instanceof PlaybackPositionEvent) {
|
||||
PlaybackPositionEvent event = (PlaybackPositionEvent) payload.get(0);
|
||||
holder.progressBar.setProgress((int) (100.0 * event.getPosition() / event.getDuration()));
|
||||
holder.progressLeft.setText(Converter.getDurationStringLong(event.getPosition()));
|
||||
holder.progressRight.setText(Converter.getDurationStringLong(event.getDuration()));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FeedItem getSelectedItem() {
|
||||
return selectedItem;
|
||||
@ -109,6 +124,12 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap
|
||||
return itemAccess.getCount();
|
||||
}
|
||||
|
||||
public void notifyCurrentlyPlayingItemChanged(PlaybackPositionEvent event) {
|
||||
if (currentlyPlayingItem != null && currentlyPlayingItem.getAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
notifyItemChanged(currentlyPlayingItem.getAdapterPosition(), event);
|
||||
}
|
||||
}
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder
|
||||
implements View.OnClickListener,
|
||||
View.OnCreateContextMenuListener,
|
||||
@ -169,7 +190,8 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap
|
||||
FeedItem item = itemAccess.getItem(getAdapterPosition());
|
||||
|
||||
MenuInflater inflater = mainActivity.get().getMenuInflater();
|
||||
inflater.inflate(R.menu.queue_context, menu);
|
||||
inflater.inflate(R.menu.queue_context, menu); // queue-specific menu items
|
||||
inflater.inflate(R.menu.feeditemlist_context, menu); // generic menu items for item feeds
|
||||
|
||||
if (item != null) {
|
||||
menu.setHeaderTitle(item.getTitle());
|
||||
@ -184,7 +206,18 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap
|
||||
item1.setVisible(visible);
|
||||
}
|
||||
};
|
||||
FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, itemAccess.getQueueIds());
|
||||
|
||||
FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item,
|
||||
R.id.skip_episode_item); // Skip Episode is not useful in Queue, so hide it.
|
||||
// Queue-specific menu preparation
|
||||
final boolean keepSorted = UserPreferences.isQueueKeepSorted();
|
||||
final LongList queueAccess = itemAccess.getQueueIds();
|
||||
if (queueAccess.size() == 0 || queueAccess.get(0) == item.getId() || keepSorted) {
|
||||
contextMenuInterface.setItemVisibility(R.id.move_to_top_item, false);
|
||||
}
|
||||
if (queueAccess.size() == 0 || queueAccess.get(queueAccess.size()-1) == item.getId() || keepSorted) {
|
||||
contextMenuInterface.setItemVisibility(R.id.move_to_bottom_item, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -276,6 +309,7 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap
|
||||
|
||||
if(media.isCurrentlyPlaying()) {
|
||||
container.setBackgroundColor(playingBackGroundColor);
|
||||
currentlyPlayingItem = this;
|
||||
} else {
|
||||
container.setBackgroundColor(normalBackGroundColor);
|
||||
}
|
||||
|
@ -14,13 +14,11 @@ import com.bumptech.glide.Glide;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
||||
import de.danoeh.antennapod.fragment.AddFeedFragment;
|
||||
import de.danoeh.antennapod.fragment.ItemlistFragment;
|
||||
import de.danoeh.antennapod.fragment.FeedItemlistFragment;
|
||||
import jp.shts.android.library.TriangleLabelView;
|
||||
|
||||
/**
|
||||
@ -142,7 +140,7 @@ public class SubscriptionsAdapter extends BaseAdapter implements AdapterView.OnI
|
||||
if (position == getAddTilePosition()) {
|
||||
mainActivityRef.get().loadChildFragment(new AddFeedFragment());
|
||||
} else {
|
||||
Fragment fragment = ItemlistFragment.newInstance(getItemId(position));
|
||||
Fragment fragment = FeedItemlistFragment.newInstance(getItemId(position));
|
||||
mainActivityRef.get().loadChildFragment(fragment);
|
||||
}
|
||||
}
|
||||
|
@ -20,12 +20,12 @@ public abstract class ItemActionButton {
|
||||
}
|
||||
|
||||
@StringRes
|
||||
abstract public int getLabel();
|
||||
public abstract int getLabel();
|
||||
|
||||
@AttrRes
|
||||
abstract public int getDrawable();
|
||||
public abstract int getDrawable();
|
||||
|
||||
abstract public void onClick(Context context);
|
||||
public abstract void onClick(Context context);
|
||||
|
||||
public int getVisibility() {
|
||||
return View.VISIBLE;
|
||||
|
@ -0,0 +1,72 @@
|
||||
package de.danoeh.antennapod.asynctask;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.provider.DocumentFile;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
import de.danoeh.antennapod.core.export.ExportWriter;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.util.LangUtils;
|
||||
import io.reactivex.Observable;
|
||||
|
||||
/**
|
||||
* Writes an OPML file into the user selected export directory in the background.
|
||||
*/
|
||||
public class DocumentFileExportWorker {
|
||||
|
||||
private final @NonNull ExportWriter exportWriter;
|
||||
private @NonNull Context context;
|
||||
private @NonNull Uri outputFileUri;
|
||||
|
||||
public DocumentFileExportWorker(@NonNull ExportWriter exportWriter, @NonNull Context context, @NonNull Uri outputFileUri) {
|
||||
this.exportWriter = exportWriter;
|
||||
this.context = context;
|
||||
this.outputFileUri = outputFileUri;
|
||||
}
|
||||
|
||||
public Observable<DocumentFile> exportObservable() {
|
||||
DocumentFile output = DocumentFile.fromSingleUri(context, outputFileUri);
|
||||
return Observable.create(subscriber -> {
|
||||
OutputStream outputStream = null;
|
||||
OutputStreamWriter writer = null;
|
||||
try {
|
||||
Uri uri = output.getUri();
|
||||
if (uri == null) {
|
||||
throw new FileNotFoundException("Export file not found.");
|
||||
}
|
||||
outputStream = context.getContentResolver().openOutputStream(uri);
|
||||
if (outputStream == null) {
|
||||
throw new IOException();
|
||||
}
|
||||
writer = new OutputStreamWriter(outputStream, LangUtils.UTF_8);
|
||||
exportWriter.writeDocument(DBReader.getFeedList(), writer);
|
||||
subscriber.onNext(output);
|
||||
} catch (IOException e) {
|
||||
subscriber.onError(e);
|
||||
} finally {
|
||||
if (writer != null) {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
subscriber.onError(e);
|
||||
}
|
||||
}
|
||||
if (outputStream != null) {
|
||||
try {
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
subscriber.onError(e);
|
||||
}
|
||||
}
|
||||
subscriber.onComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -35,6 +35,6 @@ public class PlaybackServiceCallbacksImpl implements PlaybackServiceCallbacks {
|
||||
|
||||
@Override
|
||||
public int getNotificationIconResource(Context context) {
|
||||
return R.drawable.ic_stat_antenna_default;
|
||||
return R.drawable.ic_antenna;
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import de.danoeh.antennapod.adapter.DataFolderAdapter;
|
||||
|
||||
public class ChooseDataFolderDialog {
|
||||
|
||||
public static abstract class RunnableWithString implements Runnable {
|
||||
public abstract static class RunnableWithString implements Runnable {
|
||||
public RunnableWithString() {
|
||||
super();
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package de.danoeh.antennapod.dialog;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.IdRes;
|
||||
import android.support.annotation.NonNull;
|
||||
@ -13,7 +12,6 @@ import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.util.ArrayMap;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
@ -168,7 +166,8 @@ public class EpisodesApplyActionFragment extends Fragment {
|
||||
return true;
|
||||
});
|
||||
|
||||
for(FeedItem episode : episodes) {
|
||||
titles.clear();
|
||||
for (FeedItem episode : episodes) {
|
||||
titles.add(episode.getTitle());
|
||||
}
|
||||
|
||||
@ -205,10 +204,6 @@ public class EpisodesApplyActionFragment extends Fragment {
|
||||
return true;
|
||||
});
|
||||
|
||||
if (Build.VERSION.SDK_INT == 23 || Build.VERSION.SDK_INT == 24) {
|
||||
ViewCompat.setElevation(view.findViewById(R.id.fabSDScrollCtr), 8);
|
||||
}
|
||||
|
||||
showSpeedDialIfAnyChecked();
|
||||
|
||||
return view;
|
||||
@ -221,7 +216,13 @@ public class EpisodesApplyActionFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void showSpeedDialIfAnyChecked() {
|
||||
mSpeedDialView.setVisibility(checkedIds.size() > 0 ? View.VISIBLE : View.GONE);
|
||||
if (checkedIds.size() > 0) {
|
||||
if (!mSpeedDialView.isShown()) {
|
||||
mSpeedDialView.show();
|
||||
}
|
||||
} else {
|
||||
mSpeedDialView.hide(); // hide() also handles UI, e.g., overlay properly.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -245,10 +246,13 @@ public class EpisodesApplyActionFragment extends Fragment {
|
||||
// Prepare icon for select toggle button
|
||||
|
||||
int[] icon = new int[1];
|
||||
@StringRes int titleResId;
|
||||
if (checkedIds.size() == episodes.size()) {
|
||||
icon[0] = R.attr.ic_select_none;
|
||||
titleResId = R.string.deselect_all_label;
|
||||
} else {
|
||||
icon[0] = R.attr.ic_select_all;
|
||||
titleResId = R.string.select_all_label;
|
||||
}
|
||||
|
||||
TypedArray a = getActivity().obtainStyledAttributes(icon);
|
||||
@ -256,6 +260,7 @@ public class EpisodesApplyActionFragment extends Fragment {
|
||||
a.recycle();
|
||||
|
||||
mSelectToggle.setIcon(iconDrawable);
|
||||
mSelectToggle.setTitle(titleResId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,62 @@
|
||||
package de.danoeh.antennapod.dialog;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.feed.FeedItemFilter;
|
||||
|
||||
public abstract class FilterDialog {
|
||||
|
||||
protected FeedItemFilter filter;
|
||||
protected Context context;
|
||||
|
||||
public FilterDialog(Context context, FeedItemFilter feedItemFilter) {
|
||||
this.context = context;
|
||||
this.filter = feedItemFilter;
|
||||
}
|
||||
|
||||
public void openDialog() {
|
||||
final String[] items = context.getResources().getStringArray(R.array.episode_filter_options);
|
||||
final String[] values = context.getResources().getStringArray(R.array.episode_filter_values);
|
||||
final boolean[] checkedItems = new boolean[items.length];
|
||||
|
||||
final Set<String> filterValues = new HashSet<>(Arrays.asList(filter.getValues()));
|
||||
|
||||
// make sure we have no empty strings in the filter list
|
||||
for (String filterValue : filterValues) {
|
||||
if (TextUtils.isEmpty(filterValue)) {
|
||||
filterValues.remove(filterValue);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
String value = values[i];
|
||||
if (filterValues.contains(value)) {
|
||||
checkedItems[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(R.string.filter);
|
||||
builder.setMultiChoiceItems(items, checkedItems, (dialog, which, isChecked) -> {
|
||||
if (isChecked) {
|
||||
filterValues.add(values[which]);
|
||||
} else {
|
||||
filterValues.remove(values[which]);
|
||||
}
|
||||
});
|
||||
builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
|
||||
updateFilter(filterValues);
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel_label, null);
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
protected abstract void updateFilter(Set<String> filterValues);
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
package de.danoeh.antennapod.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.util.Converter;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class PlaybackControlsDialog extends DialogFragment {
|
||||
private static final float PLAYBACK_SPEED_STEP = 0.05f;
|
||||
private static final float DEFAULT_MIN_PLAYBACK_SPEED = 0.5f;
|
||||
private static final float DEFAULT_MAX_PLAYBACK_SPEED = 2.5f;
|
||||
private static final String ARGUMENT_IS_PLAYING_VIDEO = "isPlayingVideo";
|
||||
|
||||
private PlaybackController controller;
|
||||
private MaterialDialog dialog;
|
||||
private boolean isPlayingVideo;
|
||||
|
||||
public static PlaybackControlsDialog newInstance(boolean isPlayingVideo) {
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putBoolean(ARGUMENT_IS_PLAYING_VIDEO, isPlayingVideo);
|
||||
PlaybackControlsDialog dialog = new PlaybackControlsDialog();
|
||||
dialog.setArguments(arguments);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public PlaybackControlsDialog() {
|
||||
// Empty constructor required for DialogFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
controller = new PlaybackController(getActivity(), false);
|
||||
controller.init();
|
||||
setupUi();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
controller.release();
|
||||
controller = null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
isPlayingVideo = getArguments() != null && getArguments().getBoolean(ARGUMENT_IS_PLAYING_VIDEO);
|
||||
|
||||
dialog = new MaterialDialog.Builder(getContext())
|
||||
.title(R.string.audio_controls)
|
||||
.customView(R.layout.audio_controls, true)
|
||||
.neutralText(R.string.close_label)
|
||||
.onNeutral((dialog1, which) -> {
|
||||
final SeekBar left = (SeekBar) dialog1.findViewById(R.id.volume_left);
|
||||
final SeekBar right = (SeekBar) dialog1.findViewById(R.id.volume_right);
|
||||
UserPreferences.setVolume(left.getProgress(), right.getProgress());
|
||||
}).build();
|
||||
return dialog;
|
||||
}
|
||||
|
||||
private void setupUi() {
|
||||
final SeekBar barPlaybackSpeed = (SeekBar) dialog.findViewById(R.id.playback_speed);
|
||||
final Button butDecSpeed = (Button) dialog.findViewById(R.id.butDecSpeed);
|
||||
butDecSpeed.setOnClickListener(v -> {
|
||||
if (controller != null && controller.canSetPlaybackSpeed()) {
|
||||
barPlaybackSpeed.setProgress(barPlaybackSpeed.getProgress() - 1);
|
||||
} else {
|
||||
VariableSpeedDialog.showGetPluginDialog(getContext());
|
||||
}
|
||||
});
|
||||
final Button butIncSpeed = (Button) dialog.findViewById(R.id.butIncSpeed);
|
||||
butIncSpeed.setOnClickListener(v -> {
|
||||
if (controller != null && controller.canSetPlaybackSpeed()) {
|
||||
barPlaybackSpeed.setProgress(barPlaybackSpeed.getProgress() + 1);
|
||||
} else {
|
||||
VariableSpeedDialog.showGetPluginDialog(getContext());
|
||||
}
|
||||
});
|
||||
|
||||
final TextView txtvPlaybackSpeed = (TextView) dialog.findViewById(R.id.txtvPlaybackSpeed);
|
||||
float currentSpeed = getCurrentSpeed();
|
||||
|
||||
String[] availableSpeeds = UserPreferences.getPlaybackSpeedArray();
|
||||
final float minPlaybackSpeed = availableSpeeds.length > 1 ?
|
||||
Float.valueOf(availableSpeeds[0]) : DEFAULT_MIN_PLAYBACK_SPEED;
|
||||
float maxPlaybackSpeed = availableSpeeds.length > 1 ?
|
||||
Float.valueOf(availableSpeeds[availableSpeeds.length - 1]) : DEFAULT_MAX_PLAYBACK_SPEED;
|
||||
int progressMax = (int) ((maxPlaybackSpeed - minPlaybackSpeed) / PLAYBACK_SPEED_STEP);
|
||||
barPlaybackSpeed.setMax(progressMax);
|
||||
|
||||
txtvPlaybackSpeed.setText(String.format("%.2fx", currentSpeed));
|
||||
barPlaybackSpeed.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
if (controller != null && controller.canSetPlaybackSpeed()) {
|
||||
float playbackSpeed = progress * PLAYBACK_SPEED_STEP + minPlaybackSpeed;
|
||||
controller.setPlaybackSpeed(playbackSpeed);
|
||||
String speedPref = String.format(Locale.US, "%.2f", playbackSpeed);
|
||||
|
||||
if (isPlayingVideo) {
|
||||
UserPreferences.setVideoPlaybackSpeed(speedPref);
|
||||
} else {
|
||||
UserPreferences.setPlaybackSpeed(speedPref);
|
||||
}
|
||||
|
||||
String speedStr = String.format("%.2fx", playbackSpeed);
|
||||
txtvPlaybackSpeed.setText(speedStr);
|
||||
} else if (fromUser) {
|
||||
float speed = getCurrentSpeed();
|
||||
barPlaybackSpeed.post(() -> barPlaybackSpeed.setProgress(
|
||||
(int) ((speed - minPlaybackSpeed) / PLAYBACK_SPEED_STEP)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
if (controller != null && !controller.canSetPlaybackSpeed()) {
|
||||
VariableSpeedDialog.showGetPluginDialog(getContext());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
});
|
||||
barPlaybackSpeed.setProgress((int) ((currentSpeed - minPlaybackSpeed) / PLAYBACK_SPEED_STEP));
|
||||
|
||||
final SeekBar barLeftVolume = (SeekBar) dialog.findViewById(R.id.volume_left);
|
||||
barLeftVolume.setProgress(UserPreferences.getLeftVolumePercentage());
|
||||
final SeekBar barRightVolume = (SeekBar) dialog.findViewById(R.id.volume_right);
|
||||
barRightVolume.setProgress(UserPreferences.getRightVolumePercentage());
|
||||
final CheckBox stereoToMono = (CheckBox) dialog.findViewById(R.id.stereo_to_mono);
|
||||
stereoToMono.setChecked(UserPreferences.stereoToMono());
|
||||
if (controller != null && !controller.canDownmix()) {
|
||||
stereoToMono.setEnabled(false);
|
||||
String sonicOnly = getString(R.string.sonic_only);
|
||||
stereoToMono.setText(stereoToMono.getText() + " [" + sonicOnly + "]");
|
||||
}
|
||||
|
||||
if (UserPreferences.useExoplayer()) {
|
||||
barRightVolume.setEnabled(false);
|
||||
}
|
||||
|
||||
final CheckBox skipSilence = (CheckBox) dialog.findViewById(R.id.skipSilence);
|
||||
skipSilence.setChecked(UserPreferences.isSkipSilence());
|
||||
if (!UserPreferences.useExoplayer()) {
|
||||
skipSilence.setEnabled(false);
|
||||
String exoplayerOnly = getString(R.string.exoplayer_only);
|
||||
skipSilence.setText(skipSilence.getText() + " [" + exoplayerOnly + "]");
|
||||
}
|
||||
skipSilence.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
UserPreferences.setSkipSilence(isChecked);
|
||||
controller.setSkipSilence(isChecked);
|
||||
});
|
||||
|
||||
barLeftVolume.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
controller.setVolume(
|
||||
Converter.getVolumeFromPercentage(progress),
|
||||
Converter.getVolumeFromPercentage(barRightVolume.getProgress()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
});
|
||||
barRightVolume.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
controller.setVolume(
|
||||
Converter.getVolumeFromPercentage(barLeftVolume.getProgress()),
|
||||
Converter.getVolumeFromPercentage(progress));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
});
|
||||
stereoToMono.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
UserPreferences.stereoToMono(isChecked);
|
||||
if (controller != null) {
|
||||
controller.setDownmix(isChecked);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private float getCurrentSpeed() {
|
||||
if (isPlayingVideo) {
|
||||
return UserPreferences.getVideoPlaybackSpeed();
|
||||
}
|
||||
return UserPreferences.getPlaybackSpeed();
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
|
||||
public class RatingDialog {
|
||||
|
||||
@ -59,14 +60,10 @@ public class RatingDialog {
|
||||
|
||||
private static void rateNow() {
|
||||
Context context = mContext.get();
|
||||
if(context == null) {
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
final String appPackage = "de.danoeh.antennapod";
|
||||
final Uri uri = Uri.parse("https://play.google.com/store/apps/details?id=" + appPackage);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
IntentUtils.openInBrowser(context, "https://play.google.com/store/apps/details?id=de.danoeh.antennapod");
|
||||
saveRated();
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,7 @@ public class VariableSpeedDialog {
|
||||
public static void showDialog(final Context context) {
|
||||
if (org.antennapod.audio.MediaPlayer.isPrestoLibraryInstalled(context)
|
||||
|| UserPreferences.useSonic()
|
||||
|| UserPreferences.useExoplayer()
|
||||
|| Build.VERSION.SDK_INT >= 23) {
|
||||
showSpeedSelectorDialog(context);
|
||||
} else {
|
||||
|
@ -24,7 +24,7 @@ public class CombinedSearcher implements PodcastSearcher {
|
||||
public CombinedSearcher(Context context) {
|
||||
addProvider(new FyydPodcastSearcher(), 1.f);
|
||||
addProvider(new ItunesPodcastSearcher(context), 1.f);
|
||||
addProvider(new GpodnetPodcastSearcher(), 0.6f);
|
||||
//addProvider(new GpodnetPodcastSearcher(), 0.6f);
|
||||
}
|
||||
|
||||
private void addProvider(PodcastSearcher provider, float priority) {
|
||||
|
@ -33,16 +33,12 @@ public class ItunesTopListLoader {
|
||||
OkHttpClient client = AntennapodHttpClient.getHttpClient();
|
||||
String feedString;
|
||||
try {
|
||||
try {
|
||||
feedString = getTopListFeed(client, lang, limit);
|
||||
} catch (IOException e) {
|
||||
feedString = getTopListFeed(client, "us", limit);
|
||||
}
|
||||
List<PodcastSearchResult> podcasts = parseFeed(feedString);
|
||||
emitter.onSuccess(podcasts);
|
||||
} catch (IOException | JSONException e) {
|
||||
emitter.onError(e);
|
||||
feedString = getTopListFeed(client, lang, limit);
|
||||
} catch (IOException e) {
|
||||
feedString = getTopListFeed(client, "us", limit);
|
||||
}
|
||||
List<PodcastSearchResult> podcasts = parseFeed(feedString);
|
||||
emitter.onSuccess(podcasts);
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
|
@ -70,11 +70,8 @@ public class AddFeedFragment extends Fragment {
|
||||
|
||||
combinedFeedSearchBox = root.findViewById(R.id.combinedFeedSearchBox);
|
||||
combinedFeedSearchBox.setOnEditorActionListener((v, actionId, event) -> {
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
||||
performSearch();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
performSearch();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,9 @@
|
||||
package de.danoeh.antennapod.fragment;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.support.v7.widget.SimpleItemAnimator;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@ -20,231 +11,50 @@ import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.joanzapata.iconify.Iconify;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.adapter.AllEpisodesRecycleAdapter;
|
||||
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
|
||||
import de.danoeh.antennapod.core.event.DownloadEvent;
|
||||
import de.danoeh.antennapod.core.event.DownloaderUpdate;
|
||||
import de.danoeh.antennapod.core.event.FeedItemEvent;
|
||||
import de.danoeh.antennapod.core.feed.EventDistributor;
|
||||
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.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadRequest;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadService;
|
||||
import de.danoeh.antennapod.core.service.download.Downloader;
|
||||
import de.danoeh.antennapod.core.feed.FeedItemFilter;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||
import de.danoeh.antennapod.core.util.FeedItemUtil;
|
||||
import de.danoeh.antennapod.core.util.LongList;
|
||||
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
|
||||
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
|
||||
import de.danoeh.antennapod.view.EmptyViewHandler;
|
||||
import de.danoeh.antennapod.dialog.FilterDialog;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Shows unread or recently published episodes
|
||||
* Like 'EpisodesFragment' except that it only shows new episodes and
|
||||
* supports swiping to mark as read.
|
||||
*/
|
||||
public class AllEpisodesFragment extends Fragment {
|
||||
public class AllEpisodesFragment extends EpisodesListFragment {
|
||||
|
||||
public static final String TAG = "AllEpisodesFragment";
|
||||
private static final String PREF_NAME = "PrefAllEpisodesFragment";
|
||||
|
||||
private static final int EVENTS = EventDistributor.FEED_LIST_UPDATE |
|
||||
EventDistributor.UNREAD_ITEMS_UPDATE |
|
||||
EventDistributor.PLAYER_STATUS_UPDATE;
|
||||
private static final int EPISODES_PER_PAGE = 150;
|
||||
private static final int VISIBLE_EPISODES_SCROLL_THRESHOLD = 5;
|
||||
private static int page = 1;
|
||||
|
||||
private static final int RECENT_EPISODES_LIMIT = 150;
|
||||
private static final String DEFAULT_PREF_NAME = "PrefAllEpisodesFragment";
|
||||
private static final String PREF_SCROLL_POSITION = "scroll_position";
|
||||
private static final String PREF_SCROLL_OFFSET = "scroll_offset";
|
||||
private static FeedItemFilter feedItemFilter = new FeedItemFilter("");
|
||||
|
||||
RecyclerView recyclerView;
|
||||
AllEpisodesRecycleAdapter listAdapter;
|
||||
private ProgressBar progLoading;
|
||||
EmptyViewHandler emptyView;
|
||||
|
||||
@NonNull
|
||||
List<FeedItem> episodes = new ArrayList<>();
|
||||
@NonNull
|
||||
private List<Downloader> downloaderList = new ArrayList<>();
|
||||
|
||||
private boolean isUpdatingFeeds;
|
||||
boolean isMenuInvalidationAllowed = false;
|
||||
|
||||
Disposable disposable;
|
||||
private LinearLayoutManager layoutManager;
|
||||
|
||||
boolean showOnlyNewEpisodes() {
|
||||
@Override
|
||||
protected boolean showOnlyNewEpisodes() {
|
||||
return false;
|
||||
}
|
||||
|
||||
String getPrefName() {
|
||||
return DEFAULT_PREF_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
setHasOptionsMenu(true);
|
||||
EventDistributor.getInstance().register(contentUpdate);
|
||||
EventBus.getDefault().register(this);
|
||||
loadItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
registerForContextMenu(recyclerView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
saveScrollPosition();
|
||||
unregisterForContextMenu(recyclerView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
EventBus.getDefault().unregister(this);
|
||||
EventDistributor.getInstance().unregister(contentUpdate);
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void saveScrollPosition() {
|
||||
int firstItem = layoutManager.findFirstVisibleItemPosition();
|
||||
View firstItemView = layoutManager.findViewByPosition(firstItem);
|
||||
float topOffset;
|
||||
if (firstItemView == null) {
|
||||
topOffset = 0;
|
||||
} else {
|
||||
topOffset = firstItemView.getTop();
|
||||
}
|
||||
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences(getPrefName(), Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(PREF_SCROLL_POSITION, firstItem);
|
||||
editor.putFloat(PREF_SCROLL_OFFSET, topOffset);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
private void restoreScrollPosition() {
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences(getPrefName(), Context.MODE_PRIVATE);
|
||||
int position = prefs.getInt(PREF_SCROLL_POSITION, 0);
|
||||
float offset = prefs.getFloat(PREF_SCROLL_OFFSET, 0.0f);
|
||||
if (position > 0 || offset > 0) {
|
||||
layoutManager.scrollToPositionWithOffset(position, (int) offset);
|
||||
// restore once, then forget
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(PREF_SCROLL_POSITION, 0);
|
||||
editor.putFloat(PREF_SCROLL_OFFSET, 0.0f);
|
||||
editor.commit();
|
||||
}
|
||||
}
|
||||
|
||||
private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker =
|
||||
() -> DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds();
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
if (!isAdded()) {
|
||||
return;
|
||||
}
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.episodes, menu);
|
||||
|
||||
MenuItem searchItem = menu.findItem(R.id.action_search);
|
||||
final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem);
|
||||
MenuItemUtils.adjustTextColor(getActivity(), sv);
|
||||
sv.setQueryHint(getString(R.string.search_hint));
|
||||
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String s) {
|
||||
sv.clearFocus();
|
||||
((MainActivity) requireActivity()).loadChildFragment(SearchFragment.newInstance(s));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String s) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
MenuItem markAllRead = menu.findItem(R.id.mark_all_read_item);
|
||||
if (markAllRead != null) {
|
||||
markAllRead.setVisible(!showOnlyNewEpisodes() && !episodes.isEmpty());
|
||||
}
|
||||
MenuItem removeAllNewFlags = menu.findItem(R.id.remove_all_new_flags_item);
|
||||
if (removeAllNewFlags != null) {
|
||||
removeAllNewFlags.setVisible(showOnlyNewEpisodes() && !episodes.isEmpty());
|
||||
}
|
||||
protected String getPrefName() {
|
||||
return PREF_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (!super.onOptionsItemSelected(item)) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.refresh_item:
|
||||
List<Feed> feeds = ((MainActivity) getActivity()).getFeeds();
|
||||
if (feeds != null) {
|
||||
DBTasks.refreshAllFeeds(getActivity(), feeds);
|
||||
}
|
||||
return true;
|
||||
case R.id.mark_all_read_item:
|
||||
ConfirmationDialog markAllReadConfirmationDialog = new ConfirmationDialog(getActivity(),
|
||||
R.string.mark_all_read_label,
|
||||
R.string.mark_all_read_confirmation_msg) {
|
||||
|
||||
@Override
|
||||
public void onConfirmButtonPressed(DialogInterface dialog) {
|
||||
dialog.dismiss();
|
||||
DBWriter.markAllItemsRead();
|
||||
Toast.makeText(getActivity(), R.string.mark_all_read_msg, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
};
|
||||
markAllReadConfirmationDialog.createNewDialog().show();
|
||||
return true;
|
||||
case R.id.remove_all_new_flags_item:
|
||||
ConfirmationDialog removeAllNewFlagsConfirmationDialog = new ConfirmationDialog(getActivity(),
|
||||
R.string.remove_all_new_flags_label,
|
||||
R.string.remove_all_new_flags_confirmation_msg) {
|
||||
|
||||
@Override
|
||||
public void onConfirmButtonPressed(DialogInterface dialog) {
|
||||
dialog.dismiss();
|
||||
DBWriter.removeAllNewFlags();
|
||||
Toast.makeText(getActivity(), R.string.removed_all_new_flags_msg, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
};
|
||||
removeAllNewFlagsConfirmationDialog.createNewDialog().show();
|
||||
case R.id.filter_items:
|
||||
showFilterDialog();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@ -252,250 +62,105 @@ public class AllEpisodesFragment extends Fragment {
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
Log.d(TAG, "onContextItemSelected() called with: " + "item = [" + item + "]");
|
||||
if (!getUserVisibleHint()) {
|
||||
return false;
|
||||
}
|
||||
if (!isVisible()) {
|
||||
return false;
|
||||
}
|
||||
if (item.getItemId() == R.id.share_item) {
|
||||
return true; // avoids that the position is reset when we need it in the submenu
|
||||
}
|
||||
|
||||
if (listAdapter.getSelectedItem() == null) {
|
||||
Log.i(TAG, "Selected item or listAdapter was null, ignoring selection");
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
FeedItem selectedItem = listAdapter.getSelectedItem();
|
||||
|
||||
// Remove new flag contains UI logic specific to All/New/FavoriteSegments,
|
||||
// e.g., Undo with Snackbar,
|
||||
// and is handled by this class rather than the generic FeedItemMenuHandler
|
||||
// Undo is useful for Remove new flag, given there is no UI to undo it otherwise,
|
||||
// i.e., there is context menu item for Mark as new
|
||||
if (R.id.remove_new_flag_item == item.getItemId()) {
|
||||
removeNewFlagWithUndo(selectedItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
View root = inflater.inflate(R.layout.all_episodes_fragment, container, false);
|
||||
View root = super.onCreateView(inflater, container, savedInstanceState);
|
||||
|
||||
layoutManager = new LinearLayoutManager(getActivity());
|
||||
recyclerView = root.findViewById(android.R.id.list);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
recyclerView.setHasFixedSize(true);
|
||||
recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build());
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
|
||||
RecyclerView.ItemAnimator animator = recyclerView.getItemAnimator();
|
||||
if (animator instanceof SimpleItemAnimator) {
|
||||
((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
|
||||
}
|
||||
/* Total number of episodes after last load */
|
||||
private int previousTotalEpisodes = 0;
|
||||
|
||||
progLoading = root.findViewById(R.id.progLoading);
|
||||
progLoading.setVisibility(View.VISIBLE);
|
||||
/* True if loading more episodes is still in progress */
|
||||
private boolean isLoadingMore = true;
|
||||
|
||||
emptyView = new EmptyViewHandler(getContext());
|
||||
emptyView.attachToRecyclerView(recyclerView);
|
||||
emptyView.setIcon(R.attr.feed);
|
||||
emptyView.setTitle(R.string.no_all_episodes_head_label);
|
||||
emptyView.setMessage(R.string.no_all_episodes_label);
|
||||
@Override
|
||||
public void onScrolled(RecyclerView recyclerView, int deltaX, int deltaY) {
|
||||
super.onScrolled(recyclerView, deltaX, deltaY);
|
||||
|
||||
createRecycleAdapter(recyclerView, emptyView);
|
||||
emptyView.hide();
|
||||
int visibleEpisodeCount = recyclerView.getChildCount();
|
||||
int totalEpisodeCount = recyclerView.getLayoutManager().getItemCount();
|
||||
int firstVisibleEpisode = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
|
||||
|
||||
/* Determine if loading more episodes has finished */
|
||||
if (isLoadingMore) {
|
||||
if (totalEpisodeCount > previousTotalEpisodes) {
|
||||
isLoadingMore = false;
|
||||
previousTotalEpisodes = totalEpisodeCount;
|
||||
}
|
||||
}
|
||||
|
||||
/* Determine if the user scrolled to the bottom and loading more episodes is not already in progress */
|
||||
if (!isLoadingMore && (totalEpisodeCount - visibleEpisodeCount)
|
||||
<= (firstVisibleEpisode + VISIBLE_EPISODES_SCROLL_THRESHOLD)) {
|
||||
|
||||
/* The end of the list has been reached. Load more data. */
|
||||
page++;
|
||||
loadMoreItems();
|
||||
isLoadingMore = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void onFragmentLoaded(List<FeedItem> episodes) {
|
||||
this.episodes = episodes;
|
||||
listAdapter.notifyDataSetChanged();
|
||||
|
||||
if (episodes.size() == 0) {
|
||||
createRecycleAdapter(recyclerView, emptyView);
|
||||
}
|
||||
|
||||
restoreScrollPosition();
|
||||
requireActivity().invalidateOptionsMenu();
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
menu.findItem(R.id.filter_items).setVisible(true);
|
||||
menu.findItem(R.id.mark_all_read_item).setVisible(!episodes.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently, we need to recreate the list adapter in order to be able to undo last item via the
|
||||
* snackbar. See #3084 for details.
|
||||
*/
|
||||
private void createRecycleAdapter(RecyclerView recyclerView, EmptyViewHandler emptyViewHandler) {
|
||||
MainActivity mainActivity = (MainActivity) getActivity();
|
||||
listAdapter = new AllEpisodesRecycleAdapter(mainActivity, itemAccess, showOnlyNewEpisodes());
|
||||
listAdapter.setHasStableIds(true);
|
||||
recyclerView.setAdapter(listAdapter);
|
||||
emptyViewHandler.updateAdapter(listAdapter);
|
||||
}
|
||||
@Override
|
||||
protected void onFragmentLoaded(List<FeedItem> episodes) {
|
||||
super.onFragmentLoaded(episodes);
|
||||
|
||||
private final AllEpisodesRecycleAdapter.ItemAccess itemAccess = new AllEpisodesRecycleAdapter.ItemAccess() {
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return episodes.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeedItem getItem(int position) {
|
||||
if (0 <= position && position < episodes.size()) {
|
||||
return episodes.get(position);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LongList getItemsIds() {
|
||||
LongList ids = new LongList(episodes.size());
|
||||
for (FeedItem episode : episodes) {
|
||||
ids.add(episode.getId());
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemDownloadProgressPercent(FeedItem item) {
|
||||
for (Downloader downloader : downloaderList) {
|
||||
DownloadRequest downloadRequest = downloader.getDownloadRequest();
|
||||
if (downloadRequest.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA
|
||||
&& downloadRequest.getFeedfileId() == item.getMedia().getId()) {
|
||||
return downloadRequest.getProgressPercent();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInQueue(FeedItem item) {
|
||||
return item != null && item.isTagged(FeedItem.TAG_QUEUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LongList getQueueIds() {
|
||||
LongList queueIds = new LongList();
|
||||
for (FeedItem item : episodes) {
|
||||
if (item.isTagged(FeedItem.TAG_QUEUE)) {
|
||||
queueIds.add(item.getId());
|
||||
}
|
||||
}
|
||||
return queueIds;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(FeedItemEvent event) {
|
||||
Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]");
|
||||
for (FeedItem item : event.items) {
|
||||
int pos = FeedItemUtil.indexOfItemWithId(episodes, item.getId());
|
||||
if (pos >= 0) {
|
||||
episodes.remove(pos);
|
||||
if (shouldUpdatedItemRemainInList(item)) {
|
||||
episodes.add(pos, item);
|
||||
listAdapter.notifyItemChanged(pos);
|
||||
} else {
|
||||
listAdapter.notifyItemRemoved(pos);
|
||||
}
|
||||
}
|
||||
if (feedItemFilter.getValues().length > 0) {
|
||||
txtvInformation.setText("{fa-info-circle} " + this.getString(R.string.filtered_label));
|
||||
Iconify.addIcons(txtvInformation);
|
||||
txtvInformation.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
txtvInformation.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean shouldUpdatedItemRemainInList(FeedItem item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(DownloadEvent event) {
|
||||
Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]");
|
||||
DownloaderUpdate update = event.update;
|
||||
downloaderList = update.downloaders;
|
||||
if (isMenuInvalidationAllowed && isUpdatingFeeds != update.feedIds.length > 0) {
|
||||
requireActivity().invalidateOptionsMenu();
|
||||
}
|
||||
if (update.mediaIds.length > 0) {
|
||||
for (long mediaId : update.mediaIds) {
|
||||
int pos = FeedItemUtil.indexOfItemWithMediaId(episodes, mediaId);
|
||||
if (pos >= 0) {
|
||||
listAdapter.notifyItemChanged(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
|
||||
@Override
|
||||
public void update(EventDistributor eventDistributor, Integer arg) {
|
||||
if ((arg & EVENTS) != 0) {
|
||||
loadItems();
|
||||
if (isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) {
|
||||
requireActivity().invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void loadItems() {
|
||||
private void loadMoreItems() {
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
disposable = Observable.fromCallable(this::loadData)
|
||||
disposable = Observable.fromCallable(this::loadMoreData)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(data -> {
|
||||
progLoading.setVisibility(View.GONE);
|
||||
onFragmentLoaded(data);
|
||||
episodes.addAll(data);
|
||||
onFragmentLoaded(episodes);
|
||||
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
List<FeedItem> loadData() {
|
||||
return DBReader.getRecentlyPublishedEpisodes(RECENT_EPISODES_LIMIT);
|
||||
}
|
||||
|
||||
void removeNewFlagWithUndo(FeedItem item) {
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "removeNewFlagWithUndo(" + item.getId() + ")");
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
// we're marking it as unplayed since the user didn't actually play it
|
||||
// but they don't want it considered 'NEW' anymore
|
||||
DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId());
|
||||
|
||||
final Handler h = new Handler(getActivity().getMainLooper());
|
||||
final Runnable r = () -> {
|
||||
FeedMedia media = item.getMedia();
|
||||
if (media != null && media.hasAlmostEnded() && UserPreferences.isAutoDelete()) {
|
||||
DBWriter.deleteFeedMediaOfItem(getActivity(), media.getId());
|
||||
private void showFilterDialog() {
|
||||
FilterDialog filterDialog = new FilterDialog(getContext(), feedItemFilter) {
|
||||
@Override
|
||||
protected void updateFilter(Set<String> filterValues) {
|
||||
feedItemFilter = new FeedItemFilter(filterValues.toArray(new String[filterValues.size()]));
|
||||
loadItems();
|
||||
}
|
||||
};
|
||||
|
||||
Snackbar snackbar = Snackbar.make(getView(), getString(R.string.removed_new_flag_label),
|
||||
Snackbar.LENGTH_LONG);
|
||||
snackbar.setAction(getString(R.string.undo), v -> {
|
||||
DBWriter.markItemPlayed(FeedItem.NEW, item.getId());
|
||||
// don't forget to cancel the thing that's going to remove the media
|
||||
h.removeCallbacks(r);
|
||||
});
|
||||
snackbar.show();
|
||||
h.postDelayed(r, (int) Math.ceil(snackbar.getDuration() * 1.05f));
|
||||
filterDialog.openDialog();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected List<FeedItem> loadData() {
|
||||
return feedItemFilter.filter( DBReader.getRecentlyPublishedEpisodes(0, page * EPISODES_PER_PAGE));
|
||||
}
|
||||
|
||||
List<FeedItem> loadMoreData() {
|
||||
return feedItemFilter.filter( DBReader.getRecentlyPublishedEpisodes((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE));
|
||||
}
|
||||
}
|
||||
|
@ -87,11 +87,10 @@ public class ChaptersFragment extends ListFragment {
|
||||
controller = null;
|
||||
}
|
||||
|
||||
private void scrollTo(int position) {
|
||||
getListView().setSelection(position);
|
||||
}
|
||||
|
||||
private int getCurrentChapter(Playable media) {
|
||||
if (media == null || media.getChapters() == null || media.getChapters().size() == 0 || controller == null) {
|
||||
return -1;
|
||||
}
|
||||
int currentPosition = controller.getPosition();
|
||||
|
||||
List<Chapter> chapters = media.getChapters();
|
||||
@ -126,8 +125,10 @@ public class ChaptersFragment extends ListFragment {
|
||||
if (adapter != null) {
|
||||
adapter.setMedia(media);
|
||||
adapter.notifyDataSetChanged();
|
||||
if (media != null && media.getChapters() != null && media.getChapters().size() != 0) {
|
||||
scrollTo(getCurrentChapter(media));
|
||||
|
||||
int positionOfCurrentChapter = getCurrentChapter(media);
|
||||
if (positionOfCurrentChapter != -1) {
|
||||
getListView().setSelection(positionOfCurrentChapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ public class EpisodesFragment extends Fragment {
|
||||
public static class EpisodesPagerAdapter extends FragmentPagerAdapter {
|
||||
|
||||
private final Resources resources;
|
||||
private final AllEpisodesFragment[] fragments = {
|
||||
private final EpisodesListFragment[] fragments = {
|
||||
new NewEpisodesFragment(),
|
||||
new AllEpisodesFragment(),
|
||||
new FavoriteEpisodesFragment()
|
||||
|
@ -0,0 +1,445 @@
|
||||
package de.danoeh.antennapod.fragment;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.support.v7.widget.SimpleItemAnimator;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
|
||||
|
||||
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.adapter.AllEpisodesRecycleAdapter;
|
||||
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
|
||||
import de.danoeh.antennapod.core.event.DownloadEvent;
|
||||
import de.danoeh.antennapod.core.event.DownloaderUpdate;
|
||||
import de.danoeh.antennapod.core.event.FeedItemEvent;
|
||||
import de.danoeh.antennapod.core.feed.EventDistributor;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadRequest;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadService;
|
||||
import de.danoeh.antennapod.core.service.download.Downloader;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||
import de.danoeh.antennapod.core.util.FeedItemUtil;
|
||||
import de.danoeh.antennapod.core.util.LongList;
|
||||
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
|
||||
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
|
||||
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
|
||||
import de.danoeh.antennapod.view.EmptyViewHandler;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
/**
|
||||
* Shows unread or recently published episodes
|
||||
*/
|
||||
public abstract class EpisodesListFragment extends Fragment {
|
||||
|
||||
public static final String TAG = "EpisodesListFragment";
|
||||
|
||||
private static final int EVENTS = EventDistributor.FEED_LIST_UPDATE |
|
||||
EventDistributor.UNREAD_ITEMS_UPDATE |
|
||||
EventDistributor.PLAYER_STATUS_UPDATE;
|
||||
|
||||
private static final String DEFAULT_PREF_NAME = "PrefAllEpisodesFragment";
|
||||
private static final String PREF_SCROLL_POSITION = "scroll_position";
|
||||
private static final String PREF_SCROLL_OFFSET = "scroll_offset";
|
||||
|
||||
RecyclerView recyclerView;
|
||||
AllEpisodesRecycleAdapter listAdapter;
|
||||
ProgressBar progLoading;
|
||||
EmptyViewHandler emptyView;
|
||||
|
||||
@NonNull
|
||||
List<FeedItem> episodes = new ArrayList<>();
|
||||
@NonNull
|
||||
private List<Downloader> downloaderList = new ArrayList<>();
|
||||
|
||||
private boolean isUpdatingFeeds;
|
||||
boolean isMenuInvalidationAllowed = false;
|
||||
|
||||
protected Disposable disposable;
|
||||
private LinearLayoutManager layoutManager;
|
||||
protected TextView txtvInformation;
|
||||
|
||||
boolean showOnlyNewEpisodes() {
|
||||
return false;
|
||||
}
|
||||
|
||||
String getPrefName() {
|
||||
return DEFAULT_PREF_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
setHasOptionsMenu(true);
|
||||
EventDistributor.getInstance().register(contentUpdate);
|
||||
EventBus.getDefault().register(this);
|
||||
loadItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
registerForContextMenu(recyclerView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
saveScrollPosition();
|
||||
unregisterForContextMenu(recyclerView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
EventBus.getDefault().unregister(this);
|
||||
EventDistributor.getInstance().unregister(contentUpdate);
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void saveScrollPosition() {
|
||||
int firstItem = layoutManager.findFirstVisibleItemPosition();
|
||||
View firstItemView = layoutManager.findViewByPosition(firstItem);
|
||||
float topOffset;
|
||||
if (firstItemView == null) {
|
||||
topOffset = 0;
|
||||
} else {
|
||||
topOffset = firstItemView.getTop();
|
||||
}
|
||||
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences(getPrefName(), Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(PREF_SCROLL_POSITION, firstItem);
|
||||
editor.putFloat(PREF_SCROLL_OFFSET, topOffset);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
private void restoreScrollPosition() {
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences(getPrefName(), Context.MODE_PRIVATE);
|
||||
int position = prefs.getInt(PREF_SCROLL_POSITION, 0);
|
||||
float offset = prefs.getFloat(PREF_SCROLL_OFFSET, 0.0f);
|
||||
if (position > 0 || offset > 0) {
|
||||
layoutManager.scrollToPositionWithOffset(position, (int) offset);
|
||||
// restore once, then forget
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(PREF_SCROLL_POSITION, 0);
|
||||
editor.putFloat(PREF_SCROLL_OFFSET, 0.0f);
|
||||
editor.apply();
|
||||
}
|
||||
}
|
||||
|
||||
private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker =
|
||||
() -> DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds();
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
if (!isAdded()) {
|
||||
return;
|
||||
}
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.episodes, menu);
|
||||
|
||||
MenuItem searchItem = menu.findItem(R.id.action_search);
|
||||
final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem);
|
||||
MenuItemUtils.adjustTextColor(getActivity(), sv);
|
||||
sv.setQueryHint(getString(R.string.search_hint));
|
||||
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String s) {
|
||||
sv.clearFocus();
|
||||
((MainActivity) requireActivity()).loadChildFragment(SearchFragment.newInstance(s));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String s) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (!super.onOptionsItemSelected(item)) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.refresh_item:
|
||||
AutoUpdateManager.runImmediate(requireContext());
|
||||
return true;
|
||||
case R.id.mark_all_read_item:
|
||||
ConfirmationDialog markAllReadConfirmationDialog = new ConfirmationDialog(getActivity(),
|
||||
R.string.mark_all_read_label,
|
||||
R.string.mark_all_read_confirmation_msg) {
|
||||
|
||||
@Override
|
||||
public void onConfirmButtonPressed(DialogInterface dialog) {
|
||||
dialog.dismiss();
|
||||
DBWriter.markAllItemsRead();
|
||||
Toast.makeText(getActivity(), R.string.mark_all_read_msg, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
};
|
||||
markAllReadConfirmationDialog.createNewDialog().show();
|
||||
return true;
|
||||
case R.id.remove_all_new_flags_item:
|
||||
ConfirmationDialog removeAllNewFlagsConfirmationDialog = new ConfirmationDialog(getActivity(),
|
||||
R.string.remove_all_new_flags_label,
|
||||
R.string.remove_all_new_flags_confirmation_msg) {
|
||||
|
||||
@Override
|
||||
public void onConfirmButtonPressed(DialogInterface dialog) {
|
||||
dialog.dismiss();
|
||||
DBWriter.removeAllNewFlags();
|
||||
Toast.makeText(getActivity(), R.string.removed_all_new_flags_msg, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
};
|
||||
removeAllNewFlagsConfirmationDialog.createNewDialog().show();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
Log.d(TAG, "onContextItemSelected() called with: " + "item = [" + item + "]");
|
||||
if (!getUserVisibleHint()) {
|
||||
return false;
|
||||
}
|
||||
if (!isVisible()) {
|
||||
return false;
|
||||
}
|
||||
if (item.getItemId() == R.id.share_item) {
|
||||
return true; // avoids that the position is reset when we need it in the submenu
|
||||
}
|
||||
|
||||
if (listAdapter.getSelectedItem() == null) {
|
||||
Log.i(TAG, "Selected item or listAdapter was null, ignoring selection");
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
FeedItem selectedItem = listAdapter.getSelectedItem();
|
||||
|
||||
return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
View root = inflater.inflate(R.layout.all_episodes_fragment, container, false);
|
||||
txtvInformation = root.findViewById(R.id.txtvInformation);
|
||||
|
||||
layoutManager = new LinearLayoutManager(getActivity());
|
||||
recyclerView = root.findViewById(android.R.id.list);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
recyclerView.setHasFixedSize(true);
|
||||
recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build());
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
|
||||
RecyclerView.ItemAnimator animator = recyclerView.getItemAnimator();
|
||||
if (animator instanceof SimpleItemAnimator) {
|
||||
((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
|
||||
}
|
||||
|
||||
progLoading = root.findViewById(R.id.progLoading);
|
||||
progLoading.setVisibility(View.VISIBLE);
|
||||
|
||||
emptyView = new EmptyViewHandler(getContext());
|
||||
emptyView.attachToRecyclerView(recyclerView);
|
||||
emptyView.setIcon(R.attr.feed);
|
||||
emptyView.setTitle(R.string.no_all_episodes_head_label);
|
||||
emptyView.setMessage(R.string.no_all_episodes_label);
|
||||
|
||||
createRecycleAdapter(recyclerView, emptyView);
|
||||
emptyView.hide();
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
protected void onFragmentLoaded(List<FeedItem> episodes) {
|
||||
listAdapter.notifyDataSetChanged();
|
||||
|
||||
if (episodes.size() == 0) {
|
||||
createRecycleAdapter(recyclerView, emptyView);
|
||||
}
|
||||
|
||||
restoreScrollPosition();
|
||||
requireActivity().invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently, we need to recreate the list adapter in order to be able to undo last item via the
|
||||
* snackbar. See #3084 for details.
|
||||
*/
|
||||
private void createRecycleAdapter(RecyclerView recyclerView, EmptyViewHandler emptyViewHandler) {
|
||||
MainActivity mainActivity = (MainActivity) getActivity();
|
||||
listAdapter = new AllEpisodesRecycleAdapter(mainActivity, itemAccess, showOnlyNewEpisodes());
|
||||
listAdapter.setHasStableIds(true);
|
||||
recyclerView.setAdapter(listAdapter);
|
||||
emptyViewHandler.updateAdapter(listAdapter);
|
||||
}
|
||||
|
||||
private final AllEpisodesRecycleAdapter.ItemAccess itemAccess = new AllEpisodesRecycleAdapter.ItemAccess() {
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return episodes.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeedItem getItem(int position) {
|
||||
if (0 <= position && position < episodes.size()) {
|
||||
return episodes.get(position);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LongList getItemsIds() {
|
||||
LongList ids = new LongList(episodes.size());
|
||||
for (FeedItem episode : episodes) {
|
||||
ids.add(episode.getId());
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemDownloadProgressPercent(FeedItem item) {
|
||||
for (Downloader downloader : downloaderList) {
|
||||
DownloadRequest downloadRequest = downloader.getDownloadRequest();
|
||||
if (downloadRequest.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA
|
||||
&& downloadRequest.getFeedfileId() == item.getMedia().getId()) {
|
||||
return downloadRequest.getProgressPercent();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInQueue(FeedItem item) {
|
||||
return item != null && item.isTagged(FeedItem.TAG_QUEUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LongList getQueueIds() {
|
||||
LongList queueIds = new LongList();
|
||||
for (FeedItem item : episodes) {
|
||||
if (item.isTagged(FeedItem.TAG_QUEUE)) {
|
||||
queueIds.add(item.getId());
|
||||
}
|
||||
}
|
||||
return queueIds;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(FeedItemEvent event) {
|
||||
Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]");
|
||||
for (FeedItem item : event.items) {
|
||||
int pos = FeedItemUtil.indexOfItemWithId(episodes, item.getId());
|
||||
if (pos >= 0) {
|
||||
episodes.remove(pos);
|
||||
if (shouldUpdatedItemRemainInList(item)) {
|
||||
episodes.add(pos, item);
|
||||
listAdapter.notifyItemChanged(pos);
|
||||
} else {
|
||||
listAdapter.notifyItemRemoved(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(PlaybackPositionEvent event) {
|
||||
if (listAdapter != null) {
|
||||
listAdapter.notifyCurrentlyPlayingItemChanged(event);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean shouldUpdatedItemRemainInList(FeedItem item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(DownloadEvent event) {
|
||||
Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]");
|
||||
DownloaderUpdate update = event.update;
|
||||
downloaderList = update.downloaders;
|
||||
if (isMenuInvalidationAllowed && event.hasChangedFeedUpdateStatus(isUpdatingFeeds)) {
|
||||
requireActivity().invalidateOptionsMenu();
|
||||
}
|
||||
if (update.mediaIds.length > 0) {
|
||||
for (long mediaId : update.mediaIds) {
|
||||
int pos = FeedItemUtil.indexOfItemWithMediaId(episodes, mediaId);
|
||||
if (pos >= 0) {
|
||||
listAdapter.notifyItemChanged(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
|
||||
@Override
|
||||
public void update(EventDistributor eventDistributor, Integer arg) {
|
||||
if ((arg & EVENTS) != 0) {
|
||||
loadItems();
|
||||
if (isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) {
|
||||
requireActivity().invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void loadItems() {
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
disposable = Observable.fromCallable(this::loadData)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(data -> {
|
||||
progLoading.setVisibility(View.GONE);
|
||||
episodes = data;
|
||||
onFragmentLoaded(episodes);
|
||||
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected abstract List<FeedItem> loadData();
|
||||
}
|
@ -18,7 +18,7 @@ import com.bumptech.glide.Glide;
|
||||
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.event.ServiceEvent;
|
||||
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
|
||||
import de.danoeh.antennapod.core.feed.MediaType;
|
||||
import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
@ -28,6 +28,9 @@ import io.reactivex.Maybe;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
/**
|
||||
* Fragment which is supposed to be displayed outside of the MediaplayerActivity
|
||||
@ -138,6 +141,7 @@ public class ExternalPlayerFragment extends Fragment {
|
||||
controller = setupPlaybackController();
|
||||
controller.init();
|
||||
loadMediaInfo();
|
||||
EventBus.getDefault().register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -147,6 +151,12 @@ public class ExternalPlayerFragment extends Fragment {
|
||||
controller.release();
|
||||
controller = null;
|
||||
}
|
||||
EventBus.getDefault().unregister(this);
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(PlaybackPositionEvent event) {
|
||||
onPositionObserverUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -7,6 +7,7 @@ import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
@ -25,7 +26,7 @@ import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
* Like 'EpisodesFragment' except that it only shows favorite episodes and
|
||||
* supports swiping to remove from favorites.
|
||||
*/
|
||||
public class FavoriteEpisodesFragment extends AllEpisodesFragment {
|
||||
public class FavoriteEpisodesFragment extends EpisodesListFragment {
|
||||
|
||||
private static final String TAG = "FavoriteEpisodesFrag";
|
||||
private static final String PREF_NAME = "PrefFavoriteEpisodesFragment";
|
||||
|
@ -29,6 +29,7 @@ import com.bumptech.glide.request.RequestOptions;
|
||||
import com.joanzapata.iconify.Iconify;
|
||||
import com.joanzapata.iconify.widget.IconTextView;
|
||||
|
||||
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
@ -79,7 +80,7 @@ import io.reactivex.schedulers.Schedulers;
|
||||
* Displays a list of FeedItems.
|
||||
*/
|
||||
@SuppressLint("ValidFragment")
|
||||
public class ItemlistFragment extends ListFragment {
|
||||
public class FeedItemlistFragment extends ListFragment {
|
||||
private static final String TAG = "ItemlistFragment";
|
||||
|
||||
private static final int EVENTS = EventDistributor.UNREAD_ITEMS_UPDATE
|
||||
@ -120,8 +121,8 @@ public class ItemlistFragment extends ListFragment {
|
||||
* @param feedId The id of the feed to show
|
||||
* @return the newly created instance of an ItemlistFragment
|
||||
*/
|
||||
public static ItemlistFragment newInstance(long feedId) {
|
||||
ItemlistFragment i = new ItemlistFragment();
|
||||
public static FeedItemlistFragment newInstance(long feedId) {
|
||||
FeedItemlistFragment i = new FeedItemlistFragment();
|
||||
Bundle b = new Bundle();
|
||||
b.putLong(ARGUMENT_FEED_ID, feedId);
|
||||
i.setArguments(b);
|
||||
@ -195,6 +196,21 @@ public class ItemlistFragment extends ListFragment {
|
||||
final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem);
|
||||
MenuItemUtils.adjustTextColor(getActivity(), sv);
|
||||
sv.setQueryHint(getString(R.string.search_hint));
|
||||
searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
|
||||
@Override
|
||||
public boolean onMenuItemActionExpand(MenuItem item) {
|
||||
menu.findItem(R.id.filter_items).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
|
||||
menu.findItem(R.id.episode_actions).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
|
||||
menu.findItem(R.id.refresh_item).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionCollapse(MenuItem item) {
|
||||
getActivity().invalidateOptionsMenu();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String s) {
|
||||
@ -308,7 +324,7 @@ public class ItemlistFragment extends ListFragment {
|
||||
|
||||
contextMenu = menu;
|
||||
lastMenuInfo = (AdapterView.AdapterContextMenuInfo) menuInfo;
|
||||
FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, null);
|
||||
FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -325,7 +341,7 @@ public class ItemlistFragment extends ListFragment {
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
|
||||
return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem);
|
||||
return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -375,14 +391,21 @@ public class ItemlistFragment extends ListFragment {
|
||||
Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]");
|
||||
DownloaderUpdate update = event.update;
|
||||
downloaderList = update.downloaders;
|
||||
if (isUpdatingFeed != event.update.feedIds.length > 0) {
|
||||
if (event.hasChangedFeedUpdateStatus(isUpdatingFeed)) {
|
||||
updateProgressBarVisibility();
|
||||
}
|
||||
if(adapter != null && update.mediaIds.length > 0) {
|
||||
if (adapter != null && update.mediaIds.length > 0) {
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(PlaybackPositionEvent event) {
|
||||
if (adapter != null) {
|
||||
adapter.notifyCurrentlyPlayingItemChanged(event, getListView());
|
||||
}
|
||||
}
|
||||
|
||||
private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
|
||||
|
||||
@Override
|
@ -96,13 +96,7 @@ public class ItemDescriptionFragment extends Fragment {
|
||||
if (Timeline.isTimecodeLink(url)) {
|
||||
onTimecodeLinkSelected(url);
|
||||
} else {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
try {
|
||||
startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return true;
|
||||
}
|
||||
IntentUtils.openInBrowser(getContext(), url);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -159,11 +153,7 @@ public class ItemDescriptionFragment extends Fragment {
|
||||
if (selectedURL != null) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.open_in_browser_item:
|
||||
Uri uri = Uri.parse(selectedURL);
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
if(IntentUtils.isCallable(getActivity(), intent)) {
|
||||
getActivity().startActivity(intent);
|
||||
}
|
||||
IntentUtils.openInBrowser(getContext(), selectedURL);
|
||||
break;
|
||||
case R.id.share_url_item:
|
||||
ShareUtils.shareLink(getActivity(), selectedURL);
|
||||
|
@ -55,7 +55,6 @@ import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.Downloader;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
@ -211,10 +210,7 @@ public class ItemFragment extends Fragment implements OnSwipeGesture {
|
||||
webvDescription.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
if(IntentUtils.isCallable(getActivity(), intent)) {
|
||||
startActivity(intent);
|
||||
}
|
||||
IntentUtils.openInBrowser(getContext(), url);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@ -336,10 +332,10 @@ public class ItemFragment extends Fragment implements OnSwipeGesture {
|
||||
inflater.inflate(R.menu.feeditem_options, menu);
|
||||
popupMenu = menu;
|
||||
if (item.hasMedia()) {
|
||||
FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, null);
|
||||
FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item);
|
||||
} else {
|
||||
// these are already available via button1 and button2
|
||||
FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, null,
|
||||
FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item,
|
||||
R.id.mark_read_item, R.id.visit_website_item);
|
||||
}
|
||||
}
|
||||
@ -351,7 +347,7 @@ public class ItemFragment extends Fragment implements OnSwipeGesture {
|
||||
openPodcast();
|
||||
return true;
|
||||
default:
|
||||
return FeedItemMenuHandler.onMenuItemClicked(getActivity(), menuItem.getItemId(), item);
|
||||
return FeedItemMenuHandler.onMenuItemClicked(this, menuItem.getItemId(), item);
|
||||
}
|
||||
}
|
||||
|
||||
@ -446,15 +442,6 @@ public class ItemFragment extends Fragment implements OnSwipeGesture {
|
||||
}
|
||||
}
|
||||
|
||||
FeedItem.State state = item.getState();
|
||||
if (butAction2Text == R.string.delete_label && state == FeedItem.State.PLAYING && PlaybackService.isRunning) {
|
||||
butAction2.setEnabled(false);
|
||||
butAction2.setAlpha(0.5f);
|
||||
} else {
|
||||
butAction2.setEnabled(true);
|
||||
butAction2.setAlpha(1.0f);
|
||||
}
|
||||
|
||||
if(butAction1Icon != null && butAction1Text != 0) {
|
||||
butAction1.setText(butAction1Icon +"\u0020\u0020" + getActivity().getString(butAction1Text));
|
||||
Iconify.addIcons(butAction1);
|
||||
@ -494,11 +481,7 @@ public class ItemFragment extends Fragment implements OnSwipeGesture {
|
||||
if (selectedURL != null) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.open_in_browser_item:
|
||||
Uri uri = Uri.parse(selectedURL);
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
if(IntentUtils.isCallable(getActivity(), intent)) {
|
||||
getActivity().startActivity(intent);
|
||||
}
|
||||
IntentUtils.openInBrowser(getContext(), selectedURL);
|
||||
break;
|
||||
case R.id.share_url_item:
|
||||
ShareUtils.shareLink(getActivity(), selectedURL);
|
||||
@ -543,7 +526,7 @@ public class ItemFragment extends Fragment implements OnSwipeGesture {
|
||||
}
|
||||
|
||||
private void openPodcast() {
|
||||
Fragment fragment = ItemlistFragment.newInstance(item.getFeedId());
|
||||
Fragment fragment = FeedItemlistFragment.newInstance(item.getFeedId());
|
||||
((MainActivity)getActivity()).loadChildFragment(fragment);
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
@ -14,12 +15,13 @@ import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.adapter.AllEpisodesRecycleAdapter;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
|
||||
|
||||
/**
|
||||
* Like 'EpisodesFragment' except that it only shows new episodes and
|
||||
* supports swiping to mark as read.
|
||||
*/
|
||||
public class NewEpisodesFragment extends AllEpisodesFragment {
|
||||
public class NewEpisodesFragment extends EpisodesListFragment {
|
||||
|
||||
public static final String TAG = "NewEpisodesFragment";
|
||||
private static final String PREF_NAME = "PrefNewEpisodesFragment";
|
||||
@ -39,6 +41,12 @@ public class NewEpisodesFragment extends AllEpisodesFragment {
|
||||
return item.isNew();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
menu.findItem(R.id.remove_all_new_flags_item).setVisible(!episodes.isEmpty());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
@ -55,7 +63,7 @@ public class NewEpisodesFragment extends AllEpisodesFragment {
|
||||
@Override
|
||||
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
|
||||
AllEpisodesRecycleAdapter.Holder holder = (AllEpisodesRecycleAdapter.Holder) viewHolder;
|
||||
removeNewFlagWithUndo(holder.getFeedItem());
|
||||
FeedItemMenuHandler.removeNewFlagWithUndo(NewEpisodesFragment.this, holder.getFeedItem());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -7,6 +7,7 @@ import android.os.Bundle;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.SearchView;
|
||||
@ -19,11 +20,17 @@ import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
|
||||
|
||||
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
@ -35,14 +42,12 @@ import de.danoeh.antennapod.core.event.DownloaderUpdate;
|
||||
import de.danoeh.antennapod.core.event.FeedItemEvent;
|
||||
import de.danoeh.antennapod.core.event.QueueEvent;
|
||||
import de.danoeh.antennapod.core.feed.EventDistributor;
|
||||
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.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadService;
|
||||
import de.danoeh.antennapod.core.service.download.Downloader;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||
import de.danoeh.antennapod.core.util.Converter;
|
||||
@ -50,18 +55,15 @@ import de.danoeh.antennapod.core.util.FeedItemUtil;
|
||||
import de.danoeh.antennapod.core.util.LongList;
|
||||
import de.danoeh.antennapod.core.util.QueueSorter;
|
||||
import de.danoeh.antennapod.core.util.SortOrder;
|
||||
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
|
||||
import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment;
|
||||
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
|
||||
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
|
||||
|
||||
import de.danoeh.antennapod.view.EmptyViewHandler;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_DELETE;
|
||||
import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_REMOVE_FROM_QUEUE;
|
||||
@ -91,10 +93,12 @@ public class QueueFragment extends Fragment {
|
||||
private static final String PREFS = "QueueFragment";
|
||||
private static final String PREF_SCROLL_POSITION = "scroll_position";
|
||||
private static final String PREF_SCROLL_OFFSET = "scroll_offset";
|
||||
private static final String PREF_SHOW_LOCK_WARNING = "show_lock_warning";
|
||||
|
||||
private Disposable disposable;
|
||||
private LinearLayoutManager layoutManager;
|
||||
private ItemTouchHelper itemTouchHelper;
|
||||
private SharedPreferences prefs;
|
||||
|
||||
|
||||
@Override
|
||||
@ -102,6 +106,7 @@ public class QueueFragment extends Fragment {
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
setHasOptionsMenu(true);
|
||||
prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -196,8 +201,8 @@ public class QueueFragment extends Fragment {
|
||||
Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]");
|
||||
DownloaderUpdate update = event.update;
|
||||
downloaderList = update.downloaders;
|
||||
if (isUpdatingFeeds != update.feedIds.length > 0) {
|
||||
getActivity().supportInvalidateOptionsMenu();
|
||||
if (event.hasChangedFeedUpdateStatus(isUpdatingFeeds)) {
|
||||
getActivity().invalidateOptionsMenu();
|
||||
}
|
||||
if (recyclerAdapter != null && update.mediaIds.length > 0) {
|
||||
for (long mediaId : update.mediaIds) {
|
||||
@ -209,6 +214,13 @@ public class QueueFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(PlaybackPositionEvent event) {
|
||||
if (recyclerAdapter != null) {
|
||||
recyclerAdapter.notifyCurrentlyPlayingItemChanged(event);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveScrollPosition() {
|
||||
int firstItem = layoutManager.findFirstVisibleItemPosition();
|
||||
View firstItemView = layoutManager.findViewByPosition(firstItem);
|
||||
@ -219,15 +231,13 @@ public class QueueFragment extends Fragment {
|
||||
topOffset = firstItemView.getTop();
|
||||
}
|
||||
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(PREF_SCROLL_POSITION, firstItem);
|
||||
editor.putFloat(PREF_SCROLL_OFFSET, topOffset);
|
||||
editor.commit();
|
||||
prefs.edit()
|
||||
.putInt(PREF_SCROLL_POSITION, firstItem)
|
||||
.putFloat(PREF_SCROLL_OFFSET, topOffset)
|
||||
.apply();
|
||||
}
|
||||
|
||||
private void restoreScrollPosition() {
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
|
||||
int position = prefs.getInt(PREF_SCROLL_POSITION, 0);
|
||||
float offset = prefs.getFloat(PREF_SCROLL_OFFSET, 0.0f);
|
||||
if (position > 0 || offset > 0) {
|
||||
@ -299,25 +309,10 @@ public class QueueFragment extends Fragment {
|
||||
if (!super.onOptionsItemSelected(item)) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.queue_lock:
|
||||
boolean newLockState = !UserPreferences.isQueueLocked();
|
||||
UserPreferences.setQueueLocked(newLockState);
|
||||
getActivity().supportInvalidateOptionsMenu();
|
||||
if (recyclerAdapter != null) {
|
||||
recyclerAdapter.setLocked(newLockState);
|
||||
}
|
||||
if (newLockState) {
|
||||
Snackbar.make(getActivity().findViewById(R.id.content), R.string
|
||||
.queue_locked, Snackbar.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Snackbar.make(getActivity().findViewById(R.id.content), R.string
|
||||
.queue_unlocked, Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
toggleQueueLock();
|
||||
return true;
|
||||
case R.id.refresh_item:
|
||||
List<Feed> feeds = ((MainActivity) getActivity()).getFeeds();
|
||||
if (feeds != null) {
|
||||
DBTasks.refreshAllFeeds(getActivity(), feeds);
|
||||
}
|
||||
AutoUpdateManager.runImmediate(requireContext());
|
||||
return true;
|
||||
case R.id.clear_queue:
|
||||
// make sure the user really wants to clear the queue
|
||||
@ -378,8 +373,10 @@ public class QueueFragment extends Fragment {
|
||||
if (keepSortedNew) {
|
||||
SortOrder sortOrder = UserPreferences.getQueueKeepSortedOrder();
|
||||
QueueSorter.sort(sortOrder, true);
|
||||
recyclerAdapter.setLocked(true);
|
||||
} else {
|
||||
if (recyclerAdapter != null) {
|
||||
recyclerAdapter.setLocked(true);
|
||||
}
|
||||
} else if (recyclerAdapter != null) {
|
||||
recyclerAdapter.setLocked(UserPreferences.isQueueLocked());
|
||||
}
|
||||
getActivity().invalidateOptionsMenu();
|
||||
@ -392,6 +389,48 @@ public class QueueFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleQueueLock() {
|
||||
boolean isLocked = UserPreferences.isQueueLocked();
|
||||
if (isLocked) {
|
||||
setQueueLocked(false);
|
||||
} else {
|
||||
boolean shouldShowLockWarning = prefs.getBoolean(PREF_SHOW_LOCK_WARNING, true);
|
||||
if (!shouldShowLockWarning) {
|
||||
setQueueLocked(true);
|
||||
} else {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
builder.setTitle(R.string.lock_queue);
|
||||
builder.setMessage(R.string.queue_lock_warning);
|
||||
|
||||
View view = View.inflate(getContext(), R.layout.checkbox_do_not_show_again, null);
|
||||
CheckBox checkDoNotShowAgain = view.findViewById(R.id.checkbox_do_not_show_again);
|
||||
builder.setView(view);
|
||||
|
||||
builder.setPositiveButton(R.string.lock_queue, (dialog, which) -> {
|
||||
prefs.edit().putBoolean(PREF_SHOW_LOCK_WARNING, !checkDoNotShowAgain.isChecked()).apply();
|
||||
setQueueLocked(true);
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel_label, null);
|
||||
builder.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setQueueLocked(boolean locked) {
|
||||
UserPreferences.setQueueLocked(locked);
|
||||
getActivity().supportInvalidateOptionsMenu();
|
||||
if (recyclerAdapter != null) {
|
||||
recyclerAdapter.setLocked(locked);
|
||||
}
|
||||
if (locked) {
|
||||
Snackbar.make(getActivity().findViewById(R.id.content), R.string
|
||||
.queue_locked, Snackbar.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Snackbar.make(getActivity().findViewById(R.id.content), R.string
|
||||
.queue_unlocked, Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called if the user clicks on a sort order menu item.
|
||||
*
|
||||
@ -428,7 +467,7 @@ public class QueueFragment extends Fragment {
|
||||
DBWriter.moveQueueItemToBottom(selectedItem.getId(), true);
|
||||
return true;
|
||||
default:
|
||||
return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem);
|
||||
return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem);
|
||||
}
|
||||
}
|
||||
|
||||
@ -503,7 +542,7 @@ public class QueueFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
public boolean isLongPressDragEnabled() {
|
||||
return !UserPreferences.isQueueLocked();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -596,7 +635,7 @@ public class QueueFragment extends Fragment {
|
||||
String info = queue.size() + getString(R.string.episodes_suffix);
|
||||
if(queue.size() > 0) {
|
||||
long timeLeft = 0;
|
||||
float playbackSpeed = Float.valueOf(UserPreferences.getPlaybackSpeed());
|
||||
float playbackSpeed = UserPreferences.getPlaybackSpeed();
|
||||
for(FeedItem item : queue) {
|
||||
if(item.getMedia() != null) {
|
||||
timeLeft +=
|
||||
@ -604,7 +643,7 @@ public class QueueFragment extends Fragment {
|
||||
/ playbackSpeed);
|
||||
}
|
||||
}
|
||||
info += " \u2022 ";
|
||||
info += " • ";
|
||||
info += getString(R.string.time_left_label);
|
||||
info += Converter.getDurationStringLocalized(getActivity(), timeLeft);
|
||||
}
|
||||
|
@ -25,19 +25,28 @@ import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.adapter.SubscriptionsAdapter;
|
||||
import de.danoeh.antennapod.core.asynctask.FeedRemover;
|
||||
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
|
||||
import de.danoeh.antennapod.core.event.DownloadEvent;
|
||||
import de.danoeh.antennapod.core.event.DownloaderUpdate;
|
||||
import de.danoeh.antennapod.core.feed.EventDistributor;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadService;
|
||||
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.DownloadRequester;
|
||||
import de.danoeh.antennapod.core.util.FeedItemUtil;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
|
||||
import de.danoeh.antennapod.dialog.RenameFeedDialog;
|
||||
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
/**
|
||||
* Fragment for displaying feed subscriptions
|
||||
@ -56,6 +65,7 @@ public class SubscriptionFragment extends Fragment {
|
||||
private SubscriptionsAdapter subscriptionAdapter;
|
||||
|
||||
private int mPosition = -1;
|
||||
private boolean isUpdatingFeeds = false;
|
||||
|
||||
private Disposable disposable;
|
||||
private SharedPreferences prefs;
|
||||
@ -89,6 +99,8 @@ public class SubscriptionFragment extends Fragment {
|
||||
menu.findItem(R.id.subscription_num_columns_3).setChecked(columns == 3);
|
||||
menu.findItem(R.id.subscription_num_columns_4).setChecked(columns == 4);
|
||||
menu.findItem(R.id.subscription_num_columns_5).setChecked(columns == 5);
|
||||
|
||||
isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -97,6 +109,9 @@ public class SubscriptionFragment extends Fragment {
|
||||
return true;
|
||||
}
|
||||
switch (item.getItemId()) {
|
||||
case R.id.refresh_item:
|
||||
AutoUpdateManager.runImmediate(requireContext());
|
||||
return true;
|
||||
case R.id.subscription_num_columns_2:
|
||||
setColumnNumber(2);
|
||||
return true;
|
||||
@ -136,6 +151,7 @@ public class SubscriptionFragment extends Fragment {
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
EventDistributor.getInstance().register(contentUpdate);
|
||||
EventBus.getDefault().register(this);
|
||||
loadSubscriptions();
|
||||
}
|
||||
|
||||
@ -143,6 +159,7 @@ public class SubscriptionFragment extends Fragment {
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
EventDistributor.getInstance().unregister(contentUpdate);
|
||||
EventBus.getDefault().unregister(this);
|
||||
if(disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
@ -278,6 +295,17 @@ public class SubscriptionFragment extends Fragment {
|
||||
}
|
||||
};
|
||||
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(DownloadEvent event) {
|
||||
Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]");
|
||||
if (event.hasChangedFeedUpdateStatus(isUpdatingFeeds)) {
|
||||
getActivity().invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
|
||||
private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker =
|
||||
() -> DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds();
|
||||
|
||||
private final SubscriptionsAdapter.ItemAccess itemAccess = new SubscriptionsAdapter.ItemAccess() {
|
||||
@Override
|
||||
public int getCount() {
|
||||
|
@ -1,29 +1,41 @@
|
||||
package de.danoeh.antennapod.fragment.preferences;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.net.wifi.WifiConfiguration;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.preference.CheckBoxPreference;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
import android.support.v7.preference.PreferenceScreen;
|
||||
import android.util.Log;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
|
||||
public class AutoDownloadPreferencesFragment extends PreferenceFragmentCompat {
|
||||
private static final String TAG = "AutoDnldPrefFragment";
|
||||
|
||||
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1;
|
||||
private static final String PREF_KEY_LOCATION_PERMISSION_REQUEST_PROMPT = "prefAutoDownloadWifiFilterAndroid10PermissionPrompt";
|
||||
|
||||
private CheckBoxPreference[] selectedNetworks;
|
||||
|
||||
private Preference prefPermissionRequestPromptOnAndroid10 = null;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.preferences_autodownload);
|
||||
@ -175,10 +187,65 @@ public class AutoDownloadPreferencesFragment extends PreferenceFragmentCompat {
|
||||
}
|
||||
|
||||
private void setSelectedNetworksEnabled(boolean b) {
|
||||
if (showPermissionRequestPromptOnAndroid10IfNeeded(b)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedNetworks != null) {
|
||||
for (Preference p : selectedNetworks) {
|
||||
p.setEnabled(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
if (requestCode != LOCATION_PERMISSION_REQUEST_CODE) {
|
||||
return;
|
||||
}
|
||||
if (permissions.length > 0 && permissions[0].equals(Manifest.permission.ACCESS_FINE_LOCATION) &&
|
||||
grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
buildAutodownloadSelectedNetworksPreference();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean showPermissionRequestPromptOnAndroid10IfNeeded(boolean wifiFilterEnabled) {
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cases Android 10(Q) or later
|
||||
if (prefPermissionRequestPromptOnAndroid10 != null) {
|
||||
getPreferenceScreen().removePreference(prefPermissionRequestPromptOnAndroid10);
|
||||
prefPermissionRequestPromptOnAndroid10 = null;
|
||||
}
|
||||
|
||||
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Case location permission not yet granted, permission-specific UI is needed
|
||||
if (!wifiFilterEnabled) {
|
||||
// Don't show the UI when WiFi filter disabled.
|
||||
// it still return true, so that the caller knows
|
||||
// it does not have required permission, and will not invoke codes that require so.
|
||||
return true;
|
||||
}
|
||||
|
||||
Preference pref = new Preference(requireActivity());
|
||||
pref.setKey(PREF_KEY_LOCATION_PERMISSION_REQUEST_PROMPT);
|
||||
pref.setTitle(R.string.autodl_wifi_filter_permission_title);
|
||||
pref.setSummary(R.string.autodl_wifi_filter_permission_message);
|
||||
pref.setIcon(R.drawable.ic_warning_red);
|
||||
pref.setOnPreferenceClickListener(preference -> {
|
||||
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE);
|
||||
return true;
|
||||
});
|
||||
pref.setPersistent(false);
|
||||
getPreferenceScreen().addPreference(pref);
|
||||
prefPermissionRequestPromptOnAndroid10 = pref;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,27 +1,21 @@
|
||||
package de.danoeh.antennapod.fragment.preferences;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.content.FileProvider;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
import com.bytehamster.lib.preferencesearch.SearchConfiguration;
|
||||
import com.bytehamster.lib.preferencesearch.SearchPreference;
|
||||
import de.danoeh.antennapod.CrashReportWriter;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.AboutActivity;
|
||||
import de.danoeh.antennapod.activity.BugReportActivity;
|
||||
import de.danoeh.antennapod.activity.PreferenceActivity;
|
||||
import de.danoeh.antennapod.activity.StatisticsActivity;
|
||||
|
||||
import java.util.List;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
|
||||
public class MainPreferencesFragment extends PreferenceFragmentCompat {
|
||||
private static final String TAG = "MainPreferencesFragment";
|
||||
@ -31,9 +25,9 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat {
|
||||
private static final String PREF_SCREEN_NETWORK = "prefScreenNetwork";
|
||||
private static final String PREF_SCREEN_INTEGRATIONS = "prefScreenIntegrations";
|
||||
private static final String PREF_SCREEN_STORAGE = "prefScreenStorage";
|
||||
private static final String PREF_KNOWN_ISSUES = "prefKnownIssues";
|
||||
private static final String PREF_FAQ = "prefFaq";
|
||||
private static final String PREF_SEND_CRASH_REPORT = "prefSendCrashReport";
|
||||
private static final String PREF_VIEW_MAILING_LIST = "prefViewMailingList";
|
||||
private static final String PREF_SEND_BUG_REPORT = "prefSendBugReport";
|
||||
private static final String STATISTICS = "statistics";
|
||||
private static final String PREF_ABOUT = "prefAbout";
|
||||
|
||||
@ -78,47 +72,18 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat {
|
||||
return true;
|
||||
}
|
||||
);
|
||||
findPreference(PREF_KNOWN_ISSUES).setOnPreferenceClickListener(preference -> {
|
||||
openInBrowser("https://github.com/AntennaPod/AntennaPod/labels/bug");
|
||||
return true;
|
||||
});
|
||||
findPreference(PREF_FAQ).setOnPreferenceClickListener(preference -> {
|
||||
openInBrowser("http://antennapod.org/faq.html");
|
||||
IntentUtils.openInBrowser(getContext(), "https://antennapod.org/faq.html");
|
||||
return true;
|
||||
});
|
||||
findPreference(PREF_SEND_CRASH_REPORT).setOnPreferenceClickListener(preference -> {
|
||||
Context context = getActivity().getApplicationContext();
|
||||
Intent emailIntent = new Intent(Intent.ACTION_SEND);
|
||||
emailIntent.setType("text/plain");
|
||||
emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"Martin.Fietz@gmail.com"});
|
||||
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "AntennaPod Crash Report");
|
||||
emailIntent.putExtra(Intent.EXTRA_TEXT, "Please describe what you were doing when the app crashed");
|
||||
// the attachment
|
||||
Uri fileUri = FileProvider.getUriForFile(context, context.getString(R.string.provider_authority),
|
||||
CrashReportWriter.getFile());
|
||||
emailIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
|
||||
emailIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
String intentTitle = getActivity().getString(R.string.send_email);
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
|
||||
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(emailIntent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
for (ResolveInfo resolveInfo : resInfoList) {
|
||||
String packageName = resolveInfo.activityInfo.packageName;
|
||||
context.grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
}
|
||||
getActivity().startActivity(Intent.createChooser(emailIntent, intentTitle));
|
||||
findPreference(PREF_VIEW_MAILING_LIST).setOnPreferenceClickListener(preference -> {
|
||||
IntentUtils.openInBrowser(getContext(), "https://groups.google.com/forum/#!forum/antennapod");
|
||||
return true;
|
||||
});
|
||||
findPreference(PREF_SEND_BUG_REPORT).setOnPreferenceClickListener(preference -> {
|
||||
startActivity(new Intent(getActivity(), BugReportActivity.class));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void openInBrowser(String url) {
|
||||
try {
|
||||
Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
startActivity(myIntent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(getActivity(), R.string.pref_no_browser_found, Toast.LENGTH_LONG).show();
|
||||
Log.e(TAG, Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
|
||||
private void setupSearch() {
|
||||
|
@ -4,6 +4,7 @@ import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
@ -13,13 +14,16 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.content.FileProvider;
|
||||
import android.support.v4.provider.DocumentFile;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.DirectoryChooserActivity;
|
||||
import de.danoeh.antennapod.activity.ImportExportActivity;
|
||||
import de.danoeh.antennapod.activity.OpmlImportFromPathActivity;
|
||||
import de.danoeh.antennapod.asynctask.DocumentFileExportWorker;
|
||||
import de.danoeh.antennapod.asynctask.ExportWorker;
|
||||
import de.danoeh.antennapod.core.export.ExportWriter;
|
||||
import de.danoeh.antennapod.core.export.html.HtmlWriter;
|
||||
@ -45,6 +49,12 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat {
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE };
|
||||
private static final int PERMISSION_REQUEST_EXTERNAL_STORAGE = 41;
|
||||
private static final int CHOOSE_OPML_EXPORT_PATH = 1;
|
||||
private static final String DEFAULT_OPML_OUTPUT_NAME = "antennapod-feeds.opml";
|
||||
private static final String CONTENT_TYPE_OPML = "text/x-opml";
|
||||
private static final int CHOOSE_HTML_EXPORT_PATH = 2;
|
||||
private static final String DEFAULT_HTML_OUTPUT_NAME = "antennapod-feeds.html";
|
||||
private static final String CONTENT_TYPE_HTML = "text/html";
|
||||
private Disposable disposable;
|
||||
|
||||
@Override
|
||||
@ -59,6 +69,14 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat {
|
||||
setDataFolderText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupStorageScreen() {
|
||||
final Activity activity = getActivity();
|
||||
|
||||
@ -69,9 +87,16 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat {
|
||||
}
|
||||
);
|
||||
findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener(
|
||||
preference -> export(new OpmlWriter()));
|
||||
preference -> {
|
||||
openOpmlExportPathPicker();
|
||||
return true;
|
||||
}
|
||||
);
|
||||
findPreference(PREF_HTML_EXPORT).setOnPreferenceClickListener(
|
||||
preference -> export(new HtmlWriter()));
|
||||
preference -> {
|
||||
openHtmlExportPathPicker();
|
||||
return true;
|
||||
});
|
||||
findPreference(PREF_OPML_IMPORT).setOnPreferenceClickListener(
|
||||
preference -> {
|
||||
activity.startActivity(new Intent(activity, OpmlImportFromPathActivity.class));
|
||||
@ -129,52 +154,65 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat {
|
||||
}
|
||||
|
||||
private boolean export(ExportWriter exportWriter) {
|
||||
return export(exportWriter, null);
|
||||
}
|
||||
|
||||
private boolean export(ExportWriter exportWriter, final Uri uri) {
|
||||
Context context = getActivity();
|
||||
final ProgressDialog progressDialog = new ProgressDialog(context);
|
||||
progressDialog.setMessage(context.getString(R.string.exporting_label));
|
||||
progressDialog.setIndeterminate(true);
|
||||
progressDialog.show();
|
||||
final AlertDialog.Builder alert = new AlertDialog.Builder(context)
|
||||
.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss());
|
||||
Observable<File> observable = new ExportWorker(exportWriter).exportObservable();
|
||||
disposable = observable.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(output -> {
|
||||
alert.setTitle(R.string.export_success_title);
|
||||
String message = context.getString(R.string.export_success_sum, output.toString());
|
||||
alert.setMessage(message);
|
||||
alert.setPositiveButton(R.string.send_label, (dialog, which) -> {
|
||||
if (uri == null) {
|
||||
Observable<File> observable = new ExportWorker(exportWriter).exportObservable();
|
||||
disposable = observable.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(output -> {
|
||||
Uri fileUri = FileProvider.getUriForFile(context.getApplicationContext(),
|
||||
context.getString(R.string.provider_authority), output);
|
||||
Intent sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_SUBJECT,
|
||||
context.getResources().getText(R.string.opml_export_label));
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
|
||||
sendIntent.setType("text/plain");
|
||||
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
|
||||
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(sendIntent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
for (ResolveInfo resolveInfo : resInfoList) {
|
||||
String packageName = resolveInfo.activityInfo.packageName;
|
||||
context.grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
}
|
||||
context.startActivity(Intent.createChooser(sendIntent,
|
||||
context.getResources().getText(R.string.send_label)));
|
||||
});
|
||||
alert.create().show();
|
||||
}, error -> {
|
||||
alert.setTitle(R.string.export_error_label);
|
||||
alert.setMessage(error.getMessage());
|
||||
alert.show();
|
||||
}, progressDialog::dismiss);
|
||||
showExportSuccessDialog(context.getString(R.string.export_success_sum, output.toString()), fileUri);
|
||||
}, this::showExportErrorDialog, progressDialog::dismiss);
|
||||
} else {
|
||||
Observable<DocumentFile> observable = new DocumentFileExportWorker(exportWriter, context, uri).exportObservable();
|
||||
disposable = observable.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(output -> {
|
||||
showExportSuccessDialog(context.getString(R.string.export_success_sum, output.getUri()), output.getUri());
|
||||
}, this::showExportErrorDialog, progressDialog::dismiss);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void unsubscribeExportSubscription() {
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
private void showExportSuccessDialog(final String message, final Uri streamUri) {
|
||||
final AlertDialog.Builder alert = new AlertDialog.Builder(getContext())
|
||||
.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss());
|
||||
alert.setTitle(R.string.export_success_title);
|
||||
alert.setMessage(message);
|
||||
alert.setPositiveButton(R.string.send_label, (dialog, which) -> {
|
||||
Intent sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.opml_export_label));
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, streamUri);
|
||||
sendIntent.setType("text/plain");
|
||||
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
|
||||
List<ResolveInfo> resInfoList = getContext().getPackageManager()
|
||||
.queryIntentActivities(sendIntent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
for (ResolveInfo resolveInfo : resInfoList) {
|
||||
String packageName = resolveInfo.activityInfo.packageName;
|
||||
getContext().grantUriPermission(packageName, streamUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
}
|
||||
getContext().startActivity(Intent.createChooser(sendIntent, getString(R.string.send_label)));
|
||||
});
|
||||
alert.create().show();
|
||||
}
|
||||
|
||||
private void showExportErrorDialog(final Throwable error) {
|
||||
final AlertDialog.Builder alert = new AlertDialog.Builder(getContext())
|
||||
.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss());
|
||||
alert.setTitle(R.string.export_error_label);
|
||||
alert.setMessage(error.getMessage());
|
||||
alert.show();
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@ -184,22 +222,22 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat {
|
||||
String dir = data.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR);
|
||||
|
||||
File path;
|
||||
if(dir != null) {
|
||||
if (dir != null) {
|
||||
path = new File(dir);
|
||||
} else {
|
||||
path = getActivity().getExternalFilesDir(null);
|
||||
}
|
||||
String message = null;
|
||||
final Context context= getActivity().getApplicationContext();
|
||||
if(!path.exists()) {
|
||||
final Context context = getActivity().getApplicationContext();
|
||||
if (!path.exists()) {
|
||||
message = String.format(context.getString(R.string.folder_does_not_exist_error), dir);
|
||||
} else if(!path.canRead()) {
|
||||
} else if (!path.canRead()) {
|
||||
message = String.format(context.getString(R.string.folder_not_readable_error), dir);
|
||||
} else if(!path.canWrite()) {
|
||||
} else if (!path.canWrite()) {
|
||||
message = String.format(context.getString(R.string.folder_not_writable_error), dir);
|
||||
}
|
||||
|
||||
if(message == null) {
|
||||
if (message == null) {
|
||||
Log.d(TAG, "Setting data folder: " + dir);
|
||||
UserPreferences.setDataFolder(dir);
|
||||
setDataFolderText();
|
||||
@ -210,6 +248,16 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat {
|
||||
ab.show();
|
||||
}
|
||||
}
|
||||
|
||||
if (resultCode == Activity.RESULT_OK && requestCode == CHOOSE_OPML_EXPORT_PATH) {
|
||||
Uri uri = data.getData();
|
||||
export(new OpmlWriter(), uri);
|
||||
}
|
||||
|
||||
if (resultCode == Activity.RESULT_OK && requestCode == CHOOSE_HTML_EXPORT_PATH) {
|
||||
Uri uri = data.getData();
|
||||
export(new HtmlWriter(), uri);
|
||||
}
|
||||
}
|
||||
|
||||
private void setDataFolderText() {
|
||||
@ -231,6 +279,50 @@ public class StoragePreferencesFragment extends PreferenceFragmentCompat {
|
||||
activity.startActivityForResult(intent, DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED);
|
||||
}
|
||||
|
||||
private void openOpmlExportPathPicker() {
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
Intent intentPickAction = new Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.setType(CONTENT_TYPE_OPML)
|
||||
.putExtra(Intent.EXTRA_TITLE, DEFAULT_OPML_OUTPUT_NAME);
|
||||
|
||||
// Creates an implicit intent to launch a file manager which lets
|
||||
// the user choose a specific directory to export to.
|
||||
try {
|
||||
startActivityForResult(intentPickAction, CHOOSE_OPML_EXPORT_PATH);
|
||||
return;
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(TAG, "No activity found. Should never happen...");
|
||||
}
|
||||
}
|
||||
|
||||
// If we are using a SDK lower than API 21 or the implicit intent failed
|
||||
// fallback to the legacy export process
|
||||
export(new OpmlWriter());
|
||||
}
|
||||
|
||||
private void openHtmlExportPathPicker() {
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
Intent intentPickAction = new Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.setType(CONTENT_TYPE_HTML)
|
||||
.putExtra(Intent.EXTRA_TITLE, DEFAULT_HTML_OUTPUT_NAME);
|
||||
|
||||
// Creates an implicit intent to launch a file manager which lets
|
||||
// the user choose a specific directory to export to.
|
||||
try {
|
||||
startActivityForResult(intentPickAction, CHOOSE_HTML_EXPORT_PATH);
|
||||
return;
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(TAG, "No activity found. Should never happen...");
|
||||
}
|
||||
}
|
||||
|
||||
// If we are using a SDK lower than API 21 or the implicit intent failed
|
||||
// fallback to the legacy export process
|
||||
export(new HtmlWriter());
|
||||
}
|
||||
|
||||
private void showChooseDataFolderDialog() {
|
||||
ChooseDataFolderDialog.showDialog(
|
||||
getActivity(), new ChooseDataFolderDialog.RunnableWithString() {
|
||||
|
@ -3,7 +3,10 @@ package de.danoeh.antennapod.menuhandler;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -18,7 +21,6 @@ import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.util.FeedItemUtil;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import de.danoeh.antennapod.core.util.LongList;
|
||||
import de.danoeh.antennapod.core.util.ShareUtils;
|
||||
|
||||
/**
|
||||
@ -51,35 +53,21 @@ public class FeedItemMenuHandler {
|
||||
* @param mi An instance of MenuInterface that the method uses to change a
|
||||
* MenuItem's visibility
|
||||
* @param selectedItem The FeedItem for which the menu is supposed to be prepared
|
||||
* @param showExtendedMenu True if MenuItems that let the user share information about
|
||||
* the FeedItem and visit its website should be set visible. This
|
||||
* parameter should be set to false if the menu space is limited.
|
||||
* @param queueAccess Used for testing if the queue contains the selected item; only used for
|
||||
* move to top/bottom in the queue
|
||||
* @return Returns true if selectedItem is not null.
|
||||
*/
|
||||
public static boolean onPrepareMenu(MenuInterface mi,
|
||||
FeedItem selectedItem,
|
||||
boolean showExtendedMenu,
|
||||
@Nullable LongList queueAccess) {
|
||||
FeedItem selectedItem) {
|
||||
if (selectedItem == null) {
|
||||
return false;
|
||||
}
|
||||
boolean hasMedia = selectedItem.getMedia() != null;
|
||||
boolean isPlaying = hasMedia && selectedItem.getState() == FeedItem.State.PLAYING;
|
||||
boolean keepSorted = UserPreferences.isQueueKeepSorted();
|
||||
|
||||
if (!isPlaying) {
|
||||
mi.setItemVisibility(R.id.skip_episode_item, false);
|
||||
}
|
||||
|
||||
boolean isInQueue = selectedItem.isTagged(FeedItem.TAG_QUEUE);
|
||||
if (queueAccess == null || queueAccess.size() == 0 || queueAccess.get(0) == selectedItem.getId() || keepSorted) {
|
||||
mi.setItemVisibility(R.id.move_to_top_item, false);
|
||||
}
|
||||
if (queueAccess == null || queueAccess.size() == 0 || queueAccess.get(queueAccess.size()-1) == selectedItem.getId() || keepSorted) {
|
||||
mi.setItemVisibility(R.id.move_to_bottom_item, false);
|
||||
}
|
||||
if (!isInQueue) {
|
||||
mi.setItemVisibility(R.id.remove_from_queue_item, false);
|
||||
}
|
||||
@ -87,12 +75,12 @@ public class FeedItemMenuHandler {
|
||||
mi.setItemVisibility(R.id.add_to_queue_item, false);
|
||||
}
|
||||
|
||||
if (!showExtendedMenu || !ShareUtils.hasLinkToShare(selectedItem)) {
|
||||
if (!ShareUtils.hasLinkToShare(selectedItem)) {
|
||||
mi.setItemVisibility(R.id.visit_website_item, false);
|
||||
mi.setItemVisibility(R.id.share_link_item, false);
|
||||
mi.setItemVisibility(R.id.share_link_with_position_item, false);
|
||||
}
|
||||
if (!showExtendedMenu || !hasMedia || selectedItem.getMedia().getDownload_url() == null) {
|
||||
if (!hasMedia || selectedItem.getMedia().getDownload_url() == null) {
|
||||
mi.setItemVisibility(R.id.share_download_url_item, false);
|
||||
mi.setItemVisibility(R.id.share_download_url_with_position_item, false);
|
||||
}
|
||||
@ -104,6 +92,7 @@ public class FeedItemMenuHandler {
|
||||
boolean fileDownloaded = hasMedia && selectedItem.getMedia().fileExists();
|
||||
mi.setItemVisibility(R.id.share_file, fileDownloaded);
|
||||
|
||||
mi.setItemVisibility(R.id.remove_new_flag_item, selectedItem.isNew());
|
||||
if (selectedItem.isPlayed()) {
|
||||
mi.setItemVisibility(R.id.mark_read_item, false);
|
||||
} else {
|
||||
@ -114,7 +103,7 @@ public class FeedItemMenuHandler {
|
||||
mi.setItemVisibility(R.id.reset_position, false);
|
||||
}
|
||||
|
||||
if(!UserPreferences.isEnableAutodownload()) {
|
||||
if(!UserPreferences.isEnableAutodownload() || fileDownloaded) {
|
||||
mi.setItemVisibility(R.id.activate_auto_download, false);
|
||||
mi.setItemVisibility(R.id.deactivate_auto_download, false);
|
||||
} else if(selectedItem.getAutoDownload()) {
|
||||
@ -141,10 +130,8 @@ public class FeedItemMenuHandler {
|
||||
*/
|
||||
public static boolean onPrepareMenu(MenuInterface mi,
|
||||
FeedItem selectedItem,
|
||||
boolean showExtendedMenu,
|
||||
LongList queueAccess,
|
||||
int... excludeIds) {
|
||||
boolean rc = onPrepareMenu(mi, selectedItem, showExtendedMenu, queueAccess);
|
||||
boolean rc = onPrepareMenu(mi, selectedItem);
|
||||
if (rc && excludeIds != null) {
|
||||
for (int id : excludeIds) {
|
||||
mi.setItemVisibility(id, false);
|
||||
@ -153,8 +140,16 @@ public class FeedItemMenuHandler {
|
||||
return rc;
|
||||
}
|
||||
|
||||
public static boolean onMenuItemClicked(Context context, int menuItemId,
|
||||
FeedItem selectedItem) {
|
||||
/**
|
||||
* Default menu handling for the given FeedItem.
|
||||
*
|
||||
* A Fragment instance, (rather than the more generic Context), is needed as a parameter
|
||||
* to support some UI operations, e.g., creating a Snackbar.
|
||||
*/
|
||||
public static boolean onMenuItemClicked(@NonNull Fragment fragment, int menuItemId,
|
||||
@NonNull FeedItem selectedItem) {
|
||||
|
||||
@NonNull Context context = fragment.requireContext();
|
||||
switch (menuItemId) {
|
||||
case R.id.skip_episode_item:
|
||||
IntentUtils.sendLocalBroadcast(context, PlaybackService.ACTION_SKIP_CURRENT_EPISODE);
|
||||
@ -162,6 +157,9 @@ public class FeedItemMenuHandler {
|
||||
case R.id.remove_item:
|
||||
DBWriter.deleteFeedMediaOfItem(context, selectedItem.getMedia().getId());
|
||||
break;
|
||||
case R.id.remove_new_flag_item:
|
||||
removeNewFlagWithUndo(fragment, selectedItem);
|
||||
break;
|
||||
case R.id.mark_read_item:
|
||||
selectedItem.setPlayed(true);
|
||||
DBWriter.markItemPlayed(selectedItem, FeedItem.PLAYED, false);
|
||||
@ -216,14 +214,7 @@ public class FeedItemMenuHandler {
|
||||
DBWriter.setFeedItemAutoDownload(selectedItem, false);
|
||||
break;
|
||||
case R.id.visit_website_item:
|
||||
Uri uri = Uri.parse(FeedItemUtil.getLinkWithFallback(selectedItem));
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
if(IntentUtils.isCallable(context, intent)) {
|
||||
context.startActivity(intent);
|
||||
} else {
|
||||
Toast.makeText(context, context.getString(R.string.download_error_malformed_url),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
IntentUtils.openInBrowser(context, FeedItemUtil.getLinkWithFallback(selectedItem));
|
||||
break;
|
||||
case R.id.share_link_item:
|
||||
ShareUtils.shareFeedItemLink(context, selectedItem);
|
||||
@ -249,4 +240,39 @@ public class FeedItemMenuHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove new flag with additional UI logic to allow undo with Snackbar.
|
||||
*
|
||||
* Undo is useful for Remove new flag, given there is no UI to undo it otherwise
|
||||
* ,i.e., there is (context) menu item for add new flag
|
||||
*/
|
||||
public static void removeNewFlagWithUndo(@NonNull Fragment fragment, FeedItem item) {
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "removeNewFlagWithUndo(" + item.getId() + ")");
|
||||
// we're marking it as unplayed since the user didn't actually play it
|
||||
// but they don't want it considered 'NEW' anymore
|
||||
DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId());
|
||||
|
||||
final Handler h = new Handler(fragment.requireContext().getMainLooper());
|
||||
final Runnable r = () -> {
|
||||
FeedMedia media = item.getMedia();
|
||||
if (media != null && media.hasAlmostEnded() && UserPreferences.isAutoDelete()) {
|
||||
DBWriter.deleteFeedMediaOfItem(fragment.requireContext(), media.getId());
|
||||
}
|
||||
};
|
||||
|
||||
Snackbar snackbar = Snackbar.make(fragment.getView(), fragment.getString(R.string.removed_new_flag_label),
|
||||
Snackbar.LENGTH_LONG);
|
||||
snackbar.setAction(fragment.getString(R.string.undo), v -> {
|
||||
DBWriter.markItemPlayed(FeedItem.NEW, item.getId());
|
||||
// don't forget to cancel the thing that's going to remove the media
|
||||
h.removeCallbacks(r);
|
||||
});
|
||||
snackbar.show();
|
||||
h.postDelayed(r, (int) Math.ceil(snackbar.getDuration() * 1.05f));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,11 +20,13 @@ import java.util.Set;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.feed.FeedItemFilter;
|
||||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequestException;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import de.danoeh.antennapod.core.util.ShareUtils;
|
||||
import de.danoeh.antennapod.dialog.FilterDialog;
|
||||
|
||||
/**
|
||||
* Handles interactions with the FeedItemMenu.
|
||||
@ -84,14 +86,7 @@ public class FeedMenuHandler {
|
||||
conDialog.createNewDialog().show();
|
||||
break;
|
||||
case R.id.visit_website_item:
|
||||
Uri uri = Uri.parse(selectedFeed.getLink());
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
if(IntentUtils.isCallable(context, intent)) {
|
||||
context.startActivity(intent);
|
||||
} else {
|
||||
Toast.makeText(context, context.getString(R.string.download_error_malformed_url),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
IntentUtils.openInBrowser(context, selectedFeed.getLink());
|
||||
break;
|
||||
case R.id.share_link_item:
|
||||
ShareUtils.shareFeedlink(context, selectedFeed);
|
||||
@ -105,42 +100,15 @@ public class FeedMenuHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void showFilterDialog(final Context context, final Feed feed) {
|
||||
final String[] items = context.getResources().getStringArray(R.array.episode_filter_options);
|
||||
final String[] values = context.getResources().getStringArray(R.array.episode_filter_values);
|
||||
final boolean[] checkedItems = new boolean[items.length];
|
||||
|
||||
final Set<String> filter = new HashSet<>(Arrays.asList(feed.getItemFilter().getValues()));
|
||||
Iterator<String> it = filter.iterator();
|
||||
while(it.hasNext()) {
|
||||
// make sure we have no empty strings in the filter list
|
||||
if(TextUtils.isEmpty(it.next())) {
|
||||
it.remove();
|
||||
private static void showFilterDialog(Context context, Feed selectedFeed) {
|
||||
FilterDialog filterDialog = new FilterDialog(context, selectedFeed.getItemFilter()) {
|
||||
@Override
|
||||
protected void updateFilter(Set<String> filterValues) {
|
||||
selectedFeed.setItemFilter(filterValues.toArray(new String[filterValues.size()]));
|
||||
DBWriter.setFeedItemsFilter(selectedFeed.getId(), filterValues);
|
||||
}
|
||||
}
|
||||
for(int i=0; i < values.length; i++) {
|
||||
String value = values[i];
|
||||
if(filter.contains(value)) {
|
||||
checkedItems[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(R.string.filter);
|
||||
builder.setMultiChoiceItems(items, checkedItems, (dialog, which, isChecked) -> {
|
||||
if (isChecked) {
|
||||
filter.add(values[which]);
|
||||
} else {
|
||||
filter.remove(values[which]);
|
||||
}
|
||||
});
|
||||
builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
|
||||
feed.setItemFilter(filter.toArray(new String[filter.size()]));
|
||||
DBWriter.setFeedItemsFilter(feed.getId(), filter);
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel_label, null);
|
||||
builder.create().show();
|
||||
};
|
||||
|
||||
filterDialog.openDialog();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,27 +3,31 @@ package de.danoeh.antennapod.preferences;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import de.danoeh.antennapod.BuildConfig;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
|
||||
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
|
||||
|
||||
public class PreferenceUpgrader {
|
||||
private static final String PREF_CONFIGURED_VERSION = "configuredVersion";
|
||||
private static final String PREF_NAME = "PreferenceUpgrader";
|
||||
private static final String PREF_CONFIGURED_VERSION = "version_code";
|
||||
private static final String PREF_NAME = "app_version";
|
||||
|
||||
private static SharedPreferences prefs;
|
||||
|
||||
public static void checkUpgrades(Context context) {
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences upgraderPrefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
|
||||
int oldVersion = upgraderPrefs.getInt(PREF_CONFIGURED_VERSION, 1070200);
|
||||
int oldVersion = upgraderPrefs.getInt(PREF_CONFIGURED_VERSION, -1);
|
||||
int newVersion = BuildConfig.VERSION_CODE;
|
||||
|
||||
if (oldVersion != newVersion) {
|
||||
NotificationUtils.createChannels(context);
|
||||
AutoUpdateManager.restartUpdateAlarm();
|
||||
|
||||
upgraderPrefs.edit().putInt(PREF_CONFIGURED_VERSION, newVersion).apply();
|
||||
upgrade(oldVersion);
|
||||
upgraderPrefs.edit().putInt(PREF_CONFIGURED_VERSION, newVersion).apply();
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,12 +45,8 @@ public class PreferenceUpgrader {
|
||||
}
|
||||
}
|
||||
if (oldVersion < 1070300) {
|
||||
UserPreferences.restartUpdateAlarm();
|
||||
|
||||
if (UserPreferences.getMediaPlayer().equals("builtin")) {
|
||||
prefs.edit().putString(UserPreferences.PREF_MEDIA_PLAYER,
|
||||
UserPreferences.PREF_MEDIA_PLAYER_EXOPLAYER).apply();
|
||||
}
|
||||
prefs.edit().putString(UserPreferences.PREF_MEDIA_PLAYER,
|
||||
UserPreferences.PREF_MEDIA_PLAYER_EXOPLAYER).apply();
|
||||
|
||||
if (prefs.getBoolean("prefEnableAutoDownloadOnMobile", false)) {
|
||||
UserPreferences.setAllowMobileAutoDownload(true);
|
||||
@ -65,5 +65,13 @@ public class PreferenceUpgrader {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (oldVersion < 1070400) {
|
||||
int theme = UserPreferences.getTheme();
|
||||
if (theme == R.style.Theme_AntennaPod_Light) {
|
||||
prefs.edit().putString(UserPreferences.PREF_THEME, "system").apply();
|
||||
}
|
||||
|
||||
UserPreferences.setQueueLocked(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ public class SPAUtil {
|
||||
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putBoolean(PREF_HAS_QUERIED_SP_APPS, true);
|
||||
editor.commit();
|
||||
editor.apply();
|
||||
|
||||
return true;
|
||||
} else {
|
||||
@ -63,7 +63,7 @@ public class SPAUtil {
|
||||
SharedPreferences.Editor editor = PreferenceManager
|
||||
.getDefaultSharedPreferences(c.getApplicationContext()).edit();
|
||||
editor.putBoolean(PREF_HAS_QUERIED_SP_APPS, false);
|
||||
editor.commit();
|
||||
editor.apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
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. Reduziere Aufwand, Stromverbrauch und Datenverbrauch durch die Kontrolle der Downloads (bestimmte Uhrzeiten, Intervalle, WiFi-Netze) und des Löschens (basierend auf deinen Favoriten und weiteren Einstellungen).
|
||||
Aber am wichtigsten: Downloade, streame oder füge Episoden zur Abspielliste hinzu und genieße sie mit einstellbarer Abspielgeschwindigkeit, Unterstützung von Kapiteln und Schlummerfunktion. Mit Flattr kannst du den Podcastern sogar deine Wertschätzung zeigen.
|
||||
Aber am wichtigsten: Downloade, streame oder füge Episoden zur Abspielliste hinzu und genieße sie mit einstellbarer Abspielgeschwindigkeit, Unterstützung von Kapiteln und Schlummerfunktion.
|
||||
|
||||
AntennaPod ist, von Podcast-Enthusiasten gemacht, frei im Sinne des Wortes: Open Source, keine Kosten, keine Werbung.
|
||||
|
||||
<b>Alle Funktionen:</b><br>
|
||||
IMPORTIERE, ORGANISIERE UND HÖRE<br>
|
||||
• Importiere oder füge Feeds über das iTunes und gPodder.net Verzeichnis, OMPL Dateien und RSS oder Atom Links hinzu.
|
||||
• Bediene die Wiedergabe von überall: Homescreen-Widget, Benachrichtigung und Koopfhörer- und Bluetooth-Bedienelementen<br>
|
||||
• Bediene die Wiedergabe von überall: Startbildschirm-Widget, Benachrichtigung und Kopfhörer- und Bluetooth-Bedienelemente<br>
|
||||
• Genieße das Zuhören auf deine Art mit einstellbarer Abspielgeschwindigkeit, der Unterstützung von Kapiteln (MP3, OGG, Podlove) und ausgereifter Schlummerfunktion (durch Schütteln zurücksetzen, Lautstärke verringern und Geschwindigkeit verlangsamen)
|
||||
• Greife auf Passwort-geschützte Feeds und Episoden zu<br>
|
||||
• Nutze den Vorteil von Paged Feeds (http://www.podlove.org/paged-feeds)
|
||||
@ -15,9 +15,8 @@ ORDNE, TEILE & GENIEßE
|
||||
• Bleib an den Besten der Besten dran, indem Du Episoden als Favoriten markierst<br>
|
||||
• Finde Episoden durch die Liste zuletzt gespielter Episoden oder durch Suche in Titel und Shownotes
|
||||
• Teile Episoden and Feeds über soziale Medien, E-Mail, den gPodder.net-Dienst oder als OPML-Export
|
||||
• Unterstütze die Autoren von Inhalten mit Flattr (inklusive automatischem Flattren)
|
||||
|
||||
STEUER DAS SYSTEM<br>
|
||||
STEUERE DAS SYSTEM<br>
|
||||
• Kontrolliere automatisches Herunterladen: Wähle Feeds aus, schließe mobile Netze aus, suche bestimmte WiFi-Netze aus, setze voraus, dass das Smartphone geladen wird und lege Zeitpunkte oder Intervalle fest<br>
|
||||
• Verwalte deinen Speicherplatz durch das Festlegen der Anzahl gespeicherter Episoden, schlaues Löschen und durch Auswahl des Speicherortes<br>
|
||||
• Benutze AntennaPod in deiner Sprache (EN, DE, CS, NL, NB, JA, PT, ES, SV, CA, UK, FR, KO, TR, ZH)<br>
|
||||
|
1
app/src/main/play/listings/de-DE/title.txt
Normal file
1
app/src/main/play/listings/de-DE/title.txt
Normal file
@ -0,0 +1 @@
|
||||
AntennaPod
|
BIN
app/src/main/play/listings/en-US/graphics/icon/icon_play.png
Normal file
BIN
app/src/main/play/listings/en-US/graphics/icon/icon_play.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
42
app/src/main/play/listings/es-ES/full-description.txt
Normal file
42
app/src/main/play/listings/es-ES/full-description.txt
Normal file
@ -0,0 +1,42 @@
|
||||
AntennaPod es un reproductor y administrador de pódcast que te da acceso instantáneo a millones de pódcast gratuitos y de pago, desde editores independientes a grandes editoriales como la BBC, NPR y CNN. Añade, importa y exporta tus fuentes sin complicaciones usando la base de datos de pódcast de iTunes, archivos OPML o las URL de tipo RSS. Ahorra esfuerzo, energía de la batería y uso de datos móviles con potentes controles de automatización para descargar episodios (especifica horarios, intervalos y redes wifi) y elimina episodios (según tus favoritos y la configuración de retardo).<br>
|
||||
Y lo que es más importante: Descarga, escucha en stream, o añade la cola episodios y disfrútalos como quieras con velocidad de reproducción ajustable, soporte para capítulos y temporizador de sueño.
|
||||
|
||||
Creado por entusiastas del pódcast, AntennaPod es libre en todos los sentidos: código abierto, gratuito y sin publicidad.
|
||||
|
||||
<b>Todas las características:</b><br>
|
||||
IMPORTAR, ORGANIZAR Y REPRODUCIR<br>
|
||||
• Añade e importa fuentes mediante los directorios de iTunes y gPodder.net, archivos OPML y enlaces RSS o Atom<br>
|
||||
• Administra la reproducción desde cualquier parte: control en pantalla de inicio, notificación del sistema y controles de auricular y bluetooth<br>
|
||||
• Disfruta escuchando a tu manera con velocidad de reproducción ajustable, soporte de capítulos (MP3, VorbisComment y Podlove), recordatorio del punto de reproducción y el temporizador de sueño avanzado (agita para restablecer, bajar el volumen y disminuir la velocidad de reproducción)<br>
|
||||
• Accede a fuentes y episodios protegidos con contraseña<br>
|
||||
• Aprovecha las fuentes paginadas (www.podlove.org/paged-feeds)
|
||||
|
||||
MANTÉN UN SEGUIMIENTO, COMPARTE Y APRECIA
|
||||
• Haz un seguimiento de lo mejor de lo mejor marcando episodios como favoritos<br>
|
||||
• Encuentra ese episodio a través del historial de reproducción o por búsqueda (títulos y notas de episodios)<br>
|
||||
• Comparte episodios y fuentes a través de las avanzadas redes sociales y opciones de correo electrónico, los servicios de gPodder.net y la exportación OPML<br>
|
||||
|
||||
CONTROLA EL SISTEMA<br>
|
||||
• Controla las descargas automáticas: elige las fuentes, excluye las redes móviles, selecciona redes wifi específicas, o solo cuando el teléfono se esté cargando y establece horarios o intervalos<br>
|
||||
• Administra el almacenamiento configurando la cantidad de episodios almacenados, el borrado inteligente (según tus favoritos y el estado de reproducción) y selecciona tu ubicación preferida<br>
|
||||
• Usa AntennaPod en tu idioma (EN, DE, CS, NL, NB, JA, PT, ES, SV, CA, UK, FR, KO, TR, ZH)<br>
|
||||
• Adáptate a tu entorno usando el tema claro u oscuro<br>
|
||||
• Haz una copia de seguridad de tus suscripciones con la integración de gPodder.net y la exportación OPML
|
||||
|
||||
<b>¡Únete a la comunidad AntennaPod!</b><br>
|
||||
AntennaPod está en continuo desarrollo por voluntarios. ¡Tú también puedes contribuir, con tu código o con tus comentarios!
|
||||
|
||||
GitHub es el sitio que debes visitar para solicitar características nuevas, reportar fallos y contribuir con código<br>
|
||||
https://www.github.com/AntennaPod/AntennaPod
|
||||
|
||||
Nuestro Grupo de Google es el sitio para compartir tus ideas, momentos favoritos de tus pódcast y tu gratitud a los voluntarios:<br>
|
||||
https://groups.google.com/forum/#!forum/antennapod
|
||||
|
||||
¿Tienes una pregunta o quieres darnos tu opinión?
|
||||
https://twitter.com/@AntennaPod
|
||||
|
||||
Transifex es el sitio para ayudar con las traducciones:<br>
|
||||
https://www.transifex.com/antennapod/antennapod
|
||||
|
||||
Echa un vistazo a nuestro programa de pruebas Beta y ser el primero en usar las nuevas características:<br>
|
||||
https://www.github.com/AntennaPod/AntennaPod/wiki/Help-test-AntennaPod
|
1
app/src/main/play/listings/es-ES/short-description.txt
Normal file
1
app/src/main/play/listings/es-ES/short-description.txt
Normal file
@ -0,0 +1 @@
|
||||
Reproductor y gestor de pódcast fácil de usar, flexible y de código abierto
|
1
app/src/main/play/listings/es-ES/title.txt
Normal file
1
app/src/main/play/listings/es-ES/title.txt
Normal file
@ -0,0 +1 @@
|
||||
AntennaPod
|
@ -1,5 +1,5 @@
|
||||
AntennaPod est un lecteur et gestionnaire de podcast permettant l'accès à des millions de podcast gratuits ou payants produit aussi bien par des podcasters indépendants que de gros éditeurs comme la BBC, NPR ou CNN. Ajoutez, importez et exportez leurs flux facilement à partir d'ITunes, de fichiers OPML ou simplement à partir de liens RSS. Gagnez du temps, préservez votre batterie et consommation internet grâce à une automatisation puissante des téléchargements (date, fréquence, choix du réseau WiFi, etc...) et des suppressions d’épisodes écoutés (selon vos critères)<br>
|
||||
Avant tout : téléchargez, streamez ou ajoutez à la liste de lecture vos épisodes et écoutez les comme vous voulez grâce au réglage de vitesse de lecture, au support des chapitres et au minuteur d'arrêt. Vous pouvez même montrer votre appréciation aux créateurs de contenu avec notre intégration de Flattr.
|
||||
AntennaPod est un lecteur de podcast permettant l'accès à des millions de podcast gratuits ou payants produit aussi bien par des podcasters indépendants que des éditeurs professionnels comme la BBC, NPR ou CNN. Ajoutez, importez et exportez leurs flux facilement à partir d'ITunes, de fichiers OPML ou simplement à partir de liens RSS. Economisez votre temps, votre batterie et votre consommation de données grâce à l'automatisation des téléchargements (date, fréquence, choix du réseau WiFi, etc...) et de la suppression des épisodes (selon vos critères)<br>
|
||||
Avant tout : téléchargez, streamez ou ajoutez à la liste de lecture vos épisodes et écoutez les comme vous voulez grâce au réglage de vitesse de lecture, au support des chapitres et au minuteur d'arrêt.
|
||||
|
||||
Conçu par des fans de podcast, AntennaPod est gratuit dans tous les sens du terme : open source, gratuit et sans publicité.
|
||||
|
||||
@ -15,7 +15,6 @@ SUIVEZ, PARTAGEZ & PROFITEZ<br>
|
||||
• Marquer les meilleurs épisodes en tant que favoris<br>
|
||||
• Retrouvez un épisode à partir de l'historique de lecture ou en recherchant parmi les titres et commentaires des épisodes précédents<br>
|
||||
• Partagez vos épisodes et flux sur les réseaux sociaux, par email, sur gPodder.net ou en les exportant au format OPML<br>
|
||||
• Soutenez les créateurs de contenu avec l'intégration à Flattr et la possibilité de flatter automatiquement
|
||||
|
||||
CONTRÔLER LE SYSTÈME<br>
|
||||
• Prenez le contrôle en automatisant vos téléchargements : choix des flux, restriction de la connexion mobile, sélection du réseau WIFI à utiliser, uniquement durant la recharge et spécifiez la fréquence de mise à jour vous-même<br>
|
||||
|
@ -1 +1 @@
|
||||
Un lecteur et gestionnaire de podcast facile à utiliser et flexible
|
||||
Un lecteur de podcast facile à utiliser et flexible
|
1
app/src/main/play/listings/fr-FR/title.txt
Normal file
1
app/src/main/play/listings/fr-FR/title.txt
Normal file
@ -0,0 +1 @@
|
||||
AntennaPod
|
@ -1,5 +1,5 @@
|
||||
AntenaPod é un xestor de podcast e reprodutor que lle da acceso a millóns de podcast tanto gratuítos como de pagamento, desde podcasters independentes a grandes productores como BBC, NPR e CNN. Engada, importe e exporte as súas fontes de xeito doado utilizando a base de datos de iTunes, ficheiros OPML ou URLs RSS. Aforre traballo, enerxía da batería e datos móbiles co sistema automatizado de control das descargas de episodios (indicando horario, intervalos e redes WiFi) e borrando episodios (baseado nos seus favoritos e axustes de retardo).<br>
|
||||
Mais o máis importante: Descargue, envíe ou poña na cola os episodios e disfrúteos do xeito en que máis lle conveña, axustando a velocidade de reprodución, xestión de capítulos e apagado automático. Poderá tamén mostrarlle aos creadores canto lle gustan os seus contidos gracias a integración con Flattr.
|
||||
O máis importante: Descarga, reproduce ou pon en cola os episodios e desfrútaos do xeito en que máis che conveña axustando a velocidade de reprodución, o soporte de capítulos e o apagado programable.
|
||||
|
||||
Escrito por namorados dos podcast, AntennaPod é gratuíto e libre: open source, sen custos, sen publicidade.
|
||||
|
||||
@ -15,7 +15,6 @@ SIGA, COMPARTA E VALORE <br>
|
||||
• Garde o melloriño de cada casa marcando episodios como favoritos<br>
|
||||
• Atope ese episodio especial no historial de reprodución ou buscando (títulos e notas do episodio)<br>
|
||||
• Comparta episodios e fontes a través das opcións de redes sociais e correro electrónico, os servizos de gPodder.net e exportando a OPML<br>
|
||||
• Axude aos creadores de contido coa integración con Flattr incluíndo o flattring automático
|
||||
|
||||
CONTROL DO SISTEMA <br>
|
||||
• Tome control sobre as descargas automáticas: escolla fontes, exclúa redes móbiles, redes WIFI concretas, requerir que o móbil esté a cargar e horarios e intervalos<br>
|
||||
|
1
app/src/main/play/listings/gl-ES/title.txt
Normal file
1
app/src/main/play/listings/gl-ES/title.txt
Normal file
@ -0,0 +1 @@
|
||||
AntennaPod
|
1
app/src/main/play/listings/it-IT/title.txt
Normal file
1
app/src/main/play/listings/it-IT/title.txt
Normal file
@ -0,0 +1 @@
|
||||
AntennaPod
|
@ -1,5 +1,5 @@
|
||||
היישומון אנטנה־פּוֹד הוא נגן ומנהל פודקאסטים שמעניק לך גישה ישירה למיליונים של פודקאסטים בחינם ובתשלום, החל ממגישי פודקאסטים עצמאיים ועד למפיצים גדולים כגון BBC, NPR ו־CNN. ניתן להוסיף, לייבא ולייצא את ההזנות שלהם בקלות יחסית באמצעות מסד נתוני הפודקאסטים של iTunes, קובצי OPML או כתובות של RSS. מאפשר לך לחסוך במאמץ, סוללה ותקשורת נתונים עם פקדי אוטומציה להורדה של פרקים (לפי זמנים, הפרשי זמן ורשתות אלחוטיות) ומחיקה של פרקים (על בסיס הגדרות המועדפים וההשהיה שלך).<br>
|
||||
אבל הכי חשוב: ניתן להוריד, להזרים או לסדר רשימות של פרקים וליהנות מהם בכל דרך שמתאימה לך עם מהירויות נגינה משתנות, תמיכה במקטעים ומתזמן שינה. ניתן אפילו להביע את חיבתך ליוצרי התוכן עם שילוב של Flattr ביישומון.
|
||||
אבל הכי חשוב: ניתן להוריד, להזרים או לסדר רשימות של פרקים וליהנות מהם בכל דרך שמתאימה לך עם מהירויות נגינה משתנות, תמיכה במקטעים ומתזמן שינה
|
||||
|
||||
מיוצרת על ידי חובבי פודקאסטים, אנטנהפוד הינה תוכנה חינמית בכל מובן המילה: קוד פתוח, ללא עלות וללא פרסומות.
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
• מעקב אחר הטובים שבטובים על ידי סימון פרקים כמועדפים<br>
|
||||
• ניתן לאתר פרק אחד דרך היסטוריית הנגינה או על ידי חיפוש (כותרות והערות פרק)<br>
|
||||
• ניתן לשתף פרקים והזנות דרך אפשרויות מתקדמות ברשתות חברתיות ודוא״ל, שירותי gPodder.net ודרך ייצוא OPML<br>
|
||||
• ניתן לתמוך בעורכי תוכן עם שילוב של Flattr לתוך המערכת לרבות תרומה אוטומטית
|
||||
|
||||
שליטה במערכת<br>
|
||||
• ניתן לשלוט על הורדה אוטומטית: לבחור הזנות, להחריג רשתות סלולריות, לבחור רשתות אלחוטיות מסוימות, לדרוש מהטלפון להיות בטעינה ולהגדיר מועדים או מרווחי זמן<br>
|
1
app/src/main/play/listings/iw-IL/title.txt
Normal file
1
app/src/main/play/listings/iw-IL/title.txt
Normal file
@ -0,0 +1 @@
|
||||
AntennaPod
|
@ -1,5 +1,5 @@
|
||||
AntennaPodは、独自のポッドキャスターから、BBC、NPR、CNNなどの大規模な放送まで、数百万の無料や有料ポッドキャストに瞬時にアクセスすることができる、ポッドキャストマネージャーおよびプレーヤーです。フィードは手間のかからないiTunesのPodcastのデータベース、OPMLファイルや簡単なRSSのURLを使用して追加、インポート、エクスポートします。エピソードのダウンロード (時間、間隔およびWiFiネットワークを指定) とエピソードの削除 (お気に入りと遅延設定に基づいて) をするために強力な自動コントロールで、手間、バッテリ消費、モバイルデータ使用量を節約します。<br>
|
||||
しかし最も重要なこと: エピソードをダウンロード、ストリーム再生、またはキューに入れて、そして再生速度の調整、チャプターのサポート、スリープタイマーで好きなように楽しんでください。Flattr統合でコンテンツ作成者にあなたの愛を示すことができます。
|
||||
しかし最も重要なこと: エピソードをダウンロード、ストリーム再生、またはキューに入れて、そして再生速度の調整、チャプターのサポート、スリープタイマーで好きなように楽しんでください。
|
||||
|
||||
ポッドキャスト愛好家が作成した AntennaPod はすべての意味でフリー自由です: オープンソース、コスト不要、広告はありません。
|
||||
|
||||
@ -15,7 +15,6 @@ AntennaPodは、独自のポッドキャスターから、BBC、NPR、CNNなど
|
||||
• エピソードをお気に入りとしてマークして、一番の中の一番を保存してください<br>
|
||||
• 再生履歴から、または検索 (タイトルとショーノート) して目的のエピソードを見つけてください<br>
|
||||
• 高度なソーシャルメディアとメールオプション、gPodder.netサービス、OPMLエクスポートからエピソードやフィードを共有してください<br>
|
||||
• 自動Flattrを含むFlattrの統合でコンテンツクリエイターをサポートします
|
||||
|
||||
システムのコントロール
|
||||
• 自動ダウンロードの制御: フィードを選択、モバイルネットワークを除外、特定のWiFiネットワークを選択、電話を充電する必要、時間や間隔を設定<br>
|
||||
|
1
app/src/main/play/listings/ja-JP/title.txt
Normal file
1
app/src/main/play/listings/ja-JP/title.txt
Normal file
@ -0,0 +1 @@
|
||||
AntennaPod
|
@ -1,5 +1,5 @@
|
||||
Met AntennaPod speel en beheer je al je podcasts en krijg je directe toegang tot duizenden gratis en betaalde podcasts - van onafhankelijke makers tot grote merken zoals BBC, CNN en NPO. Via de iTunes-databank, OPML-bestanden en simpele RSS-linkjes voeg je deze podcasts gemakkelijk toe. Dankzij simpele maar slimme automatische controle van het downloaden en verwijderen van afleveringen bespaar je de accu, hoef je je favoriete podcast niet meer handmatig te volgen en verbruik je geen onnodige mobiele gegevens.<br>
|
||||
Maar belangrijker: Met een handige wachtrij, aanpasbare afspeelsnelheden, ondersteuning van hoofdstukken en een slaap timer luister je naar podcasts op de manier die jij prettig vindt. Je kunt zelfs je liefde voor de makers uiten met onze Flattr-integratie.
|
||||
Maar belangrijker: download, stream of voeg afleveringen toe aan de wachtrij en geniet ervan! Je hebt beschikking over afspeelsnelheden, hoofdstukondersteuning en een slaaptimer.
|
||||
|
||||
Gemaakt door podcast-enthousiastelingen, AntennaPod is vrij in de breedste zin van het woord: vrij van advertenties, open source en gratis.
|
||||
|
||||
@ -15,7 +15,6 @@ DEEL & WAARDEER<br>
|
||||
• Hou het beste van het beste bij door afleveringen als favoriet te markeren<br>
|
||||
• Vindt die ene aflevering terug in de afspeelgeschiedenis of door te zoeken (in titels, shownotes en makers)<br>
|
||||
• Deel podcasts en afleveringen via uitgebreide opties voor sociale media, WhatsApp, email en gPodder.net<br>
|
||||
• Steun makers met Flattr-integratie, inclusief auto-Flattr
|
||||
|
||||
HOUD DE CONTROLE
|
||||
• Beheer automatische downloads: kies je podcasts, sluit mobiele netwerken uit, selecteer specifieke WiFi-verbindingen, eis dat de telefoon wordt opgeladen and bepaal tijden of intervals<br>
|
||||
|
1
app/src/main/play/listings/nl-NL/title.txt
Normal file
1
app/src/main/play/listings/nl-NL/title.txt
Normal file
@ -0,0 +1 @@
|
||||
AntennaPod
|
42
app/src/main/play/listings/sv-SE/full-description.txt
Normal file
42
app/src/main/play/listings/sv-SE/full-description.txt
Normal file
@ -0,0 +1,42 @@
|
||||
AntennaPod är en podcasthanterare och spelare som ger dig omedelbar tillgång till miljoner av gratis och betalda podcasts, från oberoende podcastare till stora publiceringshus så som BBC, NPR och CNN. Lägg till, importera och exportera enkelt deras flöden med iTunes podcastdatabas, OPML-filer eller vanliga RSS URL:er. Spara möda, batterikraft och mobildata med kraftfulla automatiseringskontroller för nedladdning (välj tider, intervall och WiFi-nätverk) och borttagning av episoder (baserat på dina favoriter och fördröjningsinställningar).
|
||||
Men viktigast av allt: Ladda ner, strömma eller köa episoder och avnjut dem på det sätt du gillar med justerbar uppspelningshastighet, kapitelstöd och insomningstimer.
|
||||
|
||||
Gjord av podcastenthusiaster, AntennaPod är fri i alla ordets bemärkelser: öppen källkod, inga kostnader, ingen reklam.
|
||||
|
||||
<b>Alla funktioner:</b><br>
|
||||
IMPORTERA, ORGANISERA OCH SPELA<br>
|
||||
• Lägg till och importera flöden via iTunes och gPodder.net, OPML filer och RSS eller Atom länkar<br>
|
||||
• Hantera uppspelningen från vartsomhelst: hemskärmswidget, aviseringsfältet och hörlurs/bluetoth-kontroller<br>
|
||||
• Njut av att lyssna på ditt sätt med justerbar uppspelningshastighet, kapitelstöd (MP4, VorbisComment och Podlove), ihågkommen uppspelningsposition och en avancerad sömntimer (skaka för återställaning, sänk volymen och sänk hastigheten)<br>
|
||||
• Kom åt lösenordsskyddade flöden och episoder<br>
|
||||
• Dra nytta av siduppdelade flöden (www.podlove.ord/paged-feeds)
|
||||
|
||||
SPÅRA, DELA & UPPSKATTA<br>
|
||||
• Håll ordning på de bästa av de bästa med favoritmarkering av episoder<br>
|
||||
• Hitta just den där episoden i uppspelningshistoriken eller genom sökning (titel och shownotes)<br>
|
||||
• Dela episoder och flöden med avancerade vald för social media och och email, tjänsten gPodder.net och via OPML export<br>
|
||||
|
||||
KONTROLLERA SYSTEMET<br>
|
||||
• Ta kontroll över automatisk nedladdning: välj flöden, exkludera mobilnätverk, välj specifika WiFi nätverk, kräv att telefonen är inkopplad för laddning och sätt tider eller intervall för körning<br>
|
||||
• Hantera lagringsutrymme genom att välja antalet cachade episoder, smart borttagning (baserat på dina favoriter och uppspelningsstatus) och välj den lagringsplats du föredrar<br>
|
||||
• Använd AntennaPod på ditt språk (EN, DE, CS, NL, NB, JA, PT, ES, SV, CA, UK, FR, KO, TR, ZH)<br>
|
||||
• Anpassa till din omgivning med det ljusa och mörka temat<br>
|
||||
• Ta backup av dina prenumerationer med integreringen av gPodder.net och OPML exportering
|
||||
|
||||
<b>Gå med i AntennaPods gemenskap!</b><br>
|
||||
AntennaPod är under aktiv utveckling av volontärer. Du kan också bidra, med kod eller kommentarer!
|
||||
|
||||
GitHub är platsen att gå till för att be om funktioner, skapa buggrapporter eller bidra med kod:<br>
|
||||
https://www.github.com/AntennaPod/AntennaPod
|
||||
|
||||
Vår Google Group är platsen för att dela idéer, dina favoritögonblick med podcasting och din uppskattning till volontärerna:<br>
|
||||
https://groups.google.com/forum/#!forum/antennapod
|
||||
|
||||
Har du frågor eller vill ge feedback?
|
||||
https://twitter.com/@AntennaPod
|
||||
|
||||
Transifex är platsen att gå till för att hjälpa till med översättningen:<br>
|
||||
https://www.transifex.com/antennapod/antennapod
|
||||
|
||||
Kolla in vårat Beta Testing program för att få de senaste funktionerna först:<br>
|
||||
https://www.github.com/AntennaPod/AntennaPod/wiki/Help-test-AntennaPod
|
1
app/src/main/play/listings/sv-SE/short-description.txt
Normal file
1
app/src/main/play/listings/sv-SE/short-description.txt
Normal file
@ -0,0 +1 @@
|
||||
Användarvänlig och flexibel podcasthanterare och spelare med öppen källkod
|
1
app/src/main/play/listings/sv-SE/title.txt
Normal file
1
app/src/main/play/listings/sv-SE/title.txt
Normal file
@ -0,0 +1 @@
|
||||
AntennaPod
|
42
app/src/main/play/listings/zh-CN/full-description.txt
Normal file
42
app/src/main/play/listings/zh-CN/full-description.txt
Normal file
@ -0,0 +1,42 @@
|
||||
AntennaPod 是一款播客管理器和播放器,可让您即时收听数百万免费和付费的播客,从独立播客到大型出版商如BBC,NPR和CNN;使用 iTunes 播客数据库,OPML文件或简单的RSS URL轻松添加,导入和导出 feeds ;通过强大的自动化控制功能节省工作量,电池电量和移动数据使用情况,以便下载剧集(指定时间,间隔和 WiFi 网络)和删除剧集(根据您的收藏和延迟设置)。
|
||||
但最重要的是:下载,串流或安排节目并用可调节回放速度,章节支持和睡眠定时器以您喜欢的方式享受它们。
|
||||
|
||||
AntennaPod由播客爱好者开发,在任意方面上都是自由的:开源,免费,无广告。
|
||||
|
||||
<b>所有功能:</b><br>
|
||||
导入、整理与播放<br>
|
||||
• 通过 iTunes、gPodder.net、OPML 文件、RSS 或 Atom 链接添加和导入订阅
|
||||
•;从任意地点管理回放:主屏widget,系统通知,耳塞和蓝牙控制装置
|
||||
•;借助可调节回放速度,章节支持(Mp3、Vorbis注释和Podlove),记忆的回放位置和一个高级的睡眠定时器 (摇动设备来重置、调低音量并减慢回放)以您自己的方式享受收听播客节目
|
||||
#8226; 访问受密码保护的 feeds 和剧集
|
||||
• 充分利用分页 feeds (www.podlove.org/paged-feeds)
|
||||
|
||||
保持更新,分享&欣赏
|
||||
&#8226; 通过将剧集标记为收藏来追踪最佳的剧集
|
||||
• 通过回放历史或搜索(标题和节目笔记)找到您需要的那期节目
|
||||
• 通过高级社交媒体和电邮选项,gPodder.net服务和导出OPML文件来分享节目和源
|
||||
|
||||
一切尽在掌控<br>
|
||||
• 控制自动下载:选择源,排除移动网络,选择特定无线网络,要求手机充电并设定次数或间隔
|
||||
• 通过设定缓存节目的数量管理存储,智能删除(基于您的喜好和播放状态)并选择您偏好的存储位置
|
||||
• 使用对应您的语言的 AntennaPod (EN, DE, CS, NL, NB, JA, PT, ES, SV, CA, UK, FR, KO, TR, ZH)<br>
|
||||
&#8226; 使用浅色和深色主题以适应您的环境
|
||||
&#8226; 使用 gPodder.net 集成功能和 OPML 导出以备份您的订阅
|
||||
|
||||
<b>加入 AntennaPod 社区!</b><br>
|
||||
志愿者正在积极开发 AntennaPod 。您也可以通过代码或评论做出贡献!
|
||||
|
||||
GitHub 是申请增加功能,报告错误和贡献代码的地方:
|
||||
https://www.github.com/AntennaPod/AntennaPod
|
||||
|
||||
对于所有志愿者来说,我们的谷歌社群是分享你的创意,收藏的播客瞬间和表达感激的地方:
|
||||
https://groups.google.com/forum/#!forum/antennapod
|
||||
|
||||
抱有疑问或者想要向我们提供反馈?
|
||||
https://twitter.com/@AntennaPod
|
||||
|
||||
Transifex 是与翻译者帮助项目的网站:
|
||||
https://www.transifex.com/antennapod/antennapod
|
||||
|
||||
查看我们的测试程序以获得最新的功能列表:
|
||||
https://www.github.com/AntennaPod/AntennaPod/wiki/Help-test-AntennaPod
|
@ -1 +1 @@
|
||||
简单易用、灵活的开源播客管理工具与播放器
|
||||
易用、灵活的开源播客管理工具与播放器
|
1
app/src/main/play/listings/zh-CN/title.txt
Normal file
1
app/src/main/play/listings/zh-CN/title.txt
Normal file
@ -0,0 +1 @@
|
||||
AntennaPod
|
@ -4,4 +4,5 @@
|
||||
- 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)
|
||||
- Tons of bug fixes and performance improvements (by @andersonvom, @archibishop, @ByteHamster, @gaul, @jas14)
|
||||
- Added filter to "All episodes" list (by @jhunnius)
|
||||
- Tons of bug fixes and performance improvements
|
||||
|
@ -10,6 +10,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:focusableInTouchMode="true"
|
||||
android:padding="8dp">
|
||||
|
||||
<android.support.v7.widget.CardView
|
||||
@ -27,6 +28,8 @@
|
||||
<ImageView
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:contentDescription="@string/search_podcast_hint"
|
||||
app:srcCompat="?attr/action_search"
|
||||
android:scaleType="center"/>
|
||||
@ -39,8 +42,10 @@
|
||||
android:inputType="text"
|
||||
android:imeOptions="actionSearch"
|
||||
android:importantForAutofill="no"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginLeft="0dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:hint="@string/search_podcast_hint"
|
||||
@ -109,16 +114,17 @@
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/advanced_search"
|
||||
android:layout_width="96dp"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="16dp"
|
||||
android:padding="8dp"
|
||||
android:background="?android:attr/selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
@ -127,8 +133,7 @@
|
||||
android:contentDescription="@string/advanced_search"
|
||||
app:srcCompat="?attr/action_search"
|
||||
android:scaleType="center"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:tint="?android:attr/textColorPrimary"/>
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
@ -140,11 +145,11 @@
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/btn_opml_import"
|
||||
android:layout_width="96dp"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="16dp"
|
||||
android:padding="8dp"
|
||||
android:background="?android:attr/selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
@ -153,8 +158,7 @@
|
||||
android:contentDescription="@string/opml_import_label"
|
||||
app:srcCompat="?attr/av_download"
|
||||
android:scaleType="center"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:tint="?android:attr/textColorPrimary"/>
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
|
@ -1,19 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<TextView
|
||||
android:id="@+id/txtvInformation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:visibility="gone"
|
||||
tools:text="(i) Information" />
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@android:id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@+id/txtvInformation"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingTop="@dimen/list_vertical_padding"
|
||||
android:paddingBottom="@dimen/list_vertical_padding"
|
||||
android:scrollbarStyle="outsideOverlay"
|
||||
app:fastScrollEnabled="true"
|
||||
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
|
||||
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
|
||||
tools:itemCount="13"
|
||||
tools:listitem="@layout/new_episodes_listitem" />
|
||||
|
||||
@ -21,12 +39,13 @@
|
||||
android:id="@+id/progLoading"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:indeterminateOnly="true"
|
||||
android:visibility="gone"
|
||||
android:layout_centerInParent="true"
|
||||
tools:visibility="gone"
|
||||
tools:layout_width="match_parent"
|
||||
tools:layout_height="64dp"
|
||||
tools:background="@android:color/holo_red_light"/>
|
||||
|
||||
</FrameLayout>
|
||||
</RelativeLayout>
|
28
app/src/main/res/layout/bug_report.xml
Normal file
28
app/src/main/res/layout/bug_report.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?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">
|
||||
<Button
|
||||
android:id="@+id/btn_open_bug_tracker"
|
||||
android:text="@string/open_bug_tracker"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_copy_log"
|
||||
android:text="@string/copy_to_clipboard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:layout_marginTop="8dp"
|
||||
android:id="@+id/crash_report_logs"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="12sp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
</LinearLayout>
|
17
app/src/main/res/layout/checkbox_do_not_show_again.xml
Normal file
17
app/src/main/res/layout/checkbox_do_not_show_again.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?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="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<CheckBox
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/checkbox_do_not_show_again"
|
||||
android:text="@string/checkbox_do_not_show_again"/>
|
||||
|
||||
</LinearLayout>
|
@ -17,7 +17,7 @@
|
||||
android:layout_marginTop="@dimen/listitem_threeline_verticalpadding"
|
||||
android:contentDescription="@string/cover_label"
|
||||
android:scaleType="centerCrop"
|
||||
tools:src="@drawable/ic_stat_antenna_default"
|
||||
tools:src="@drawable/ic_antenna"
|
||||
tools:background="@android:color/holo_green_dark"/>
|
||||
|
||||
|
||||
|
@ -29,17 +29,14 @@
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:elevation="@dimen/sd_close_elevation"
|
||||
tools:ignore="UnusedAttribute">
|
||||
<!-- android:elevation:
|
||||
1. Needs to match the speed dial's minimal elevation,
|
||||
or the speed dial can't be clicked at all
|
||||
-->
|
||||
android:elevation="@dimen/sd_open_elevation"
|
||||
tools:ignore="UnusedAttribute" >
|
||||
|
||||
<com.leinardi.android.speeddial.SpeedDialView
|
||||
android:id="@+id/fabSD"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:sdMainFabClosedSrc="@drawable/ic_fab_edit"
|
||||
app:sdMainFabClosedSrc="?attr/batch_edit_fab_icon"
|
||||
app:sdOverlayLayout="@id/fabSDOverlay"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
|
@ -36,7 +36,8 @@
|
||||
android:layout_marginBottom="16dp"
|
||||
android:contentDescription="@string/cover_label"
|
||||
android:gravity="center_vertical"
|
||||
tools:src="@drawable/ic_stat_antenna_default"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
tools:src="@drawable/ic_antenna"
|
||||
tools:background="@android:color/holo_green_dark" />
|
||||
|
||||
<TextView
|
||||
@ -47,6 +48,7 @@
|
||||
android:layout_alignTop="@id/imgvCover"
|
||||
android:layout_toRightOf="@id/imgvCover"
|
||||
android:layout_toEndOf="@id/imgvCover"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
tools:text="Podcast title"
|
||||
tools:background="@android:color/holo_green_dark" />
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user