Merge branch 'develop' into feat/simple-adjust-volume-per-feed

This commit is contained in:
H. Lehmann 2019-09-29 17:31:34 +02:00 committed by GitHub
commit 5bf7216064
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
310 changed files with 4926 additions and 3992 deletions

View File

@ -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
View File

@ -0,0 +1,2 @@
antennapod.description
antennapod.shortdescription

View File

@ -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>
&#8226; Adicione e importe fontes existentes nos diretórios iTunes e gPodder.net, ficheiros OPML e ligações ATOM e RSS<br>
&#8226; Gestão de podcasts através do widget, barra de notificações e controlos de auriculares ou auscultadores<br>
&#8226; 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>
&#8226; Acesso a fontes e episódios protegidos por palavra-passe<br>
&#8226; Possibilidade de subscrever fontes paginadas (www.podlove.org/paged-feeds)
Monitorização, partilha e suporte<br>
&#8226; Monitorize os seus podcasts preferidos marcando-os como favoritos<br>
&#8226; Localize um episódio através do histórico de reprodução ou através de uma pesquisa (títulos e notas)<br>
&#8226; Partilhe episódios e fontes nas redes sociais, por e-mail, no diretório gPodder.net ou através de ficheiros OPML<br>
&#8226; Ajude os criadores de conteúdos através do serviço Flattr
Controlo do sistema<br>
&#8226; 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>
&#8226; 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>
&#8226; Utilize o AntennaPod no seu idioma (EN, DE, CS, NL, NB, JA, PT, ES, SV, CA, UK, FR, KO, TR, ZH)<br>
&#8226; Adapte-se ao seu ambiente através dos temas claro ou escuro<br>
&#8226; 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

View File

@ -1 +0,0 @@
Gestor e reprodutor de podcasts simples, flexível e open souce

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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')
}
}

View File

@ -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)),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"

View File

@ -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";
}
}

View File

@ -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/", "");

View File

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

View File

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

View File

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

View File

@ -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 + ")");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View File

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

View File

@ -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 {

View File

@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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

View File

@ -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";

View File

@ -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

View File

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

View File

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

View File

@ -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

View File

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

View File

@ -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() {

View File

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

View File

@ -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() {

View File

@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

@ -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>
&#8226; Importiere oder füge Feeds über das iTunes und gPodder.net Verzeichnis, OMPL Dateien und RSS oder Atom Links hinzu.
&#8226; Bediene die Wiedergabe von überall: Homescreen-Widget, Benachrichtigung und Koopfhörer- und Bluetooth-Bedienelementen<br>
&#8226; Bediene die Wiedergabe von überall: Startbildschirm-Widget, Benachrichtigung und Kopfhörer- und Bluetooth-Bedienelemente<br>
&#8226; 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)
&#8226; Greife auf Passwort-geschützte Feeds und Episoden zu<br>
&#8226; Nutze den Vorteil von Paged Feeds (http://www.podlove.org/paged-feeds)
@ -15,9 +15,8 @@ ORDNE, TEILE & GENIEßE
&#8226; Bleib an den Besten der Besten dran, indem Du Episoden als Favoriten markierst<br>
&#8226; Finde Episoden durch die Liste zuletzt gespielter Episoden oder durch Suche in Titel und Shownotes
&#8226; Teile Episoden and Feeds über soziale Medien, E-Mail, den gPodder.net-Dienst oder als OPML-Export
&#8226; Unterstütze die Autoren von Inhalten mit Flattr (inklusive automatischem Flattren)
STEUER DAS SYSTEM<br>
STEUERE DAS SYSTEM<br>
&#8226; 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>
&#8226; Verwalte deinen Speicherplatz durch das Festlegen der Anzahl gespeicherter Episoden, schlaues Löschen und durch Auswahl des Speicherortes<br>
&#8226; Benutze AntennaPod in deiner Sprache (EN, DE, CS, NL, NB, JA, PT, ES, SV, CA, UK, FR, KO, TR, ZH)<br>

View File

@ -0,0 +1 @@
AntennaPod

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View 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>
&#8226; Añade e importa fuentes mediante los directorios de iTunes y gPodder.net, archivos OPML y enlaces RSS o Atom<br>
&#8226; Administra la reproducción desde cualquier parte: control en pantalla de inicio, notificación del sistema y controles de auricular y bluetooth<br>
&#8226; 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>
&#8226; Accede a fuentes y episodios protegidos con contraseña<br>
&#8226; Aprovecha las fuentes paginadas (www.podlove.org/paged-feeds)
MANTÉN UN SEGUIMIENTO, COMPARTE Y APRECIA
&#8226; Haz un seguimiento de lo mejor de lo mejor marcando episodios como favoritos<br>
&#8226; Encuentra ese episodio a través del historial de reproducción o por búsqueda (títulos y notas de episodios)<br>
&#8226; 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>
&#8226; 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>
&#8226; 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>
&#8226; Usa AntennaPod en tu idioma (EN, DE, CS, NL, NB, JA, PT, ES, SV, CA, UK, FR, KO, TR, ZH)<br>
&#8226; Adáptate a tu entorno usando el tema claro u oscuro<br>
&#8226; 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

View File

@ -0,0 +1 @@
Reproductor y gestor de pódcast fácil de usar, flexible y de código abierto

View File

@ -0,0 +1 @@
AntennaPod

View File

@ -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>
&#8226; Marquer les meilleurs épisodes en tant que favoris<br>
&#8226; Retrouvez un épisode à partir de l'historique de lecture ou en recherchant parmi les titres et commentaires des épisodes précédents<br>
&#8226; Partagez vos épisodes et flux sur les réseaux sociaux, par email, sur gPodder.net ou en les exportant au format OPML<br>
&#8226; Soutenez les créateurs de contenu avec l'intégration à Flattr et la possibilité de flatter automatiquement
CONTRÔLER LE SYSTÈME<br>
&#8226; 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>

View File

@ -1 +1 @@
Un lecteur et gestionnaire de podcast facile à utiliser et flexible
Un lecteur de podcast facile à utiliser et flexible

View File

@ -0,0 +1 @@
AntennaPod

View File

@ -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>
&#8226; Garde o melloriño de cada casa marcando episodios como favoritos<br>
&#8226; Atope ese episodio especial no historial de reprodución ou buscando (títulos e notas do episodio)<br>
&#8226; 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>
&#8226; Axude aos creadores de contido coa integración con Flattr incluíndo o flattring automático
CONTROL DO SISTEMA <br>
&#8226; 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>

View File

@ -0,0 +1 @@
AntennaPod

View File

@ -0,0 +1 @@
AntennaPod

View File

@ -1,5 +1,5 @@
היישומון אנטנה־פּוֹד הוא נגן ומנהל פודקאסטים שמעניק לך גישה ישירה למיליונים של פודקאסטים בחינם ובתשלום, החל ממגישי פודקאסטים עצמאיים ועד למפיצים גדולים כגון BBC, NPR ו־CNN. ניתן להוסיף, לייבא ולייצא את ההזנות שלהם בקלות יחסית באמצעות מסד נתוני הפודקאסטים של iTunes, קובצי OPML או כתובות של RSS. מאפשר לך לחסוך במאמץ, סוללה ותקשורת נתונים עם פקדי אוטומציה להורדה של פרקים (לפי זמנים, הפרשי זמן ורשתות אלחוטיות) ומחיקה של פרקים (על בסיס הגדרות המועדפים וההשהיה שלך).<br>
אבל הכי חשוב: ניתן להוריד, להזרים או לסדר רשימות של פרקים וליהנות מהם בכל דרך שמתאימה לך עם מהירויות נגינה משתנות, תמיכה במקטעים ומתזמן שינה. ניתן אפילו להביע את חיבתך ליוצרי התוכן עם שילוב של Flattr ביישומון.
אבל הכי חשוב: ניתן להוריד, להזרים או לסדר רשימות של פרקים וליהנות מהם בכל דרך שמתאימה לך עם מהירויות נגינה משתנות, תמיכה במקטעים ומתזמן שינה
מיוצרת על ידי חובבי פודקאסטים, אנטנהפוד הינה תוכנה חינמית בכל מובן המילה: קוד פתוח, ללא עלות וללא פרסומות.
@ -15,7 +15,6 @@
&#8226; מעקב אחר הטובים שבטובים על ידי סימון פרקים כמועדפים<br>
&#8226; ניתן לאתר פרק אחד דרך היסטוריית הנגינה או על ידי חיפוש (כותרות והערות פרק)<br>
&#8226; ניתן לשתף פרקים והזנות דרך אפשרויות מתקדמות ברשתות חברתיות ודוא״ל, שירותי gPodder.net ודרך ייצוא OPML<br>
&#8226; ניתן לתמוך בעורכי תוכן עם שילוב של Flattr לתוך המערכת לרבות תרומה אוטומטית
שליטה במערכת<br>
&#8226; ניתן לשלוט על הורדה אוטומטית: לבחור הזנות, להחריג רשתות סלולריות, לבחור רשתות אלחוטיות מסוימות, לדרוש מהטלפון להיות בטעינה ולהגדיר מועדים או מרווחי זמן<br>

View File

@ -0,0 +1 @@
AntennaPod

View File

@ -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など
&#8226; エピソードをお気に入りとしてマークして、一番の中の一番を保存してください<br>
&#8226; 再生履歴から、または検索 (タイトルとショーノート) して目的のエピソードを見つけてください<br>
&#8226; 高度なソーシャルメディアとメールオプション、gPodder.netサービス、OPMLエクスポートからエピソードやフィードを共有してください<br>
&#8226; 自動Flattrを含むFlattrの統合でコンテンツクリエイターをサポートします
システムのコントロール
&#8226; 自動ダウンロードの制御: フィードを選択、モバイルネットワークを除外、特定のWiFiネットワークを選択、電話を充電する必要、時間や間隔を設定<br>

View File

@ -0,0 +1 @@
AntennaPod

View File

@ -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>
&#8226; Hou het beste van het beste bij door afleveringen als favoriet te markeren<br>
&#8226; Vindt die ene aflevering terug in de afspeelgeschiedenis of door te zoeken (in titels, shownotes en makers)<br>
&#8226; Deel podcasts en afleveringen via uitgebreide opties voor sociale media, WhatsApp, email en gPodder.net<br>
&#8226; Steun makers met Flattr-integratie, inclusief auto-Flattr
HOUD DE CONTROLE
&#8226; 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>

View File

@ -0,0 +1 @@
AntennaPod

View 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>
&#8226; Lägg till och importera flöden via iTunes och gPodder.net, OPML filer och RSS eller Atom länkar<br>
&#8226; Hantera uppspelningen från vartsomhelst: hemskärmswidget, aviseringsfältet och hörlurs/bluetoth-kontroller<br>
&#8226; 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>
&#8226; Kom åt lösenordsskyddade flöden och episoder<br>
&#8226; Dra nytta av siduppdelade flöden (www.podlove.ord/paged-feeds)
SPÅRA, DELA & UPPSKATTA<br>
&#8226; Håll ordning på de bästa av de bästa med favoritmarkering av episoder<br>
&#8226; Hitta just den där episoden i uppspelningshistoriken eller genom sökning (titel och shownotes)<br>
&#8226; 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>
&#8226; 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>
&#8226; 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>
&#8226; Använd AntennaPod på ditt språk (EN, DE, CS, NL, NB, JA, PT, ES, SV, CA, UK, FR, KO, TR, ZH)<br>
&#8226; Anpassa till din omgivning med det ljusa och mörka temat<br>
&#8226; 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

View File

@ -0,0 +1 @@
Användarvänlig och flexibel podcasthanterare och spelare med öppen källkod

View File

@ -0,0 +1 @@
AntennaPod

View File

@ -0,0 +1,42 @@
AntennaPod 是一款播客管理器和播放器可让您即时收听数百万免费和付费的播客从独立播客到大型出版商如BBCNPR和CNN使用 iTunes 播客数据库OPML文件或简单的RSS URL轻松添加导入和导出 feeds ;通过强大的自动化控制功能节省工作量,电池电量和移动数据使用情况,以便下载剧集(指定时间,间隔和 WiFi 网络)和删除剧集(根据您的收藏和延迟设置)。
但最重要的是:下载,串流或安排节目并用可调节回放速度,章节支持和睡眠定时器以您喜欢的方式享受它们。
AntennaPod由播客爱好者开发在任意方面上都是自由的开源免费无广告。
<b>所有功能:</b><br>
导入、整理与播放<br>
&#8226; 通过 iTunes、gPodder.net、OPML 文件、RSS 或 Atom 链接添加和导入订阅
&#8226从任意地点管理回放主屏widget系统通知耳塞和蓝牙控制装置
&#8226借助可调节回放速度章节支持Mp3、Vorbis注释和Podlove记忆的回放位置和一个高级的睡眠定时器 (摇动设备来重置、调低音量并减慢回放)以您自己的方式享受收听播客节目
#8226; 访问受密码保护的 feeds 和剧集
&#8226; 充分利用分页 feeds (www.podlove.org/paged-feeds)
保持更新,分享&欣赏
8226; 通过将剧集标记为收藏来追踪最佳的剧集
&#8226; 通过回放历史或搜索(标题和节目笔记)找到您需要的那期节目
&#8226; 通过高级社交媒体和电邮选项gPodder.net服务和导出OPML文件来分享节目和源
一切尽在掌控<br>
&#8226; 控制自动下载:选择源,排除移动网络,选择特定无线网络,要求手机充电并设定次数或间隔
&#8226; 通过设定缓存节目的数量管理存储,智能删除(基于您的喜好和播放状态)并选择您偏好的存储位置
&#8226; 使用对应您的语言的 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

View File

@ -1 +1 @@
简单易用、灵活的开源播客管理工具与播放器
易用、灵活的开源播客管理工具与播放器

View File

@ -0,0 +1 @@
AntennaPod

View File

@ -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

View File

@ -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"

View File

@ -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>

View 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>

View 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>

View File

@ -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"/>

View File

@ -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"

View File

@ -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