Merge branch 'develop' into fix_episodes_list_item_loading_b
This commit is contained in:
commit
cdf59a1c8e
185
CONTRIBUTORS
185
CONTRIBUTORS
@ -1,185 +0,0 @@
|
||||
ByteHamster
|
||||
danieloeh
|
||||
mfietz
|
||||
TomHennen
|
||||
orionlee
|
||||
domingos86
|
||||
andersonvom
|
||||
TacoTheDank
|
||||
shortspider
|
||||
ebraminio
|
||||
spacecowboy
|
||||
patheticpat
|
||||
tonytamsf
|
||||
brad
|
||||
Cj-Malone
|
||||
maxbechtold
|
||||
asdoi
|
||||
gaul
|
||||
qkolj
|
||||
pachecosf
|
||||
gerardolgvr
|
||||
bws9000
|
||||
ahangarha
|
||||
damoasda
|
||||
hannesa2
|
||||
keunes
|
||||
rharriso
|
||||
xgouchet
|
||||
sevenmaster
|
||||
TheRealFalcon
|
||||
jas14
|
||||
johnjohndoe
|
||||
udif
|
||||
malockin
|
||||
dirkmueller
|
||||
jatinkumarg
|
||||
peschmae0
|
||||
orelogo
|
||||
txtd
|
||||
ydinath
|
||||
CedricCabessa
|
||||
mchelen
|
||||
dethstar
|
||||
drabux
|
||||
saqura
|
||||
bibz
|
||||
hzulla
|
||||
deandreamatias
|
||||
MeirAtIMDDE
|
||||
egsavage
|
||||
ligi
|
||||
dreiss
|
||||
liesen
|
||||
nereocystis
|
||||
rezanejati
|
||||
twiceyuan
|
||||
JessieVela
|
||||
HaBaLeS
|
||||
volhol
|
||||
michaelmwhite
|
||||
CameronBanga
|
||||
HrBDev
|
||||
HolgerJeromin
|
||||
xisberto
|
||||
jmue
|
||||
katrinleinweber
|
||||
LatinSuD
|
||||
24hours
|
||||
SosoTughushi
|
||||
fabolhak
|
||||
archibishop
|
||||
alifeflow
|
||||
toggles
|
||||
matdb
|
||||
kingargyle
|
||||
dsmith47
|
||||
hannesaa2
|
||||
jhunnius
|
||||
ShadowIce
|
||||
raghulj
|
||||
raghulrm
|
||||
mamehacker
|
||||
skitt
|
||||
wseemann
|
||||
mr-intj
|
||||
tuxayo
|
||||
schlch
|
||||
alimemonzx
|
||||
olivoto
|
||||
alanorth
|
||||
alexte
|
||||
andrey-krutov
|
||||
arantius
|
||||
chrissicool
|
||||
cszucko
|
||||
CWftw
|
||||
danielm5
|
||||
ariedov
|
||||
brettle
|
||||
edwinhere
|
||||
eirikv
|
||||
eerden
|
||||
jklippel
|
||||
jannic
|
||||
Foso
|
||||
Kaligule
|
||||
kvithayathil
|
||||
luiscruz
|
||||
mlasson
|
||||
M-arcel
|
||||
msoose
|
||||
mo
|
||||
mdeveloper20
|
||||
Slinger
|
||||
mschuetz
|
||||
MolarAmbiguity
|
||||
mounirlamouri
|
||||
ortylp
|
||||
PtilopsisLeucotis
|
||||
ramzan
|
||||
SamWhited
|
||||
selivan
|
||||
sonnayasomnambula
|
||||
sethoscope
|
||||
shantanahardy
|
||||
danners
|
||||
corecode
|
||||
vimsick
|
||||
edent
|
||||
atrus6
|
||||
waylife
|
||||
amhokies
|
||||
andrewc1
|
||||
axq
|
||||
fossterer
|
||||
jmdouglas
|
||||
lightonflux
|
||||
minusf
|
||||
|
||||
|
||||
Arabic: abuzar3.khalid, keunes, nabilMaghura, rex07
|
||||
Asturian (ast_ES): enolp
|
||||
Basque: gaztainalde, keunes, Osoitz, pospolos
|
||||
Breton: Belvar, keunes
|
||||
Bulgarian: keunes, solusitor
|
||||
Catalan: carles.llacer, dvd1985, exort12, javiercoll, keunes, Kintu, lambdani, marcmetallextrem, xc70
|
||||
Chinese (zh_CN): brnme, cyril3, Felix2yu, gaohongyuan, Guaidaodl, Huck0, iconteral, jhxie, jxj2zzz79pfp9bpo, keunes, kyleehee, molisiye, owen8877, RainSlide, Sak94664, spice2wolf, tupunco, wongsyrone, yangyang, yiqiok
|
||||
Chinese (zh_TW): bobchao, ijliao, keunes, mapobi, pggdt, ymhuang0808
|
||||
Czech (cs_CZ): anotheranonymoususer, elich, Hanzmeister, svetlemodry, Thomaash
|
||||
Danish: jhertel, keunes, SebastianKiwiDk, twikedk
|
||||
Dutch: e2jk, keunes, rwv, Vistaus
|
||||
Estonian: Eraser, keunes, mahfiaz
|
||||
Finnish: Ban3, keunes, Sahtor
|
||||
French: ChaoticMind, clombion, Cornegidouille, e2jk, keunes, lacouture, LouFex, Matth78, Poussinou, sterylmreep
|
||||
Galician: antiparvos, pikamoku, Raichely
|
||||
German: ByteHamster, ceving, dadosch, DerSilly, elkangaroo, enz, f_grubm, finsterwalder, HolgerJeromin, kalei, keunes, mfietz, Quiss42, repat, ypid
|
||||
Modern Greek (1453-): AnimaRain, antonist, keunes, pavlosv
|
||||
Hebrew (he_IL): amir.dafnyman, E1i9, mongoose4004, pinkasey, rellieberman, Yaron
|
||||
Hindi (hi_IN): keunes, purple.coder, siddhusengar
|
||||
Hungarian: hurrikan, keunes, lna91, marthynw, meskobalazs, naren93
|
||||
Icelandic: keunes, marthjod
|
||||
Indonesian: dbrw, keunes, levirs565
|
||||
Italian (it_IT): aalex70, allin, Bonnee, dontknowcris, giuseppep, Guybrush88, ilmanzo, keunes, m.chinni, marco_pag, neonsoftware, niccord, theloca95
|
||||
Japanese: keunes, KotaKato, Naofumi, sh3llc4t, TranslatorG
|
||||
Kannada (kn_IN): chiraag.nataraj, keunes, thejeshgn
|
||||
Korean: changwoo, keunes, libliboom
|
||||
Lithuanian: keunes, naglis
|
||||
Macedonian: krisfremen
|
||||
Malayalam: joice, keunes, rashivkp
|
||||
Norwegian Bokmål (nb_NO): abstrakct, bablecopherye, corkie, heraldo, jakobkg, keunes, kongk, sevenmaster, timbast
|
||||
Persian: ahangarha, danialbehzadi, ebraminio, F7D, hamidrezabayat76, keunes, sinamoghaddas
|
||||
Polish (pl_PL): hiro2020, Iwangelion, keunes, lomapur, mandlus, maniexx, Mephistofeles, shark103, tyle
|
||||
Portuguese: emansije, keunes, smarquespt
|
||||
Portuguese (pt_BR): alexupits, alysonborges, andersonvom, arua, caioau, carlo_valente, castrors, edman, keunes, lipefire, mbaltar, olivoto, rogervezaro, RubeensVinicius, SamWilliam
|
||||
Romanian (ro_RO): corneliu.e, fuzzmz, keunes, ralienpp
|
||||
Russian (ru_RU): btimofeev, Duke_Raven, gammja, homocomputeris, IgorPolyakov, keunes, mercutiy, null, overmind88, Platun0v, PtilopsisLeucotis, s.chebotar, un_logic, Vladryyu, whereisthetea
|
||||
Slovak: ati3, keunes, tiborepcek
|
||||
Slovenian (sl_SI): keunes, panter23
|
||||
Spanish: AleksSyntek, andersonvom, andrespelaezp, deandreamatias, dvd1985, Fitoschido, frandavid100, hard_ware, javiercoll, keunes, LatinSuD, leogrignafini, tres.14159, vfmatzkin, wakutiteo
|
||||
Swahili (macrolanguage): keunes, kmtra
|
||||
Swedish (sv_SE): bpnilsson, keunes, nilso, TwoD
|
||||
Telugu: keunes, veeven
|
||||
Turkish: brsata, Erdy, keunes, overbite, Slsdem
|
||||
Ukrainian (uk_UA): IndibidAbulya, keunes, older, paul_sm, sergiyr, zhenya97
|
||||
Vietnamese: abnvolk, keunes, ppanhh
|
53
CONTRIBUTORS.md
Normal file
53
CONTRIBUTORS.md
Normal file
File diff suppressed because one or more lines are too long
681
LICENSE
681
LICENSE
@ -1,6 +1,685 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Parts of AntennaPod use the MIT license.
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2012-2015 AntennaPod contributors
|
||||
Copyright (c) 2012-2020 AntennaPod contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -22,7 +22,7 @@ AntennaPod has many users and we don't want them to run into trouble when we add
|
||||
|
||||
## License
|
||||
|
||||
AntennaPod is licensed under the MIT License. You can find the license text in the LICENSE file.
|
||||
AntennaPod is licensed under the GNU General Public License (GPL-3.0). You can find the license text in the LICENSE file.
|
||||
|
||||
## Translating AntennaPod
|
||||
If you want to translate AntennaPod into another language, you can visit the [Transifex project page](https://www.transifex.com/antennapod/antennapod/).
|
||||
|
@ -22,8 +22,8 @@ android {
|
||||
// "1.2.3-SNAPSHOT" -> 1020300
|
||||
// "1.2.3-RC4" -> 1020304
|
||||
// "1.2.3" -> 1020395
|
||||
versionCode 2000395
|
||||
versionName "2.0.3"
|
||||
versionCode 2010295
|
||||
versionName "2.1.2"
|
||||
|
||||
multiDexEnabled false
|
||||
vectorDrawables.useSupportLibrary true
|
||||
|
@ -3,8 +3,10 @@ package de.test.antennapod;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.test.espresso.NoMatchingViewException;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.espresso.PerformException;
|
||||
import androidx.test.espresso.UiController;
|
||||
@ -15,6 +17,9 @@ import androidx.test.espresso.contrib.RecyclerViewActions;
|
||||
import androidx.test.espresso.util.HumanReadables;
|
||||
import androidx.test.espresso.util.TreeIterables;
|
||||
import android.view.View;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
@ -33,6 +38,7 @@ import java.util.concurrent.TimeoutException;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
|
||||
@ -57,7 +63,7 @@ public class EspressoTestUtils {
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "wait for a specific view for" + millis + " millis.";
|
||||
return "wait for a specific view for " + millis + " millis.";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -87,6 +93,33 @@ public class EspressoTestUtils {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until a certain view becomes visible, but at the longest until the timeout.
|
||||
* Unlike {@link #waitForView(Matcher, long)} it doesn't stick to the initial root view.
|
||||
*
|
||||
* @param viewMatcher The view to wait for.
|
||||
* @param timeoutMillis Maximum waiting period in milliseconds.
|
||||
* @throws Exception Throws an Exception in case of a timeout.
|
||||
*/
|
||||
public static void waitForViewGlobally(@NonNull Matcher<View> viewMatcher, long timeoutMillis) throws Exception {
|
||||
long startTime = System.currentTimeMillis();
|
||||
long endTime = startTime + timeoutMillis;
|
||||
|
||||
do {
|
||||
try {
|
||||
onView(viewMatcher).check(matches(isDisplayed()));
|
||||
// no Exception thrown -> check successful
|
||||
return;
|
||||
} catch (NoMatchingViewException | AssertionFailedError ignore) {
|
||||
// check was not successful "not found" -> continue waiting
|
||||
}
|
||||
//noinspection BusyWait
|
||||
Thread.sleep(50);
|
||||
} while (System.currentTimeMillis() < endTime);
|
||||
|
||||
throw new Exception("Timeout after " + timeoutMillis + " ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform action of waiting for a specific view id.
|
||||
* https://stackoverflow.com/a/30338665/
|
||||
@ -113,7 +146,7 @@ public class EspressoTestUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all app databases
|
||||
* Clear all app databases.
|
||||
*/
|
||||
public static void clearPreferences() {
|
||||
File root = InstrumentationRegistry.getInstrumentation().getTargetContext().getFilesDir().getParentFile();
|
||||
|
@ -1,43 +0,0 @@
|
||||
package de.test.antennapod.feed;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@SmallTest
|
||||
public class FeedItemTest {
|
||||
private static final String TEXT_LONG = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
|
||||
private static final String TEXT_SHORT = "Lorem ipsum";
|
||||
|
||||
/**
|
||||
* If one of `description` or `content:encoded` is null, use the other one.
|
||||
*/
|
||||
@Test
|
||||
public void testShownotesNullValues() throws Exception {
|
||||
testShownotes(null, TEXT_LONG);
|
||||
testShownotes(TEXT_LONG, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* If `description` is reasonably longer than `content:encoded`, use `description`.
|
||||
*/
|
||||
@Test
|
||||
public void testShownotesLength() throws Exception {
|
||||
testShownotes(TEXT_SHORT, TEXT_LONG);
|
||||
testShownotes(TEXT_LONG, TEXT_SHORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the shownotes equal TEXT_LONG, using the given `description` and `content:encoded`
|
||||
* @param description Description of the feed item
|
||||
* @param contentEncoded `content:encoded` of the feed item
|
||||
*/
|
||||
private void testShownotes(String description, String contentEncoded) throws Exception {
|
||||
FeedItem item = new FeedItem();
|
||||
item.setDescription(description);
|
||||
item.setContentEncoded(contentEncoded);
|
||||
assertEquals(TEXT_LONG, item.loadShownotes().call());
|
||||
}
|
||||
}
|
@ -17,10 +17,8 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
|
@ -4,14 +4,12 @@ import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import de.danoeh.antennapod.core.ClientConfig;
|
||||
import de.danoeh.antennapod.core.DBTasksCallbacks;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
import de.test.antennapod.ui.UITestUtils;
|
||||
@ -32,7 +30,7 @@ public class AutoDownloadTest {
|
||||
private Context context;
|
||||
private UITestUtils stubFeedsServer;
|
||||
|
||||
private DBTasksCallbacks dbTasksCallbacksOrig;
|
||||
private AutomaticDownloadAlgorithm automaticDownloadAlgorithmOrig;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@ -41,7 +39,7 @@ public class AutoDownloadTest {
|
||||
stubFeedsServer = new UITestUtils(context);
|
||||
stubFeedsServer.setup();
|
||||
|
||||
dbTasksCallbacksOrig = ClientConfig.dbTasksCallbacks;
|
||||
automaticDownloadAlgorithmOrig = ClientConfig.automaticDownloadAlgorithm;
|
||||
|
||||
EspressoTestUtils.clearPreferences();
|
||||
EspressoTestUtils.clearDatabase();
|
||||
@ -50,7 +48,7 @@ public class AutoDownloadTest {
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
ClientConfig.dbTasksCallbacks = dbTasksCallbacksOrig;
|
||||
ClientConfig.automaticDownloadAlgorithm = automaticDownloadAlgorithmOrig;
|
||||
EspressoTestUtils.tryKillPlaybackService();
|
||||
stubFeedsServer.tearDown();
|
||||
}
|
||||
@ -79,7 +77,7 @@ public class AutoDownloadTest {
|
||||
// Setup: enable automatic download
|
||||
// it is not needed, as the actual automatic download is stubbed.
|
||||
StubDownloadAlgorithm stubDownloadAlgorithm = new StubDownloadAlgorithm();
|
||||
useDownloadAlgorithm(stubDownloadAlgorithm);
|
||||
ClientConfig.automaticDownloadAlgorithm = stubDownloadAlgorithm;
|
||||
|
||||
// Actual test
|
||||
// Play the first one in the queue
|
||||
@ -113,20 +111,6 @@ public class AutoDownloadTest {
|
||||
.until(() -> item.getMedia().getId() == PlaybackPreferences.getCurrentlyPlayingFeedMediaId());
|
||||
}
|
||||
|
||||
private void useDownloadAlgorithm(final AutomaticDownloadAlgorithm downloadAlgorithm) {
|
||||
ClientConfig.dbTasksCallbacks = new DBTasksCallbacks() {
|
||||
@Override
|
||||
public AutomaticDownloadAlgorithm getAutomaticDownloadAlgorithm() {
|
||||
return downloadAlgorithm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EpisodeCleanupAlgorithm getEpisodeCacheCleanupAlgorithm() {
|
||||
return dbTasksCallbacksOrig.getEpisodeCacheCleanupAlgorithm();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static class StubDownloadAlgorithm implements AutomaticDownloadAlgorithm {
|
||||
private long currentlyPlaying = -1;
|
||||
|
||||
|
@ -11,7 +11,6 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
|
@ -2,16 +2,14 @@ package de.test.antennapod.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import androidx.test.espresso.Espresso;
|
||||
import androidx.test.espresso.intent.rule.IntentsTestRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import com.robotium.solo.Solo;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.storage.PodDBAdapter;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@ -20,6 +18,12 @@ import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.storage.PodDBAdapter;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.replaceText;
|
||||
@ -28,18 +32,17 @@ import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.contrib.ActivityResultMatchers.hasResultCode;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static de.test.antennapod.EspressoTestUtils.clickPreference;
|
||||
import static de.test.antennapod.EspressoTestUtils.openNavDrawer;
|
||||
import static de.test.antennapod.EspressoTestUtils.waitForView;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static de.test.antennapod.EspressoTestUtils.waitForViewGlobally;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* User interface tests for MainActivity
|
||||
* User interface tests for MainActivity.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class MainActivityTest {
|
||||
@ -48,19 +51,19 @@ public class MainActivityTest {
|
||||
private UITestUtils uiTestUtils;
|
||||
|
||||
@Rule
|
||||
public IntentsTestRule<MainActivity> mActivityRule = new IntentsTestRule<>(MainActivity.class, false, false);
|
||||
public IntentsTestRule<MainActivity> activityRule = new IntentsTestRule<>(MainActivity.class, false, false);
|
||||
|
||||
@Before
|
||||
public void setUp() throws IOException {
|
||||
EspressoTestUtils.clearPreferences();
|
||||
EspressoTestUtils.clearDatabase();
|
||||
|
||||
mActivityRule.launchActivity(new Intent());
|
||||
activityRule.launchActivity(new Intent());
|
||||
|
||||
uiTestUtils = new UITestUtils(InstrumentationRegistry.getInstrumentation().getTargetContext());
|
||||
uiTestUtils.setup();
|
||||
|
||||
solo = new Solo(InstrumentationRegistry.getInstrumentation(), mActivityRule.getActivity());
|
||||
solo = new Solo(InstrumentationRegistry.getInstrumentation(), activityRule.getActivity());
|
||||
}
|
||||
|
||||
@After
|
||||
@ -71,16 +74,22 @@ public class MainActivityTest {
|
||||
|
||||
@Test
|
||||
public void testAddFeed() throws Exception {
|
||||
// connect to podcast feed
|
||||
uiTestUtils.addHostedFeedData();
|
||||
final Feed feed = uiTestUtils.hostedFeeds.get(0);
|
||||
openNavDrawer();
|
||||
onView(withText(R.string.add_feed_label)).perform(click());
|
||||
onView(withId(R.id.btn_add_via_url)).perform(scrollTo(), click());
|
||||
onView(withId(R.id.text)).perform(replaceText(feed.getDownload_url()));
|
||||
onView(withId(R.id.addViaUrlButton)).perform(scrollTo(), click());
|
||||
onView(withId(R.id.urlEditText)).perform(replaceText(feed.getDownload_url()));
|
||||
onView(withText(R.string.confirm_label)).perform(scrollTo(), click());
|
||||
|
||||
// subscribe podcast
|
||||
Espresso.closeSoftKeyboard();
|
||||
waitForViewGlobally(withText(R.string.subscribe_label), 15000);
|
||||
onView(withText(R.string.subscribe_label)).perform(click());
|
||||
onView(isRoot()).perform(waitForView(withId(R.id.butShowSettings), 5000));
|
||||
|
||||
// wait for podcast feed item list
|
||||
waitForViewGlobally(withId(R.id.butShowSettings), 15000);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -100,7 +109,7 @@ public class MainActivityTest {
|
||||
onView(allOf(withId(R.id.toolbar), isDisplayed())).check(
|
||||
matches(hasDescendant(withText(R.string.subscriptions_label))));
|
||||
solo.goBack();
|
||||
assertThat(mActivityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -113,7 +122,7 @@ public class MainActivityTest {
|
||||
solo.goBackToActivity(MainActivity.class.getSimpleName());
|
||||
solo.goBack();
|
||||
solo.goBack();
|
||||
assertTrue(((MainActivity)solo.getCurrentActivity()).isDrawerOpen());
|
||||
assertTrue(((MainActivity) solo.getCurrentActivity()).isDrawerOpen());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -127,7 +136,7 @@ public class MainActivityTest {
|
||||
solo.goBack();
|
||||
solo.goBack();
|
||||
solo.goBack();
|
||||
assertThat(mActivityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -142,7 +151,7 @@ public class MainActivityTest {
|
||||
solo.goBack();
|
||||
onView(withText(R.string.yes)).perform(click());
|
||||
Thread.sleep(100);
|
||||
assertThat(mActivityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -155,6 +164,6 @@ public class MainActivityTest {
|
||||
solo.goBackToActivity(MainActivity.class.getSimpleName());
|
||||
solo.goBack();
|
||||
solo.goBack();
|
||||
assertThat(mActivityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
}
|
||||
}
|
||||
|
@ -42,9 +42,9 @@ import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static de.test.antennapod.EspressoTestUtils.onDrawerItem;
|
||||
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.assertTrue;
|
||||
|
||||
/**
|
||||
* User interface tests for MainActivity drawer.
|
||||
|
@ -43,9 +43,9 @@ import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static de.test.antennapod.EspressoTestUtils.clickPreference;
|
||||
import static de.test.antennapod.EspressoTestUtils.waitForView;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@LargeTest
|
||||
public class PreferencesTest {
|
||||
|
@ -1,15 +1,12 @@
|
||||
package de.test.antennapod.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.view.View;
|
||||
import androidx.test.espresso.Espresso;
|
||||
import androidx.test.espresso.intent.rule.IntentsTestRule;
|
||||
import androidx.test.ext.junit.runners.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.hamcrest.Matcher;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
@ -14,7 +14,6 @@ import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.playback.PlayerStatus;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
import de.danoeh.antennapod.fragment.ExternalPlayerFragment;
|
||||
import de.danoeh.antennapod.fragment.QueueFragment;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
import de.test.antennapod.IgnoreOnCi;
|
||||
@ -37,8 +36,6 @@ import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
import static de.test.antennapod.EspressoTestUtils.waitForView;
|
||||
import static de.test.antennapod.NthMatcher.first;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
|
||||
/**
|
||||
* User interface tests for changing the playback speed.
|
||||
|
@ -6,7 +6,6 @@ import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.filters.MediumTest;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
|
@ -189,15 +189,6 @@
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="de.danoeh.antennapod.activity.MainActivity"/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data android:scheme="file"/>
|
||||
<data android:mimeType="video/*"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
@ -292,19 +283,6 @@
|
||||
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activity.gpoddernet.GpodnetAuthenticationActivity"
|
||||
android:configChanges="orientation"
|
||||
android:label="@string/gpodnet_auth_label">
|
||||
<intent-filter>
|
||||
<action android:name=".activity.gpoddernet.GpodnetAuthenticationActivity"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="de.danoeh.antennapod.activity.PreferenceActivity"/>
|
||||
</activity>
|
||||
|
||||
<receiver android:name=".receiver.ConnectivityActionReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
|
||||
|
@ -1,35 +1,36 @@
|
||||
ByteHamster;5811634;Maintainer
|
||||
danieloeh;968613;Original creator of AntennaPod (retired)
|
||||
mfietz;6860662;Maintainer
|
||||
mfietz;6860662;Maintainer (retired)
|
||||
TomHennen;5216560;Maintainer (retired)
|
||||
orionlee;250644;Contributor
|
||||
domingos86;9538859;Contributor
|
||||
tonytamsf;149837;Contributor
|
||||
andersonvom;69922;Contributor
|
||||
damoasda;46045854;Contributor
|
||||
TacoTheDank;32376686;Contributor
|
||||
shortspider;5712543;Contributor
|
||||
ebraminio;833473;Contributor
|
||||
asdoi;36813904;Contributor
|
||||
spacecowboy;223655;Contributor
|
||||
patheticpat;16046;Contributor
|
||||
tonytamsf;149837;Contributor
|
||||
brad;1614;Contributor
|
||||
Cj-Malone;10121513;Contributor
|
||||
maxbechtold;9162198;Contributor
|
||||
asdoi;36813904;Contributor
|
||||
gaul;848247;Contributor
|
||||
qkolj;6667105;Contributor
|
||||
keunes;11229646;Maintainer
|
||||
pachecosf;46357909;Contributor
|
||||
gerardolgvr;20119298;Contributor
|
||||
bws9000;262625;Contributor
|
||||
ahangarha;11241315;Contributor
|
||||
damoasda;46045854;Contributor
|
||||
hannesa2;3314607;Contributor
|
||||
keunes;11229646;Contributor
|
||||
rharriso;570910;Contributor
|
||||
xgouchet;818706;Contributor
|
||||
sevenmaster;12869538;Contributor
|
||||
TheRealFalcon;153674;Contributor
|
||||
jas14;569991;Contributor
|
||||
Slinger;75751;Contributor
|
||||
johnjohndoe;144518;Contributor
|
||||
jas14;569991;Contributor
|
||||
udif;809640;Contributor
|
||||
malockin;12814657;Contributor
|
||||
dirkmueller;1029152;Contributor
|
||||
@ -49,6 +50,7 @@ deandreamatias;21011641;Contributor
|
||||
MeirAtIMDDE;4421079;Contributor
|
||||
egsavage;126165;Contributor
|
||||
ligi;111600;Contributor
|
||||
Xeitor;8825715;Contributor
|
||||
dreiss;4121;Contributor
|
||||
liesen;26872;Contributor
|
||||
nereocystis;2257107;Contributor
|
||||
@ -70,30 +72,43 @@ SosoTughushi;19908097;Contributor
|
||||
fabolhak;20029691;Contributor
|
||||
archibishop;36948493;Contributor
|
||||
alifeflow;24603829;Contributor
|
||||
avirajrsingh;69088913;Contributor
|
||||
toggles;14695;Contributor
|
||||
matdb;48329535;Contributor
|
||||
damlayildiz;56313500;Contributor
|
||||
kingargyle;177042;Contributor
|
||||
dsmith47;14109426;Contributor
|
||||
hannesaa2;18496079;Contributor
|
||||
jhunnius;9149031;Contributor
|
||||
ShadowIce;59123;Contributor
|
||||
Niffler;8172446;Contributor
|
||||
raghulj;57007;Contributor
|
||||
raghulrm;5362986;Contributor
|
||||
mamehacker;16738348;Contributor
|
||||
skitt;2128935;Contributor
|
||||
wseemann;2296196;Contributor
|
||||
markamaze;17114678;Contributor
|
||||
mohitshah3111999;42018918;Contributor
|
||||
moralesg;14352147;Contributor
|
||||
mr-intj;6268767;Contributor
|
||||
tuxayo;2678215;Contributor
|
||||
schlch;56929215;Contributor
|
||||
alimemonzx;44647595;Contributor
|
||||
dev-darrell;52300159;Contributor
|
||||
jmdouglas;10855634;Contributor
|
||||
olivoto;15932680;Contributor
|
||||
PtilopsisLeucotis;54054883;Contributor
|
||||
abhinavg1997;60095795;Contributor
|
||||
alanorth;191754;Contributor
|
||||
alexte;7724992;Contributor
|
||||
andrey-krutov;1488973;Contributor
|
||||
arantius;84729;Contributor
|
||||
BoJacobs;25435640;Contributor
|
||||
chetan882777;36985543;Contributor
|
||||
chrissicool;232590;Contributor
|
||||
cszucko;1810383;Contributor
|
||||
CWftw;1498303;Contributor
|
||||
connectety;26038710;Contributor
|
||||
danielm5;66779;Contributor
|
||||
ariedov;958646;Contributor
|
||||
brettle;118192;Contributor
|
||||
@ -107,17 +122,21 @@ Kaligule;3586246;Contributor
|
||||
kvithayathil;1056073;Contributor
|
||||
luiscruz;1080714;Contributor
|
||||
mlasson;5814258;Contributor
|
||||
schwedenmut;9077622;Contributor
|
||||
M-arcel;56698158;Contributor
|
||||
msoose;30473690;Contributor
|
||||
mo;7117;Contributor
|
||||
mdeveloper20;2319126;Contributor
|
||||
Slinger;75751;Contributor
|
||||
mschuetz;108637;Contributor
|
||||
max-wittig;6639323;Contributor
|
||||
MolarAmbiguity;10541979;Contributor
|
||||
mounirlamouri;573590;Contributor
|
||||
nikhil097;35090769;Contributor
|
||||
panoreak;25068506;Contributor
|
||||
patrickjkennedy;8617261;Contributor
|
||||
ortylp;470439;Contributor
|
||||
PtilopsisLeucotis;54054883;Contributor
|
||||
ramzan;55637406;Contributor
|
||||
iamrichR;44210678;Contributor
|
||||
SamWhited;512573;Contributor
|
||||
selivan;1208989;Contributor
|
||||
sonnayasomnambula;7716779;Contributor
|
||||
@ -126,13 +145,17 @@ shantanahardy;26757164;Contributor
|
||||
danners;116551;Contributor
|
||||
corecode;177979;Contributor
|
||||
vimsick;20211590;Contributor
|
||||
lyallemma;25173082;Contributor
|
||||
edent;837136;Contributor
|
||||
atrus6;357881;Contributor
|
||||
heyyviv;56256802;Contributor
|
||||
waylife;3348620;Contributor
|
||||
amhokies;3124968;Contributor
|
||||
andrewc1;19559401;Contributor
|
||||
axq;5077221;Contributor
|
||||
binarytoto;75904760;Contributor
|
||||
chrk2205;44704035;Contributor
|
||||
fossterer;4236021;Contributor
|
||||
jmdouglas;10855634;Contributor
|
||||
lightonflux;1377943;Contributor
|
||||
minusf;3632883;Contributor
|
||||
zawad2221;32180355;Contributor
|
||||
|
|
@ -4,7 +4,7 @@
|
||||
name="AntennaPod"
|
||||
author="The AntennaPod team"
|
||||
website="https://github.com/AntennaPod/AntennaPod/"
|
||||
license="MIT"
|
||||
license="GPL-3.0"
|
||||
licenseText="LICENSE.txt" />
|
||||
<library
|
||||
name="AntennaPod-AudioPlayer"
|
||||
|
@ -1,3 +1,4 @@
|
||||
221 Pixels;Logo design;https://avatars2.githubusercontent.com/u/58243143?s=60&v=4
|
||||
Anxhelo Lushka;Website design;https://avatars2.githubusercontent.com/u/25004151?s=60&v=4
|
||||
ByteHamster;Forum admin;https://avatars2.githubusercontent.com/u/5811634?s=60&v=4
|
||||
Keunes;Communications;https://avatars2.githubusercontent.com/u/11229646?s=60&v=4
|
||||
|
|
@ -1,45 +1,45 @@
|
||||
Arabic;abuzar3.khalid, keunes, nabilMaghura, rex07
|
||||
Arabic;abuzar3.khalid, badarotti, keunes, nabilMaghura, rex07, shubbar
|
||||
Asturian (ast_ES);enolp
|
||||
Basque;gaztainalde, keunes, Osoitz, pospolos
|
||||
Breton;Belvar, keunes
|
||||
Bulgarian;keunes, solusitor
|
||||
Catalan;carles.llacer, dvd1985, exort12, javiercoll, keunes, Kintu, lambdani, marcmetallextrem, xc70
|
||||
Chinese (zh_CN);brnme, cyril3, Felix2yu, gaohongyuan, Guaidaodl, Huck0, iconteral, jhxie, jxj2zzz79pfp9bpo, keunes, kyleehee, molisiye, owen8877, RainSlide, Sak94664, spice2wolf, tupunco, wongsyrone, yangyang, yiqiok
|
||||
Catalan;carles.llacer, dvd1985, exort12, IvanAmarante, javiercoll, keunes, Kintu, lambdani, marcmetallextrem, xc70
|
||||
Chinese (zh_CN);brnme, cyril3, Felix2yu, gaohongyuan, Guaidaodl, Huck0, iconteral, jhxie, jxj2zzz79pfp9bpo, keunes, kyleehee, molisiye, owen8877, RainSlide, RangerNJU, Sak94664, spice2wolf, tupunco, wongsyrone, yangyang, yiqiok
|
||||
Chinese (zh_TW);bobchao, ijliao, keunes, mapobi, pggdt, ymhuang0808
|
||||
Czech (cs_CZ);anotheranonymoususer, elich, Hanzmeister, svetlemodry, Thomaash
|
||||
Danish;jhertel, keunes, SebastianKiwiDk, twikedk
|
||||
Danish;JFreak, jhertel, keunes, SebastianKiwiDk, twikedk
|
||||
Dutch;e2jk, keunes, rwv, Vistaus
|
||||
Estonian;Eraser, keunes, mahfiaz
|
||||
Finnish;Ban3, keunes, Sahtor
|
||||
French;ChaoticMind, clombion, Cornegidouille, e2jk, keunes, lacouture, LouFex, Matth78, Poussinou, sterylmreep
|
||||
Galician;antiparvos, pikamoku, Raichely
|
||||
German;ByteHamster, ceving, dadosch, DerSilly, elkangaroo, enz, f_grubm, finsterwalder, HolgerJeromin, kalei, keunes, mfietz, Quiss42, repat, ypid
|
||||
German;_Er, ByteHamster, ceving, dadosch, DerSilly, elkangaroo, enz, f_grubm, finsterwalder, hbilke, HolgerJeromin, JoeMcFly, kalei, keunes, mfietz, pudeeh, Quiss42, repat, tomte, tweimer, Willhelm, ypid
|
||||
Modern Greek (1453-);AnimaRain, antonist, keunes, pavlosv
|
||||
Hebrew (he_IL);amir.dafnyman, E1i9, mongoose4004, pinkasey, rellieberman, Yaron
|
||||
Hindi (hi_IN);keunes, purple.coder, siddhusengar
|
||||
Hungarian;hurrikan, keunes, lna91, marthynw, meskobalazs, naren93
|
||||
Hindi (hi_IN);keunes, purple.coder, siddhusengar, thelazyoxymoron
|
||||
Hu;hurrikan, keunes, lna91, marthynw, meskobalazs, naren93
|
||||
Icelandic;keunes, marthjod
|
||||
Indonesian;dbrw, keunes, levirs565
|
||||
Italian (it_IT);aalex70, allin, Bonnee, dontknowcris, giuseppep, Guybrush88, ilmanzo, keunes, m.chinni, marco_pag, neonsoftware, niccord, theloca95
|
||||
Italian (it_IT);aalex70, allin, alvami, Bonnee, dontknowcris, giuseppep, Guybrush88, ilmanzo, keunes, m.chinni, marco_pag, neonsoftware, niccord, salorock, theloca95
|
||||
Japanese;keunes, KotaKato, Naofumi, sh3llc4t, TranslatorG
|
||||
Kannada (kn_IN);chiraag.nataraj, keunes, thejeshgn
|
||||
Korean;changwoo, keunes, libliboom
|
||||
Ko;changwoo, keunes, libliboom
|
||||
Lithuanian;keunes, naglis
|
||||
Macedonian;krisfremen
|
||||
Malayalam;joice, keunes, rashivkp
|
||||
Norwegian Bokmål (nb_NO);abstrakct, bablecopherye, corkie, heraldo, jakobkg, keunes, kongk, sevenmaster, timbast
|
||||
Persian;ahangarha, danialbehzadi, ebraminio, F7D, hamidrezabayat76, keunes, sinamoghaddas
|
||||
Polish (pl_PL);hiro2020, Iwangelion, keunes, lomapur, mandlus, maniexx, Mephistofeles, shark103, tyle
|
||||
Norwegian Bokmål (nb_NO);abstrakct, ahysing, bablecopherye, corkie, forteller, heraldo, jakobkg, keunes, kongk, sevenmaster, timbast
|
||||
Persian;ahangarha, danialbehzadi, ebadi, ebraminio, F7D, hamidrezabayat76, keunes, sinamoghaddas
|
||||
Polish (pl_PL);befeleme, hiro2020, Iwangelion, keunes, lomapur, mandlus, maniexx, Mephistofeles, shark103, tyle
|
||||
Portuguese;emansije, keunes, smarquespt
|
||||
Portuguese (pt_BR);alexupits, alysonborges, andersonvom, arua, caioau, carlo_valente, castrors, edman, keunes, lipefire, mbaltar, olivoto, rogervezaro, RubeensVinicius, SamWilliam
|
||||
Romanian (ro_RO);corneliu.e, fuzzmz, keunes, ralienpp
|
||||
Russian (ru_RU);btimofeev, Duke_Raven, gammja, homocomputeris, IgorPolyakov, keunes, mercutiy, null, overmind88, Platun0v, PtilopsisLeucotis, s.chebotar, un_logic, Vladryyu, whereisthetea
|
||||
Slovak;ati3, keunes, tiborepcek
|
||||
Russian (ru_RU);ashed, btimofeev, Duke_Raven, gammja, homocomputeris, IgorPolyakov, keunes, mercutiy, null, overmind88, Platun0v, PtilopsisLeucotis, s.chebotar, un_logic, Vladryyu, whereisthetea
|
||||
Slovak;ati3, keunes, marulinko, tiborepcek
|
||||
Slovenian (sl_SI);keunes, panter23
|
||||
Spanish;AleksSyntek, andersonvom, andrespelaezp, deandreamatias, dvd1985, Fitoschido, frandavid100, hard_ware, javiercoll, keunes, LatinSuD, leogrignafini, tres.14159, vfmatzkin, wakutiteo
|
||||
Spanish;AleksSyntek, andersonvom, andrespelaezp, deandreamatias, dvd1985, elojodepajaro, Fitoschido, frandavid100, hard_ware, javiercoll, keunes, LatinSuD, leogrignafini, rafael.osuna, tres.14159, vfmatzkin, wakutiteo
|
||||
Swahili (macrolanguage);keunes, kmtra
|
||||
Swedish (sv_SE);bpnilsson, keunes, nilso, TwoD
|
||||
Telugu;keunes, veeven
|
||||
Turkish;brsata, Erdy, keunes, overbite, Slsdem
|
||||
Ukrainian (uk_UA);IndibidAbulya, keunes, older, paul_sm, sergiyr, zhenya97
|
||||
Turkish;AhmedDuran, brsata, Erdy, keunes, overbite, Slsdem
|
||||
Ukrainian (uk_UA);keunes, older, paul_sm, sergiyr, zhenya97
|
||||
Vietnamese;abnvolk, keunes, ppanhh
|
||||
|
|
@ -11,10 +11,15 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.TextView;
|
||||
|
||||
|
||||
@ -67,42 +72,65 @@ public class BugReportActivity extends AppCompatActivity {
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Snackbar.make(findViewById(android.R.id.content), R.string.copied_to_clipboard, Snackbar.LENGTH_SHORT).show();
|
||||
});
|
||||
|
||||
findViewById(R.id.btn_export_logcat).setOnClickListener(v -> {
|
||||
try {
|
||||
File filename = new File(UserPreferences.getDataFolder(null), "full-logs.txt");
|
||||
filename.createNewFile();
|
||||
String cmd = "logcat -d -f " + filename.getAbsolutePath();
|
||||
Runtime.getRuntime().exec(cmd);
|
||||
//share file
|
||||
try {
|
||||
Intent i = new Intent(Intent.ACTION_SEND);
|
||||
i.setType("text/*");
|
||||
String authString = getString(de.danoeh.antennapod.core.R.string.provider_authority);
|
||||
Uri fileUri = FileProvider.getUriForFile(this, authString, filename);
|
||||
i.putExtra(Intent.EXTRA_STREAM, fileUri);
|
||||
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
|
||||
PackageManager pm = getPackageManager();
|
||||
List<ResolveInfo> resInfos = pm.queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
for (ResolveInfo resolveInfo : resInfos) {
|
||||
String packageName = resolveInfo.activityInfo.packageName;
|
||||
grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
}
|
||||
String chooserTitle = getString(de.danoeh.antennapod.core.R.string.share_file_label);
|
||||
startActivity(Intent.createChooser(i, chooserTitle));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
int strResId = R.string.log_file_share_exception;
|
||||
Snackbar.make(findViewById(android.R.id.content), strResId, Snackbar.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
Snackbar.make(findViewById(android.R.id.content), e.getMessage(), Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.bug_report_options, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == R.id.export_logcat) {
|
||||
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
|
||||
alertBuilder.setMessage(R.string.confirm_export_log_dialog_message);
|
||||
alertBuilder.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
|
||||
exportLog();
|
||||
dialog.dismiss();
|
||||
});
|
||||
alertBuilder.setNegativeButton(R.string.cancel_label, null);
|
||||
alertBuilder.show();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void exportLog() {
|
||||
try {
|
||||
File filename = new File(UserPreferences.getDataFolder(null), "full-logs.txt");
|
||||
filename.createNewFile();
|
||||
String cmd = "logcat -d -f " + filename.getAbsolutePath();
|
||||
Runtime.getRuntime().exec(cmd);
|
||||
//share file
|
||||
try {
|
||||
Intent i = new Intent(Intent.ACTION_SEND);
|
||||
i.setType("text/*");
|
||||
String authString = getString(de.danoeh.antennapod.core.R.string.provider_authority);
|
||||
Uri fileUri = FileProvider.getUriForFile(this, authString, filename);
|
||||
i.putExtra(Intent.EXTRA_STREAM, fileUri);
|
||||
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
|
||||
PackageManager pm = getPackageManager();
|
||||
List<ResolveInfo> resInfos = pm.queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
for (ResolveInfo resolveInfo : resInfos) {
|
||||
String packageName = resolveInfo.activityInfo.packageName;
|
||||
grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
}
|
||||
String chooserTitle = getString(de.danoeh.antennapod.core.R.string.share_file_label);
|
||||
startActivity(Intent.createChooser(i, chooserTitle));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
int strResId = R.string.log_file_share_exception;
|
||||
Snackbar.make(findViewById(android.R.id.content), strResId, Snackbar.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
Snackbar.make(findViewById(android.R.id.content), e.getMessage(), Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,62 +1,54 @@
|
||||
package de.danoeh.antennapod.activity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.widget.Button;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadRequest;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequester;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
|
||||
/**
|
||||
* Shows a username and a password text field.
|
||||
* The activity MUST be started with the ARG_DOWNlOAD_REQUEST argument set to a non-null value.
|
||||
* Other arguments are optional.
|
||||
* The activity's result will be the same DownloadRequest with the entered username and password.
|
||||
*/
|
||||
public class DownloadAuthenticationActivity extends AppCompatActivity {
|
||||
|
||||
/**
|
||||
* The download request object that contains information about the resource that requires a username and a password
|
||||
* The download request object that contains information about the resource that requires a username and a password.
|
||||
*/
|
||||
public static final String ARG_DOWNLOAD_REQUEST = "request";
|
||||
/**
|
||||
* True if the request should be sent to the DownloadRequester when this activity is finished, false otherwise.
|
||||
* The default value is false.
|
||||
*/
|
||||
public static final String ARG_SEND_TO_DOWNLOAD_REQUESTER_BOOL = "send_to_downloadrequester";
|
||||
|
||||
private static final String RESULT_REQUEST = "request";
|
||||
|
||||
private EditText etxtUsername;
|
||||
private EditText etxtPassword;
|
||||
private DownloadRequest request;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(UserPreferences.getNoTitleTheme());
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.download_authentication_activity);
|
||||
TextView txtvDescription = findViewById(R.id.txtvDescription);
|
||||
|
||||
etxtUsername = findViewById(R.id.etxtUsername);
|
||||
etxtPassword = findViewById(R.id.etxtPassword);
|
||||
Button butConfirm = findViewById(R.id.butConfirm);
|
||||
Button butCancel = findViewById(R.id.butCancel);
|
||||
|
||||
Validate.isTrue(getIntent().hasExtra(ARG_DOWNLOAD_REQUEST), "Download request missing");
|
||||
DownloadRequest request = getIntent().getParcelableExtra(ARG_DOWNLOAD_REQUEST);
|
||||
boolean sendToDownloadRequester = getIntent().getBooleanExtra(ARG_SEND_TO_DOWNLOAD_REQUESTER_BOOL, false);
|
||||
request = getIntent().getParcelableExtra(ARG_DOWNLOAD_REQUEST);
|
||||
|
||||
TextView txtvDescription = findViewById(R.id.txtvDescription);
|
||||
String newDescription = txtvDescription.getText() + ":\n\n" + request.getTitle();
|
||||
txtvDescription.setText(newDescription);
|
||||
|
||||
@ -65,28 +57,42 @@ public class DownloadAuthenticationActivity extends AppCompatActivity {
|
||||
etxtPassword.setText(savedInstanceState.getString("password"));
|
||||
}
|
||||
|
||||
butConfirm.setOnClickListener(v -> {
|
||||
String username = etxtUsername.getText().toString();
|
||||
String password = etxtPassword.getText().toString();
|
||||
request.setUsername(username);
|
||||
request.setPassword(password);
|
||||
Intent result = new Intent();
|
||||
result.putExtra(RESULT_REQUEST, request);
|
||||
setResult(Activity.RESULT_OK, result);
|
||||
findViewById(R.id.butConfirm).setOnClickListener(v ->
|
||||
Completable.fromAction(this::updatePassword)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(() -> {
|
||||
DownloadRequester.getInstance().download(DownloadAuthenticationActivity.this, request);
|
||||
finish();
|
||||
}));
|
||||
|
||||
if (sendToDownloadRequester) {
|
||||
DownloadRequester.getInstance().download(DownloadAuthenticationActivity.this, request);
|
||||
}
|
||||
finish();
|
||||
});
|
||||
|
||||
butCancel.setOnClickListener(v -> {
|
||||
findViewById(R.id.butCancel).setOnClickListener(v -> {
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
finish();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void updatePassword() {
|
||||
String username = etxtUsername.getText().toString();
|
||||
String password = etxtPassword.getText().toString();
|
||||
request.setUsername(username);
|
||||
request.setPassword(password);
|
||||
|
||||
if (request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
|
||||
long mediaId = request.getFeedfileId();
|
||||
FeedMedia media = DBReader.getFeedMedia(mediaId);
|
||||
if (media != null) {
|
||||
FeedPreferences preferences = media.getItem().getFeed().getPreferences();
|
||||
if (TextUtils.isEmpty(preferences.getPassword()) || TextUtils.isEmpty(preferences.getUsername())) {
|
||||
preferences.setUsername(username);
|
||||
preferences.setPassword(password);
|
||||
DBWriter.setFeedPreferences(preferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
@ -16,6 +16,7 @@ import android.view.KeyEvent;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
@ -542,6 +543,11 @@ public class MainActivity extends CastEnabledActivity {
|
||||
//Hardware keyboard support
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
View currentFocus = getCurrentFocus();
|
||||
if (currentFocus instanceof EditText) {
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
|
||||
Integer customKeyCode = null;
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
package de.danoeh.antennapod.activity;
|
||||
|
||||
import android.Manifest;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@ -17,7 +16,6 @@ import android.widget.ImageButton;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
|
||||
@ -28,18 +26,13 @@ import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.ActivityOptionsCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
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;
|
||||
@ -51,11 +44,9 @@ import de.danoeh.antennapod.core.util.ShareUtils;
|
||||
import de.danoeh.antennapod.core.util.StorageUtils;
|
||||
import de.danoeh.antennapod.core.util.TimeSpeedConverter;
|
||||
import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil;
|
||||
import de.danoeh.antennapod.core.util.playback.ExternalMedia;
|
||||
import de.danoeh.antennapod.core.util.playback.MediaPlayerError;
|
||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
|
||||
import de.danoeh.antennapod.dialog.PlaybackControlsDialog;
|
||||
import de.danoeh.antennapod.dialog.ShareDialog;
|
||||
import de.danoeh.antennapod.dialog.SkipPreferenceDialog;
|
||||
@ -74,8 +65,6 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
||||
private static final String TAG = "MediaplayerActivity";
|
||||
private static final String PREFS = "MediaPlayerActivityPreferences";
|
||||
private static final String PREF_SHOW_TIME_LEFT = "showTimeLeft";
|
||||
private static final int REQUEST_CODE_STORAGE_PLAY_VIDEO = 42;
|
||||
private static final int REQUEST_CODE_STORAGE_PLAY_AUDIO = 43;
|
||||
|
||||
PlaybackController controller;
|
||||
|
||||
@ -664,50 +653,6 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
||||
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
|
||||
}
|
||||
|
||||
void playExternalMedia(Intent intent, MediaType type) {
|
||||
if (intent == null || intent.getData() == null) {
|
||||
return;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 23
|
||||
&& ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
|
||||
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
|
||||
Toast.makeText(this, R.string.needs_storage_permission, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
int code = REQUEST_CODE_STORAGE_PLAY_AUDIO;
|
||||
if (type == MediaType.VIDEO) {
|
||||
code = REQUEST_CODE_STORAGE_PLAY_VIDEO;
|
||||
}
|
||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, code);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Received VIEW intent: " + intent.getData().getPath());
|
||||
ExternalMedia media = new ExternalMedia(intent.getData().getPath(), type);
|
||||
|
||||
new PlaybackServiceStarter(this, media)
|
||||
.callEvenIfRunning(true)
|
||||
.startWhenPrepared(true)
|
||||
.shouldStream(false)
|
||||
.prepareImmediately(true)
|
||||
.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, int[] grantResults) {
|
||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
if (requestCode == REQUEST_CODE_STORAGE_PLAY_AUDIO) {
|
||||
playExternalMedia(getIntent(), MediaType.AUDIO);
|
||||
} else if (requestCode == REQUEST_CODE_STORAGE_PLAY_VIDEO) {
|
||||
playExternalMedia(getIntent(), MediaType.VIDEO);
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this, R.string.needs_storage_permission, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static FeedItem getFeedItem(@Nullable Playable playable) {
|
||||
if (playable instanceof FeedMedia) {
|
||||
|
@ -4,6 +4,7 @@ import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.LightingColorFilter;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@ -13,7 +14,6 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.UiThread;
|
||||
@ -88,6 +88,9 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
|
||||
// Optional argument: specify a title for the actionbar.
|
||||
private static final int RESULT_ERROR = 2;
|
||||
private static final String TAG = "OnlineFeedViewActivity";
|
||||
private static final String PREFS = "OnlineFeedViewActivityPreferences";
|
||||
private static final String PREF_LAST_AUTO_DOWNLOAD = "lastAutoDownload";
|
||||
|
||||
private volatile List<Feed> feeds;
|
||||
private Feed feed;
|
||||
private String selectedDownloadUrl;
|
||||
@ -446,6 +449,11 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
|
||||
IntentUtils.sendLocalBroadcast(this, PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE);
|
||||
});
|
||||
|
||||
if (UserPreferences.isEnableAutodownload()) {
|
||||
SharedPreferences preferences = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
viewBinding.autoDownloadCheckBox.setChecked(preferences.getBoolean(PREF_LAST_AUTO_DOWNLOAD, true));
|
||||
}
|
||||
|
||||
final int MAX_LINES_COLLAPSED = 10;
|
||||
description.setMaxLines(MAX_LINES_COLLAPSED);
|
||||
description.setOnClickListener(v -> {
|
||||
@ -512,10 +520,17 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
|
||||
if (didPressSubscribe) {
|
||||
didPressSubscribe = false;
|
||||
if (UserPreferences.isEnableAutodownload()) {
|
||||
boolean autoDownload = viewBinding.autoDownloadCheckBox.isChecked();
|
||||
|
||||
Feed feed1 = DBReader.getFeed(getFeedId(feed));
|
||||
FeedPreferences feedPreferences = feed1.getPreferences();
|
||||
feedPreferences.setAutoDownload(viewBinding.autoDownloadCheckBox.isChecked());
|
||||
feedPreferences.setAutoDownload(autoDownload);
|
||||
feed1.savePreferences();
|
||||
|
||||
SharedPreferences preferences = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putBoolean(PREF_LAST_AUTO_DOWNLOAD, autoDownload);
|
||||
editor.apply();
|
||||
}
|
||||
openFeed();
|
||||
}
|
||||
|
@ -17,9 +17,8 @@ import de.danoeh.antennapod.asynctask.OpmlFeedQueuer;
|
||||
import de.danoeh.antennapod.asynctask.OpmlImportWorker;
|
||||
import de.danoeh.antennapod.core.export.opml.OpmlElement;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.util.LangUtils;
|
||||
|
||||
import org.apache.commons.io.ByteOrderMark;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.input.BOMInputStream;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
|
@ -13,9 +13,9 @@ import android.widget.ProgressBar;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.storage.PodDBAdapter;
|
||||
import de.danoeh.antennapod.error.CrashReportWriter;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
/**
|
||||
@ -44,13 +44,15 @@ public class SplashActivity extends AppCompatActivity {
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(() -> {
|
||||
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
|
||||
startActivity(intent);
|
||||
overridePendingTransition(0, 0);
|
||||
finish();
|
||||
}, error -> {
|
||||
.subscribe(
|
||||
() -> {
|
||||
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
|
||||
startActivity(intent);
|
||||
overridePendingTransition(0, 0);
|
||||
finish();
|
||||
}, error -> {
|
||||
error.printStackTrace();
|
||||
CrashReportWriter.write(error);
|
||||
Toast.makeText(this, error.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
});
|
||||
|
@ -12,12 +12,11 @@ import android.view.KeyEvent;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.AnimationSet;
|
||||
import android.view.animation.ScaleAnimation;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.appcompat.view.menu.ActionMenuItem;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.Menu;
|
||||
@ -37,7 +36,6 @@ import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.feed.MediaType;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.service.playback.PlayerStatus;
|
||||
@ -88,9 +86,7 @@ public class VideoplayerActivity extends MediaplayerActivity {
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (TextUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) {
|
||||
playExternalMedia(getIntent(), MediaType.VIDEO);
|
||||
} else if (PlaybackService.isCasting()) {
|
||||
if (PlaybackService.isCasting()) {
|
||||
Intent intent = PlaybackService.getPlayerActivityIntent(this);
|
||||
if (!intent.getComponent().getClassName().equals(VideoplayerActivity.class.getName())) {
|
||||
destroyingDueToReload = true;
|
||||
@ -489,6 +485,11 @@ public class VideoplayerActivity extends MediaplayerActivity {
|
||||
//Hardware keyboard support
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
View currentFocus = getCurrentFocus();
|
||||
if (currentFocus instanceof EditText) {
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
|
||||
|
||||
switch (keyCode) {
|
||||
|
@ -2,22 +2,19 @@ package de.danoeh.antennapod.activity;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.WallpaperManager;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RemoteViews;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
@ -29,7 +26,10 @@ public class WidgetConfigActivity extends AppCompatActivity {
|
||||
|
||||
private SeekBar opacitySeekBar;
|
||||
private TextView opacityTextView;
|
||||
private RelativeLayout widgetPreview;
|
||||
private View widgetPreview;
|
||||
private CheckBox ckRewind;
|
||||
private CheckBox ckFastForward;
|
||||
private CheckBox ckSkip;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -74,6 +74,32 @@ public class WidgetConfigActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
widgetPreview.findViewById(R.id.txtNoPlaying).setVisibility(View.GONE);
|
||||
TextView title = widgetPreview.findViewById(R.id.txtvTitle);
|
||||
title.setVisibility(View.VISIBLE);
|
||||
title.setText(R.string.app_name);
|
||||
TextView progress = widgetPreview.findViewById(R.id.txtvProgress);
|
||||
progress.setVisibility(View.VISIBLE);
|
||||
progress.setText(R.string.position_default_label);
|
||||
|
||||
ckRewind = findViewById(R.id.ckRewind);
|
||||
ckRewind.setOnClickListener(v -> displayPreviewPanel());
|
||||
ckFastForward = findViewById(R.id.ckFastForward);
|
||||
ckFastForward.setOnClickListener(v -> displayPreviewPanel());
|
||||
ckSkip = findViewById(R.id.ckSkip);
|
||||
ckSkip.setOnClickListener(v -> displayPreviewPanel());
|
||||
}
|
||||
|
||||
private void displayPreviewPanel() {
|
||||
boolean showExtendedPreview = ckRewind.isChecked() || ckFastForward.isChecked() || ckSkip.isChecked();
|
||||
widgetPreview.findViewById(R.id.extendedButtonsContainer)
|
||||
.setVisibility(showExtendedPreview ? View.VISIBLE : View.GONE);
|
||||
widgetPreview.findViewById(R.id.butPlay).setVisibility(showExtendedPreview ? View.GONE : View.VISIBLE);
|
||||
widgetPreview.findViewById(R.id.butFastForward)
|
||||
.setVisibility(ckFastForward.isChecked() ? View.VISIBLE : View.GONE);
|
||||
widgetPreview.findViewById(R.id.butSkip).setVisibility(ckSkip.isChecked() ? View.VISIBLE : View.GONE);
|
||||
widgetPreview.findViewById(R.id.butRew).setVisibility(ckRewind.isChecked() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void displayDeviceBackground() {
|
||||
@ -92,6 +118,9 @@ public class WidgetConfigActivity extends AppCompatActivity {
|
||||
SharedPreferences prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(PlayerWidget.KEY_WIDGET_COLOR + appWidgetId, backgroundColor);
|
||||
editor.putBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, ckSkip.isChecked());
|
||||
editor.putBoolean(PlayerWidget.KEY_WIDGET_REWIND + appWidgetId, ckRewind.isChecked());
|
||||
editor.putBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + appWidgetId, ckFastForward.isChecked());
|
||||
editor.apply();
|
||||
|
||||
Intent resultValue = new Intent();
|
||||
|
@ -1,395 +0,0 @@
|
||||
package de.danoeh.antennapod.activity.gpoddernet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ViewFlipper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import de.danoeh.antennapod.BuildConfig;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
|
||||
import de.danoeh.antennapod.core.sync.SyncService;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetDevice;
|
||||
|
||||
/**
|
||||
* Guides the user through the authentication process
|
||||
* Step 1: Request username and password from user
|
||||
* Step 2: Choose device from a list of available devices or create a new one
|
||||
* Step 3: Choose from a list of actions
|
||||
*/
|
||||
public class GpodnetAuthenticationActivity extends AppCompatActivity {
|
||||
private static final String TAG = "GpodnetAuthActivity";
|
||||
|
||||
private ViewFlipper viewFlipper;
|
||||
|
||||
private static final int STEP_DEFAULT = -1;
|
||||
private static final int STEP_LOGIN = 0;
|
||||
private static final int STEP_DEVICE = 1;
|
||||
private static final int STEP_FINISH = 2;
|
||||
|
||||
private int currentStep = -1;
|
||||
|
||||
private GpodnetService service;
|
||||
private volatile String username;
|
||||
private volatile String password;
|
||||
private volatile GpodnetDevice selectedDevice;
|
||||
|
||||
private View[] views;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(UserPreferences.getTheme());
|
||||
super.onCreate(savedInstanceState);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
setContentView(R.layout.gpodnetauth_activity);
|
||||
service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetPreferences.getHostname());
|
||||
|
||||
viewFlipper = findViewById(R.id.viewflipper);
|
||||
LayoutInflater inflater = (LayoutInflater)
|
||||
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
views = new View[]{
|
||||
inflater.inflate(R.layout.gpodnetauth_credentials, viewFlipper, false),
|
||||
inflater.inflate(R.layout.gpodnetauth_device, viewFlipper, false),
|
||||
inflater.inflate(R.layout.gpodnetauth_finish, viewFlipper, false)
|
||||
};
|
||||
for (View view : views) {
|
||||
viewFlipper.addView(view);
|
||||
}
|
||||
advance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void setupLoginView(View view) {
|
||||
final EditText username = view.findViewById(R.id.etxtUsername);
|
||||
final EditText password = view.findViewById(R.id.etxtPassword);
|
||||
final Button login = view.findViewById(R.id.butLogin);
|
||||
final TextView txtvError = view.findViewById(R.id.txtvError);
|
||||
final ProgressBar progressBar = view.findViewById(R.id.progBarLogin);
|
||||
|
||||
password.setOnEditorActionListener((v, actionID, event) ->
|
||||
actionID == EditorInfo.IME_ACTION_GO && login.performClick());
|
||||
|
||||
login.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
final String usernameStr = username.getText().toString();
|
||||
final String passwordStr = password.getText().toString();
|
||||
|
||||
if (usernameHasUnwantedChars(usernameStr)) {
|
||||
txtvError.setText(R.string.gpodnetsync_username_characters_error);
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Checking login credentials");
|
||||
AsyncTask<GpodnetService, Void, Void> authTask = new AsyncTask<GpodnetService, Void, Void>() {
|
||||
|
||||
volatile Exception exception;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
login.setEnabled(false);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
txtvError.setVisibility(View.GONE);
|
||||
// hide the keyboard
|
||||
InputMethodManager inputManager = (InputMethodManager)
|
||||
getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
inputManager.hideSoftInputFromWindow(login.getWindowToken(),
|
||||
InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
super.onPostExecute(aVoid);
|
||||
login.setEnabled(true);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
|
||||
if (exception == null) {
|
||||
advance();
|
||||
} else {
|
||||
txtvError.setText(exception.getCause().getMessage());
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(GpodnetService... params) {
|
||||
try {
|
||||
params[0].authenticate(usernameStr, passwordStr);
|
||||
GpodnetAuthenticationActivity.this.username = usernameStr;
|
||||
GpodnetAuthenticationActivity.this.password = passwordStr;
|
||||
} catch (GpodnetServiceException e) {
|
||||
e.printStackTrace();
|
||||
exception = e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
authTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, service);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupDeviceView(View view) {
|
||||
final EditText deviceID = view.findViewById(R.id.etxtDeviceID);
|
||||
final EditText caption = view.findViewById(R.id.etxtCaption);
|
||||
final Button createNewDevice = view.findViewById(R.id.butCreateNewDevice);
|
||||
final Button chooseDevice = view.findViewById(R.id.butChooseExistingDevice);
|
||||
final TextView txtvError = view.findViewById(R.id.txtvError);
|
||||
final ProgressBar progBarCreateDevice = view.findViewById(R.id.progbarCreateDevice);
|
||||
final Spinner spinnerDevices = view.findViewById(R.id.spinnerChooseDevice);
|
||||
|
||||
|
||||
// load device list
|
||||
final AtomicReference<List<GpodnetDevice>> devices = new AtomicReference<>();
|
||||
new AsyncTask<GpodnetService, Void, List<GpodnetDevice>>() {
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
chooseDevice.setEnabled(false);
|
||||
spinnerDevices.setEnabled(false);
|
||||
createNewDevice.setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<GpodnetDevice> gpodnetDevices) {
|
||||
super.onPostExecute(gpodnetDevices);
|
||||
if (gpodnetDevices != null) {
|
||||
List<String> deviceNames = new ArrayList<>();
|
||||
for (GpodnetDevice device : gpodnetDevices) {
|
||||
deviceNames.add(device.getCaption());
|
||||
}
|
||||
spinnerDevices.setAdapter(new ArrayAdapter<>(GpodnetAuthenticationActivity.this,
|
||||
android.R.layout.simple_spinner_dropdown_item, deviceNames));
|
||||
spinnerDevices.setEnabled(true);
|
||||
if (!deviceNames.isEmpty()) {
|
||||
chooseDevice.setEnabled(true);
|
||||
}
|
||||
devices.set(gpodnetDevices);
|
||||
deviceID.setText(generateDeviceID(gpodnetDevices));
|
||||
createNewDevice.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<GpodnetDevice> doInBackground(GpodnetService... params) {
|
||||
try {
|
||||
return params[0].getDevices();
|
||||
} catch (GpodnetServiceException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}.execute(service);
|
||||
|
||||
|
||||
createNewDevice.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (checkDeviceIDText(deviceID, caption, txtvError, devices.get())) {
|
||||
final String deviceStr = deviceID.getText().toString();
|
||||
final String captionStr = caption.getText().toString();
|
||||
|
||||
new AsyncTask<GpodnetService, Void, GpodnetDevice>() {
|
||||
|
||||
private volatile Exception exception;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
createNewDevice.setEnabled(false);
|
||||
chooseDevice.setEnabled(false);
|
||||
progBarCreateDevice.setVisibility(View.VISIBLE);
|
||||
txtvError.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(GpodnetDevice result) {
|
||||
super.onPostExecute(result);
|
||||
createNewDevice.setEnabled(true);
|
||||
chooseDevice.setEnabled(true);
|
||||
progBarCreateDevice.setVisibility(View.GONE);
|
||||
if (exception == null) {
|
||||
selectedDevice = result;
|
||||
advance();
|
||||
} else {
|
||||
txtvError.setText(exception.getMessage());
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GpodnetDevice doInBackground(GpodnetService... params) {
|
||||
try {
|
||||
params[0].configureDevice(deviceStr, captionStr, GpodnetDevice.DeviceType.MOBILE);
|
||||
return new GpodnetDevice(deviceStr, captionStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0);
|
||||
} catch (GpodnetServiceException e) {
|
||||
e.printStackTrace();
|
||||
exception = e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.execute(service);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
chooseDevice.setOnClickListener(v -> {
|
||||
final int position = spinnerDevices.getSelectedItemPosition();
|
||||
if (position != AdapterView.INVALID_POSITION) {
|
||||
selectedDevice = devices.get().get(position);
|
||||
advance();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private String generateDeviceID(List<GpodnetDevice> gpodnetDevices) {
|
||||
// devices names must be of a certain form:
|
||||
// https://gpoddernet.readthedocs.org/en/latest/api/reference/general.html#devices
|
||||
// This is more restrictive than needed, but I think it makes for more readable names.
|
||||
String baseId = Build.MODEL.replaceAll("\\W", "");
|
||||
String id = baseId;
|
||||
int num = 0;
|
||||
|
||||
while (isDeviceWithIdInList(id, gpodnetDevices)) {
|
||||
id = baseId + "_" + num;
|
||||
num++;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private boolean isDeviceWithIdInList(String id, List<GpodnetDevice> gpodnetDevices) {
|
||||
if (gpodnetDevices == null) {
|
||||
return false;
|
||||
}
|
||||
for (GpodnetDevice device : gpodnetDevices) {
|
||||
if (device.getId().equals(id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean checkDeviceIDText(EditText deviceID, EditText caption, TextView txtvError, List<GpodnetDevice> devices) {
|
||||
String text = deviceID.getText().toString();
|
||||
if (text.length() == 0) {
|
||||
txtvError.setText(R.string.gpodnetauth_device_errorEmpty);
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
return false;
|
||||
} else if (caption.length() == 0) {
|
||||
txtvError.setText(R.string.gpodnetauth_device_caption_errorEmpty);
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
return false;
|
||||
} else {
|
||||
if (devices != null) {
|
||||
if (isDeviceWithIdInList(text, devices)) {
|
||||
txtvError.setText(R.string.gpodnetauth_device_errorAlreadyUsed);
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
return false;
|
||||
}
|
||||
txtvError.setVisibility(View.GONE);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void setupFinishView(View view) {
|
||||
final Button sync = view.findViewById(R.id.butSyncNow);
|
||||
final Button back = view.findViewById(R.id.butGoMainscreen);
|
||||
|
||||
sync.setOnClickListener(v -> {
|
||||
finish();
|
||||
SyncService.sync(getApplicationContext());
|
||||
});
|
||||
back.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(GpodnetAuthenticationActivity.this, MainActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
});
|
||||
}
|
||||
|
||||
private void writeLoginCredentials() {
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Writing login credentials");
|
||||
GpodnetPreferences.setUsername(username);
|
||||
GpodnetPreferences.setPassword(password);
|
||||
GpodnetPreferences.setDeviceID(selectedDevice.getId());
|
||||
}
|
||||
|
||||
private void advance() {
|
||||
if (currentStep < STEP_FINISH) {
|
||||
|
||||
View view = views[currentStep + 1];
|
||||
if (currentStep == STEP_DEFAULT) {
|
||||
setupLoginView(view);
|
||||
} else if (currentStep == STEP_LOGIN) {
|
||||
if (username == null || password == null) {
|
||||
throw new IllegalStateException("Username and password must not be null here");
|
||||
} else {
|
||||
setupDeviceView(view);
|
||||
}
|
||||
} else if (currentStep == STEP_DEVICE) {
|
||||
if (selectedDevice == null) {
|
||||
throw new IllegalStateException("Device must not be null here");
|
||||
} else {
|
||||
writeLoginCredentials();
|
||||
setupFinishView(view);
|
||||
}
|
||||
}
|
||||
if (currentStep != STEP_DEFAULT) {
|
||||
viewFlipper.showNext();
|
||||
}
|
||||
currentStep++;
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean usernameHasUnwantedChars(String username) {
|
||||
Pattern special = Pattern.compile("[!@#$%&*()+=|<>?{}\\[\\]~]");
|
||||
Matcher containsUnwantedChars = special.matcher(username);
|
||||
return containsUnwantedChars.find();
|
||||
}
|
||||
}
|
@ -22,12 +22,14 @@ import de.danoeh.antennapod.core.util.EmbeddedChapterImage;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import de.danoeh.antennapod.core.util.ThemeUtils;
|
||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||
import de.danoeh.antennapod.view.CircularProgressBar;
|
||||
|
||||
public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapter.ChapterHolder> {
|
||||
private Playable media;
|
||||
private final Callback callback;
|
||||
private final Context context;
|
||||
private int currentChapterIndex = -1;
|
||||
private long currentChapterPosition = -1;
|
||||
private boolean hasImages = false;
|
||||
|
||||
public ChaptersListAdapter(Context context, Callback callback) {
|
||||
@ -48,7 +50,6 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ChapterHolder holder, int position) {
|
||||
Chapter sc = getItem(position);
|
||||
@ -65,7 +66,7 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte
|
||||
holder.duration.setText(context.getString(R.string.chapter_duration,
|
||||
Converter.getDurationStringLocalized(context, (int) duration)));
|
||||
|
||||
if (sc.getLink() == null) {
|
||||
if (TextUtils.isEmpty(sc.getLink())) {
|
||||
holder.link.setVisibility(View.GONE);
|
||||
} else {
|
||||
holder.link.setVisibility(View.VISIBLE);
|
||||
@ -73,6 +74,7 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte
|
||||
holder.link.setOnClickListener(v -> IntentUtils.openInBrowser(context, sc.getLink()));
|
||||
}
|
||||
holder.secondaryActionIcon.setImageResource(ThemeUtils.getDrawableFromAttr(context, R.attr.av_play));
|
||||
holder.secondaryActionButton.setContentDescription(context.getString(R.string.play_chapter));
|
||||
holder.secondaryActionButton.setOnClickListener(v -> {
|
||||
if (callback != null) {
|
||||
callback.onPlayChapterButtonClicked(position);
|
||||
@ -82,8 +84,14 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte
|
||||
if (position == currentChapterIndex) {
|
||||
int playingBackGroundColor = ThemeUtils.getColorFromAttr(context, R.attr.currently_playing_background);
|
||||
holder.itemView.setBackgroundColor(playingBackGroundColor);
|
||||
float progress = ((float) (currentChapterPosition - sc.getStart())) / duration;
|
||||
progress = Math.max(progress, CircularProgressBar.MINIMUM_PERCENTAGE);
|
||||
progress = Math.min(progress, CircularProgressBar.MAXIMUM_PERCENTAGE);
|
||||
holder.progressBar.setPercentage(progress, position);
|
||||
holder.secondaryActionIcon.setImageResource(ThemeUtils.getDrawableFromAttr(context, R.attr.av_replay));
|
||||
} else {
|
||||
holder.itemView.setBackgroundColor(ContextCompat.getColor(context, android.R.color.transparent));
|
||||
holder.progressBar.setPercentage(0, null);
|
||||
}
|
||||
|
||||
if (hasImages) {
|
||||
@ -135,6 +143,7 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte
|
||||
final ImageView image;
|
||||
final View secondaryActionButton;
|
||||
final ImageView secondaryActionIcon;
|
||||
final CircularProgressBar progressBar;
|
||||
|
||||
public ChapterHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
@ -145,14 +154,23 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte
|
||||
duration = itemView.findViewById(R.id.txtvDuration);
|
||||
secondaryActionButton = itemView.findViewById(R.id.secondaryActionButton);
|
||||
secondaryActionIcon = itemView.findViewById(R.id.secondaryActionIcon);
|
||||
progressBar = itemView.findViewById(R.id.secondaryActionProgress);
|
||||
}
|
||||
}
|
||||
|
||||
public void notifyChapterChanged(int newChapterIndex) {
|
||||
currentChapterIndex = newChapterIndex;
|
||||
currentChapterPosition = getItem(newChapterIndex).getStart();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void notifyTimeChanged(long timeMs) {
|
||||
currentChapterPosition = timeMs;
|
||||
// Passing an argument prevents flickering.
|
||||
// See EpisodeItemListAdapter.notifyItemChangedCompat.
|
||||
notifyItemChanged(currentChapterIndex, "foo");
|
||||
}
|
||||
|
||||
private boolean ignoreChapter(Chapter c) {
|
||||
return media.getDuration() > 0 && media.getDuration() < c.getStart();
|
||||
}
|
||||
|
@ -1,23 +1,15 @@
|
||||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.text.Layout;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.joanzapata.iconify.widget.IconButton;
|
||||
import com.joanzapata.iconify.widget.IconTextView;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
|
||||
|
@ -65,6 +65,7 @@ public class DownloadlistAdapter extends BaseAdapter {
|
||||
|
||||
holder.title.setText(request.getTitle());
|
||||
holder.secondaryActionIcon.setImageResource(ThemeUtils.getDrawableFromAttr(context, R.attr.navigation_cancel));
|
||||
holder.secondaryActionButton.setContentDescription(context.getString(R.string.cancel_download_label));
|
||||
holder.secondaryActionButton.setTag(downloader);
|
||||
holder.secondaryActionButton.setOnClickListener(butSecondaryListener);
|
||||
holder.secondaryActionProgress.setPercentage(0, request);
|
||||
|
@ -13,8 +13,6 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.feed.MediaType;
|
||||
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import de.danoeh.antennapod.core.util.NetworkUtils;
|
||||
import de.danoeh.antennapod.core.util.playback.RemoteMedia;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
|
@ -75,7 +75,7 @@ public class NavListAdapter extends BaseAdapter
|
||||
}
|
||||
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (key.equals(UserPreferences.PREF_HIDDEN_DRAWER_ITEMS)) {
|
||||
if (UserPreferences.PREF_HIDDEN_DRAWER_ITEMS.equals(key)) {
|
||||
loadItems();
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import android.view.ContextMenu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import androidx.core.view.MotionEventCompat;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
|
@ -13,8 +13,6 @@ import android.widget.BaseAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
@ -23,7 +21,6 @@ import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.feed.LocalFeedUpdater;
|
||||
import de.danoeh.antennapod.fragment.AddFeedFragment;
|
||||
import de.danoeh.antennapod.fragment.FeedItemlistFragment;
|
||||
import jp.shts.android.library.TriangleLabelView;
|
||||
|
||||
|
@ -7,11 +7,8 @@ import de.danoeh.antennapod.R;
|
||||
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.UsageStatistics;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.util.NetworkUtils;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
|
||||
import de.danoeh.antennapod.dialog.StreamingConfirmationDialog;
|
||||
|
||||
public class PlayLocalActionButton extends ItemActionButton {
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
package de.danoeh.antennapod.adapter.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.view.View;
|
||||
import androidx.annotation.AttrRes;
|
||||
import androidx.annotation.StringRes;
|
||||
|
@ -9,10 +9,10 @@ import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -44,7 +44,7 @@ public class DocumentFileExportWorker {
|
||||
if (outputStream == null) {
|
||||
throw new IOException();
|
||||
}
|
||||
writer = new OutputStreamWriter(outputStream, LangUtils.UTF_8);
|
||||
writer = new OutputStreamWriter(outputStream, Charset.forName("UTF-8"));
|
||||
exportWriter.writeDocument(DBReader.getFeedList(), writer, context);
|
||||
subscriber.onNext(output);
|
||||
} catch (IOException e) {
|
||||
|
@ -8,11 +8,11 @@ import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import de.danoeh.antennapod.core.export.ExportWriter;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.util.LangUtils;
|
||||
import io.reactivex.Observable;
|
||||
|
||||
/**
|
||||
@ -47,7 +47,7 @@ public class ExportWorker {
|
||||
return Observable.create(subscriber -> {
|
||||
OutputStreamWriter writer = null;
|
||||
try {
|
||||
writer = new OutputStreamWriter(new FileOutputStream(output), LangUtils.UTF_8);
|
||||
writer = new OutputStreamWriter(new FileOutputStream(output), Charset.forName("UTF-8"));
|
||||
exportWriter.writeDocument(DBReader.getFeedList(), writer, context);
|
||||
subscriber.onNext(output);
|
||||
} catch (IOException e) {
|
||||
|
@ -2,6 +2,7 @@ package de.danoeh.antennapod.config;
|
||||
|
||||
import de.danoeh.antennapod.BuildConfig;
|
||||
import de.danoeh.antennapod.core.ClientConfig;
|
||||
import de.danoeh.antennapod.core.storage.APDownloadAlgorithm;
|
||||
|
||||
/**
|
||||
* Configures the ClientConfig class of the core package.
|
||||
@ -15,7 +16,7 @@ class ClientConfigurator {
|
||||
ClientConfig.applicationCallbacks = new ApplicationCallbacksImpl();
|
||||
ClientConfig.downloadServiceCallbacks = new DownloadServiceCallbacksImpl();
|
||||
ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl();
|
||||
ClientConfig.dbTasksCallbacks = new DBTasksCallbacksImpl();
|
||||
ClientConfig.automaticDownloadAlgorithm = new APDownloadAlgorithm();
|
||||
ClientConfig.castCallbacks = new CastCallbackImpl();
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
package de.danoeh.antennapod.config;
|
||||
|
||||
import de.danoeh.antennapod.core.DBTasksCallbacks;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.storage.APDownloadAlgorithm;
|
||||
import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm;
|
||||
import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
|
||||
|
||||
public class DBTasksCallbacksImpl implements DBTasksCallbacks {
|
||||
|
||||
@Override
|
||||
public AutomaticDownloadAlgorithm getAutomaticDownloadAlgorithm() {
|
||||
return new APDownloadAlgorithm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EpisodeCleanupAlgorithm getEpisodeCacheCleanupAlgorithm() {
|
||||
return UserPreferences.getEpisodeCleanupAlgorithm();
|
||||
}
|
||||
}
|
@ -31,7 +31,6 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks {
|
||||
public PendingIntent getAuthentificationNotificationContentIntent(Context context, DownloadRequest request) {
|
||||
final Intent activityIntent = new Intent(context.getApplicationContext(), DownloadAuthenticationActivity.class);
|
||||
activityIntent.putExtra(DownloadAuthenticationActivity.ARG_DOWNLOAD_REQUEST, request);
|
||||
activityIntent.putExtra(DownloadAuthenticationActivity.ARG_SEND_TO_DOWNLOAD_REQUESTER_BOOL, true);
|
||||
return PendingIntent.getActivity(context.getApplicationContext(),
|
||||
R.id.pending_intent_download_service_auth, activityIntent, PendingIntent.FLAG_ONE_SHOT);
|
||||
}
|
||||
@ -54,9 +53,4 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks {
|
||||
return PendingIntent.getActivity(context, R.id.pending_intent_download_service_autodownload_report,
|
||||
intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldCreateReport() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package de.danoeh.antennapod.config;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.activity.VideoplayerActivity;
|
||||
import de.danoeh.antennapod.core.PlaybackServiceCallbacks;
|
||||
@ -22,10 +21,4 @@ public class PlaybackServiceCallbacksImpl implements PlaybackServiceCallbacks {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useQueue() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -436,13 +436,18 @@ public class EpisodesApplyActionFragment extends Fragment implements Toolbar.OnM
|
||||
}
|
||||
|
||||
private void deleteChecked() {
|
||||
int countHasMedia = 0;
|
||||
int countNoMedia = 0;
|
||||
for (long id : checkedIds.toArray()) {
|
||||
FeedItem episode = idMap.get(id);
|
||||
if (episode.hasMedia()) {
|
||||
if (episode.hasMedia() && episode.getMedia().isDownloaded()) {
|
||||
countHasMedia++;
|
||||
DBWriter.deleteFeedMediaOfItem(getActivity(), episode.getMedia().getId());
|
||||
} else {
|
||||
countNoMedia++;
|
||||
}
|
||||
}
|
||||
close(R.plurals.deleted_episode_batch_label, checkedIds.size());
|
||||
closeMore(R.plurals.deleted_multi_episode_batch_label, countNoMedia, countHasMedia);
|
||||
}
|
||||
|
||||
private void close(@PluralsRes int msgId, int numItems) {
|
||||
@ -451,4 +456,12 @@ public class EpisodesApplyActionFragment extends Fragment implements Toolbar.OnM
|
||||
getActivity().getSupportFragmentManager().popBackStack();
|
||||
}
|
||||
|
||||
private void closeMore(@PluralsRes int msgId, int countNoMedia, int countHasMedia) {
|
||||
((MainActivity) getActivity()).showSnackbarAbovePlayer(
|
||||
getResources().getQuantityString(msgId,
|
||||
(countHasMedia + countNoMedia),
|
||||
(countHasMedia + countNoMedia), countHasMedia),
|
||||
Snackbar.LENGTH_LONG);
|
||||
getActivity().getSupportFragmentManager().popBackStack();
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package de.danoeh.antennapod.dialog;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RadioButton;
|
||||
|
||||
@ -34,18 +35,19 @@ public abstract class FilterDialog {
|
||||
builder.setTitle(R.string.filter);
|
||||
|
||||
LayoutInflater inflater = LayoutInflater.from(this.context);
|
||||
LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.filter_dialog, null, false);
|
||||
View layout = inflater.inflate(R.layout.filter_dialog, null, false);
|
||||
LinearLayout rows = layout.findViewById(R.id.filter_rows);
|
||||
builder.setView(layout);
|
||||
|
||||
for (FeedItemFilterGroup item : FeedItemFilterGroup.values()) {
|
||||
RecursiveRadioGroup row = (RecursiveRadioGroup) inflater.inflate(R.layout.filter_dialog_row, null);
|
||||
RecursiveRadioGroup row = (RecursiveRadioGroup) inflater.inflate(R.layout.filter_dialog_row, null, false);
|
||||
RadioButton filter1 = row.findViewById(R.id.filter_dialog_radioButton1);
|
||||
RadioButton filter2 = row.findViewById(R.id.filter_dialog_radioButton2);
|
||||
filter1.setText(item.values[0].displayName);
|
||||
filter1.setTag(item.values[0].filterId);
|
||||
filter2.setText(item.values[1].displayName);
|
||||
filter2.setTag(item.values[1].filterId);
|
||||
layout.addView(row);
|
||||
rows.addView(row);
|
||||
}
|
||||
|
||||
for (String filterId : filterValues) {
|
||||
@ -56,11 +58,11 @@ public abstract class FilterDialog {
|
||||
|
||||
builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
|
||||
filterValues.clear();
|
||||
for (int i = 0; i < layout.getChildCount(); i++) {
|
||||
if (!(layout.getChildAt(i) instanceof RecursiveRadioGroup)) {
|
||||
for (int i = 0; i < rows.getChildCount(); i++) {
|
||||
if (!(rows.getChildAt(i) instanceof RecursiveRadioGroup)) {
|
||||
continue;
|
||||
}
|
||||
RecursiveRadioGroup group = (RecursiveRadioGroup) layout.getChildAt(i);
|
||||
RecursiveRadioGroup group = (RecursiveRadioGroup) rows.getChildAt(i);
|
||||
if (group.getCheckedButton() != null) {
|
||||
String tag = (String) group.getCheckedButton().getTag();
|
||||
if (tag != null) { // Clear buttons use no tag
|
||||
|
@ -1,59 +0,0 @@
|
||||
package de.danoeh.antennapod.dialog;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
|
||||
|
||||
/**
|
||||
* Creates a dialog that lets the user change the hostname for the gpodder.net service.
|
||||
*/
|
||||
public class GpodnetSetHostnameDialog {
|
||||
|
||||
private GpodnetSetHostnameDialog(){}
|
||||
|
||||
private static final String TAG = "GpodnetSetHostnameDialog";
|
||||
|
||||
public static AlertDialog createDialog(final Context context) {
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
|
||||
final EditText et = new EditText(context);
|
||||
et.setText(GpodnetPreferences.getHostname());
|
||||
et.setInputType(InputType.TYPE_TEXT_VARIATION_URI);
|
||||
dialog.setTitle(R.string.pref_gpodnet_sethostname_title)
|
||||
.setView(setupContentView(context, et))
|
||||
.setPositiveButton(R.string.confirm_label, (dialog1, which) -> {
|
||||
final Editable e = et.getText();
|
||||
if (e != null) {
|
||||
GpodnetPreferences.setHostname(e.toString());
|
||||
}
|
||||
dialog1.dismiss();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel_label, (dialog1, which) -> dialog1.cancel())
|
||||
.setNeutralButton(R.string.pref_gpodnet_sethostname_use_default_host, (dialog1, which) -> {
|
||||
GpodnetPreferences.setHostname(GpodnetService.DEFAULT_BASE_HOST);
|
||||
dialog1.dismiss();
|
||||
})
|
||||
.setCancelable(true);
|
||||
return dialog.show();
|
||||
}
|
||||
|
||||
private static View setupContentView(Context context, EditText et) {
|
||||
LinearLayout ll = new LinearLayout(context);
|
||||
ll.addView(et);
|
||||
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) et.getLayoutParams();
|
||||
if (params != null) {
|
||||
params.setMargins(8, 8, 8, 8);
|
||||
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
params.height = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
}
|
||||
return ll;
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package de.danoeh.antennapod.dialog;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.util.Log;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class RemoveFeedDialog {
|
||||
private static final String TAG = "RemoveFeedDialog";
|
||||
|
||||
public static void show(Context context, Feed feed, Runnable onSuccess) {
|
||||
int messageId = feed.isLocalFeed() ? R.string.feed_delete_confirmation_local_msg
|
||||
: R.string.feed_delete_confirmation_msg;
|
||||
String message = context.getString(messageId, feed.getTitle());
|
||||
|
||||
ConfirmationDialog dialog = new ConfirmationDialog(context, R.string.remove_feed_label, message) {
|
||||
@Override
|
||||
public void onConfirmButtonPressed(DialogInterface clickedDialog) {
|
||||
clickedDialog.dismiss();
|
||||
|
||||
ProgressDialog progressDialog = new ProgressDialog(context);
|
||||
progressDialog.setMessage(context.getString(R.string.feed_remover_msg));
|
||||
progressDialog.setIndeterminate(true);
|
||||
progressDialog.setCancelable(false);
|
||||
progressDialog.show();
|
||||
|
||||
Completable.fromCallable(() -> DBWriter.deleteFeed(context, feed.getId()).get())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
() -> {
|
||||
Log.d(TAG, "Feed was deleted");
|
||||
if (onSuccess != null) {
|
||||
onSuccess.run();
|
||||
}
|
||||
progressDialog.dismiss();
|
||||
}, error -> {
|
||||
Log.e(TAG, Log.getStackTraceString(error));
|
||||
progressDialog.dismiss();
|
||||
});
|
||||
}
|
||||
};
|
||||
dialog.createNewDialog().show();
|
||||
}
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
package de.danoeh.antennapod.dialog;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.text.InputType;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.databinding.EditTextDialogBinding;
|
||||
|
||||
public class RenameFeedDialog {
|
||||
|
||||
@ -29,13 +28,14 @@ public class RenameFeedDialog {
|
||||
}
|
||||
|
||||
View content = View.inflate(activity, R.layout.edit_text_dialog, null);
|
||||
EditText editText = content.findViewById(R.id.text);
|
||||
editText.setText(feed.getTitle());
|
||||
EditTextDialogBinding alertViewBinding = EditTextDialogBinding.bind(content);
|
||||
|
||||
alertViewBinding.urlEditText.setText(feed.getTitle());
|
||||
AlertDialog dialog = new AlertDialog.Builder(activity)
|
||||
.setView(content)
|
||||
.setTitle(de.danoeh.antennapod.core.R.string.rename_feed_label)
|
||||
.setPositiveButton(android.R.string.ok, (d, input) -> {
|
||||
feed.setCustomTitle(editText.getText().toString());
|
||||
feed.setCustomTitle(alertViewBinding.urlEditText.getText().toString());
|
||||
DBWriter.setFeedCustomTitle(feed);
|
||||
})
|
||||
.setNeutralButton(de.danoeh.antennapod.core.R.string.reset, null)
|
||||
@ -44,7 +44,7 @@ public class RenameFeedDialog {
|
||||
|
||||
// To prevent cancelling the dialog on button click
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(
|
||||
(view) -> editText.setText(feed.getFeedTitle()));
|
||||
(view) -> alertViewBinding.urlEditText.setText(feed.getFeedTitle()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package de.danoeh.antennapod.dialog;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
@ -88,6 +87,27 @@ public class SleepTimerDialog extends DialogFragment {
|
||||
timeSetup = content.findViewById(R.id.timeSetup);
|
||||
timeDisplay = content.findViewById(R.id.timeDisplay);
|
||||
time = content.findViewById(R.id.time);
|
||||
Button extendSleepFiveMinutesButton = content.findViewById(R.id.extendSleepFiveMinutesButton);
|
||||
extendSleepFiveMinutesButton.setText(getString(R.string.extend_sleep_timer_label, 5));
|
||||
Button extendSleepTenMinutesButton = content.findViewById(R.id.extendSleepTenMinutesButton);
|
||||
extendSleepTenMinutesButton.setText(getString(R.string.extend_sleep_timer_label, 10));
|
||||
Button extendSleepTwentyMinutesButton = content.findViewById(R.id.extendSleepTwentyMinutesButton);
|
||||
extendSleepTwentyMinutesButton.setText(getString(R.string.extend_sleep_timer_label, 20));
|
||||
extendSleepFiveMinutesButton.setOnClickListener(v -> {
|
||||
if (controller != null) {
|
||||
controller.extendSleepTimer(5 * 1000 * 60);
|
||||
}
|
||||
});
|
||||
extendSleepTenMinutesButton.setOnClickListener(v -> {
|
||||
if (controller != null) {
|
||||
controller.extendSleepTimer(10 * 1000 * 60);
|
||||
}
|
||||
});
|
||||
extendSleepTwentyMinutesButton.setOnClickListener(v -> {
|
||||
if (controller != null) {
|
||||
controller.extendSleepTimer(20 * 1000 * 60);
|
||||
}
|
||||
});
|
||||
|
||||
etxtTime.setText(SleepTimerPreferences.lastTimerValue());
|
||||
etxtTime.postDelayed(() -> {
|
||||
|
@ -30,7 +30,8 @@ public class SubscriptionsFilterDialog {
|
||||
builder.setTitle(context.getString(R.string.pref_filter_feed_title));
|
||||
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.filter_dialog, null, false);
|
||||
View layout = inflater.inflate(R.layout.filter_dialog, null, false);
|
||||
LinearLayout rows = layout.findViewById(R.id.filter_rows);
|
||||
builder.setView(layout);
|
||||
|
||||
for (SubscriptionsFilterGroup item : SubscriptionsFilterGroup.values()) {
|
||||
@ -45,7 +46,7 @@ public class SubscriptionsFilterDialog {
|
||||
} else {
|
||||
filter2.setVisibility(View.GONE);
|
||||
}
|
||||
layout.addView(row);
|
||||
rows.addView(row);
|
||||
}
|
||||
|
||||
for (String filterId : filterValues) {
|
||||
@ -56,11 +57,11 @@ public class SubscriptionsFilterDialog {
|
||||
|
||||
builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
|
||||
filterValues.clear();
|
||||
for (int i = 0; i < layout.getChildCount(); i++) {
|
||||
if (!(layout.getChildAt(i) instanceof RecursiveRadioGroup)) {
|
||||
for (int i = 0; i < rows.getChildCount(); i++) {
|
||||
if (!(rows.getChildAt(i) instanceof RecursiveRadioGroup)) {
|
||||
continue;
|
||||
}
|
||||
RecursiveRadioGroup group = (RecursiveRadioGroup) layout.getChildAt(i);
|
||||
RecursiveRadioGroup group = (RecursiveRadioGroup) rows.getChildAt(i);
|
||||
if (group.getCheckedButton() != null) {
|
||||
String tag = (String) group.getCheckedButton().getTag();
|
||||
if (tag != null) { // Clear buttons use no tag
|
||||
|
@ -1,7 +1,5 @@
|
||||
package de.danoeh.antennapod.discovery;
|
||||
|
||||
import android.content.Context;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.SingleOnSubscribe;
|
||||
|
@ -1,8 +1,6 @@
|
||||
package de.danoeh.antennapod.discovery;
|
||||
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import java.util.List;
|
||||
|
||||
public interface PodcastSearcher {
|
||||
|
@ -12,7 +12,6 @@ import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@ -30,6 +29,8 @@ import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.storage.DownloadRequestException;
|
||||
import de.danoeh.antennapod.core.util.SortOrder;
|
||||
import de.danoeh.antennapod.databinding.AddfeedBinding;
|
||||
import de.danoeh.antennapod.databinding.EditTextDialogBinding;
|
||||
import de.danoeh.antennapod.discovery.CombinedSearcher;
|
||||
import de.danoeh.antennapod.discovery.FyydPodcastSearcher;
|
||||
import de.danoeh.antennapod.discovery.ItunesPodcastSearcher;
|
||||
@ -50,7 +51,7 @@ public class AddFeedFragment extends Fragment {
|
||||
private static final int REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH = 1;
|
||||
private static final int REQUEST_CODE_ADD_LOCAL_FOLDER = 2;
|
||||
|
||||
private EditText combinedFeedSearchBox;
|
||||
private AddfeedBinding viewBinding;
|
||||
private MainActivity activity;
|
||||
|
||||
@Override
|
||||
@ -59,39 +60,43 @@ public class AddFeedFragment extends Fragment {
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
View root = inflater.inflate(R.layout.addfeed, container, false);
|
||||
viewBinding = AddfeedBinding.inflate(getLayoutInflater());
|
||||
activity = (MainActivity) getActivity();
|
||||
Toolbar toolbar = root.findViewById(R.id.toolbar);
|
||||
|
||||
Toolbar toolbar = viewBinding.toolbar;
|
||||
((MainActivity) getActivity()).setupToolbarToggle(toolbar);
|
||||
|
||||
root.findViewById(R.id.btn_search_itunes).setOnClickListener(v
|
||||
viewBinding.searchItunesButton.setOnClickListener(v
|
||||
-> activity.loadChildFragment(OnlineSearchFragment.newInstance(ItunesPodcastSearcher.class)));
|
||||
root.findViewById(R.id.btn_search_fyyd).setOnClickListener(v
|
||||
viewBinding.searchFyydButton.setOnClickListener(v
|
||||
-> activity.loadChildFragment(OnlineSearchFragment.newInstance(FyydPodcastSearcher.class)));
|
||||
root.findViewById(R.id.btn_search_gpodder).setOnClickListener(v
|
||||
viewBinding.searchGPodderButton.setOnClickListener(v
|
||||
-> activity.loadChildFragment(new GpodnetMainFragment()));
|
||||
root.findViewById(R.id.btn_search_podcastindex).setOnClickListener(v
|
||||
viewBinding.searchPodcastIndexButton.setOnClickListener(v
|
||||
-> activity.loadChildFragment(OnlineSearchFragment.newInstance(PodcastIndexPodcastSearcher.class)));
|
||||
|
||||
combinedFeedSearchBox = root.findViewById(R.id.combinedFeedSearchBox);
|
||||
combinedFeedSearchBox.setOnEditorActionListener((v, actionId, event) -> {
|
||||
viewBinding.combinedFeedSearchEditText.setOnEditorActionListener((v, actionId, event) -> {
|
||||
performSearch();
|
||||
return true;
|
||||
});
|
||||
root.findViewById(R.id.btn_add_via_url).setOnClickListener(v
|
||||
|
||||
viewBinding.addViaUrlButton.setOnClickListener(v
|
||||
-> showAddViaUrlDialog());
|
||||
|
||||
root.findViewById(R.id.btn_opml_import).setOnClickListener(v -> {
|
||||
viewBinding.opmlImportButton.setOnClickListener(v -> {
|
||||
try {
|
||||
Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intentGetContentAction.setType("*/*");
|
||||
startActivityForResult(intentGetContentAction, REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(TAG, "No activity found. Should never happen...");
|
||||
e.printStackTrace();
|
||||
((MainActivity) getActivity())
|
||||
.showSnackbarAbovePlayer(R.string.unable_to_start_system_file_manager, Snackbar.LENGTH_LONG);
|
||||
}
|
||||
});
|
||||
root.findViewById(R.id.btn_add_local_folder).setOnClickListener(v -> {
|
||||
|
||||
viewBinding.addLocalFolderButton.setOnClickListener(v -> {
|
||||
if (Build.VERSION.SDK_INT < 21) {
|
||||
return;
|
||||
}
|
||||
@ -100,29 +105,35 @@ public class AddFeedFragment extends Fragment {
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
startActivityForResult(intent, REQUEST_CODE_ADD_LOCAL_FOLDER);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(TAG, "No activity found. Should never happen...");
|
||||
e.printStackTrace();
|
||||
((MainActivity) getActivity())
|
||||
.showSnackbarAbovePlayer(R.string.unable_to_start_system_file_manager, Snackbar.LENGTH_LONG);
|
||||
}
|
||||
});
|
||||
if (Build.VERSION.SDK_INT < 21) {
|
||||
root.findViewById(R.id.btn_add_local_folder).setVisibility(View.GONE);
|
||||
viewBinding.addLocalFolderButton.setVisibility(View.GONE);
|
||||
}
|
||||
root.findViewById(R.id.search_icon).setOnClickListener(view -> performSearch());
|
||||
return root;
|
||||
|
||||
viewBinding.searchButton.setOnClickListener(view -> performSearch());
|
||||
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
private void showAddViaUrlDialog() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
builder.setTitle(R.string.add_podcast_by_url);
|
||||
View content = View.inflate(getContext(), R.layout.edit_text_dialog, null);
|
||||
EditText editText = content.findViewById(R.id.text);
|
||||
editText.setHint(R.string.add_podcast_by_url_hint);
|
||||
EditTextDialogBinding alertViewBinding = EditTextDialogBinding.bind(content);
|
||||
alertViewBinding.urlEditText.setHint(R.string.add_podcast_by_url_hint);
|
||||
|
||||
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
String clipboardContent = clipboard.getText() != null ? clipboard.getText().toString() : "";
|
||||
if (clipboardContent.trim().startsWith("http")) {
|
||||
editText.setText(clipboardContent.trim());
|
||||
alertViewBinding.urlEditText.setText(clipboardContent.trim());
|
||||
}
|
||||
builder.setView(content);
|
||||
builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> addUrl(editText.getText().toString()));
|
||||
builder.setView(alertViewBinding.getRoot());
|
||||
builder.setPositiveButton(R.string.confirm_label,
|
||||
(dialog, which) -> addUrl(alertViewBinding.urlEditText.getText().toString()));
|
||||
builder.setNegativeButton(R.string.cancel_label, null);
|
||||
builder.show();
|
||||
}
|
||||
@ -134,8 +145,7 @@ public class AddFeedFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void performSearch() {
|
||||
String query = combinedFeedSearchBox.getText().toString();
|
||||
|
||||
String query = viewBinding.combinedFeedSearchEditText.getText().toString();
|
||||
if (query.matches("http[s]?://.*")) {
|
||||
addUrl(query);
|
||||
return;
|
||||
@ -187,7 +197,6 @@ public class AddFeedFragment extends Fragment {
|
||||
throw new IllegalArgumentException("Unable to retrieve document tree");
|
||||
}
|
||||
Feed dirFeed = new Feed(Feed.PREFIX_LOCAL_FOLDER + uri.toString(), null, documentFile.getName());
|
||||
dirFeed.setDescription(getString(R.string.local_feed_description));
|
||||
dirFeed.setItems(Collections.emptyList());
|
||||
dirFeed.setSortOrder(SortOrder.EPISODE_TITLE_A_Z);
|
||||
Feed fromDatabase = DBTasks.updateFeed(getContext(), dirFeed, false);
|
||||
|
@ -4,7 +4,6 @@ import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -22,6 +22,8 @@ import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.CastEnabledActivity;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
@ -43,7 +45,6 @@ import de.danoeh.antennapod.dialog.SkipPreferenceDialog;
|
||||
import de.danoeh.antennapod.dialog.SleepTimerDialog;
|
||||
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
|
||||
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
|
||||
import de.danoeh.antennapod.view.PagerIndicatorView;
|
||||
import de.danoeh.antennapod.view.PlaybackSpeedIndicatorView;
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
@ -74,7 +75,6 @@ public class AudioPlayerFragment extends Fragment implements
|
||||
PlaybackSpeedIndicatorView butPlaybackSpeed;
|
||||
TextView txtvPlaybackSpeed;
|
||||
private ViewPager2 pager;
|
||||
private PagerIndicatorView pageIndicator;
|
||||
private TextView txtvPosition;
|
||||
private TextView txtvLength;
|
||||
private SeekBar sbPosition;
|
||||
@ -90,6 +90,8 @@ public class AudioPlayerFragment extends Fragment implements
|
||||
private PlaybackController controller;
|
||||
private Disposable disposable;
|
||||
private boolean showTimeLeft;
|
||||
private boolean hasChapters = false;
|
||||
private TabLayoutMediator tabLayoutMediator;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@ -141,13 +143,37 @@ public class AudioPlayerFragment extends Fragment implements
|
||||
});
|
||||
}
|
||||
});
|
||||
pageIndicator = root.findViewById(R.id.page_indicator);
|
||||
pageIndicator.setViewPager(pager);
|
||||
pageIndicator.setOnClickListener(v ->
|
||||
pager.setCurrentItem((pager.getCurrentItem() + 1) % NUM_CONTENT_FRAGMENTS));
|
||||
|
||||
TabLayout tabLayout = root.findViewById(R.id.sliding_tabs);
|
||||
tabLayoutMediator = new TabLayoutMediator(tabLayout, pager, (tab, position) -> {
|
||||
tab.view.setAlpha(1.0f);
|
||||
switch (position) {
|
||||
case POS_COVER:
|
||||
tab.setText(R.string.cover_label);
|
||||
break;
|
||||
case POS_DESCR:
|
||||
tab.setText(R.string.description_label);
|
||||
break;
|
||||
case POS_CHAPTERS:
|
||||
tab.setText(R.string.chapters_label);
|
||||
if (!hasChapters) {
|
||||
tab.view.setAlpha(0.5f);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
tabLayoutMediator.attach();
|
||||
return root;
|
||||
}
|
||||
|
||||
public void setHasChapters(boolean hasChapters) {
|
||||
this.hasChapters = hasChapters;
|
||||
tabLayoutMediator.detach();
|
||||
tabLayoutMediator.attach();
|
||||
}
|
||||
|
||||
public View getExternalPlayerHolder() {
|
||||
return getView().findViewById(R.id.playerFragment);
|
||||
}
|
||||
@ -362,10 +388,6 @@ public class AudioPlayerFragment extends Fragment implements
|
||||
setupOptionsMenu(media);
|
||||
}
|
||||
|
||||
public void setHasChapters(boolean hasChapters) {
|
||||
pageIndicator.setDisabledPage(hasChapters ? -1 : 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
@ -106,6 +106,7 @@ public class ChaptersFragment extends Fragment {
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(PlaybackPositionEvent event) {
|
||||
updateChapterSelection(getCurrentChapter(media));
|
||||
adapter.notifyTimeChanged(event.getPosition());
|
||||
}
|
||||
|
||||
private int getCurrentChapter(Playable media) {
|
||||
|
@ -4,7 +4,6 @@ import android.os.Bundle;
|
||||
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;
|
||||
|
@ -4,7 +4,6 @@ import android.app.Dialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@ -13,7 +12,6 @@ import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.ListFragment;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
@ -176,7 +174,7 @@ public class DownloadLogFragment extends ListFragment {
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (!super.onOptionsItemSelected(item)) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.clear_history_item:
|
||||
case R.id.clear_logs_item:
|
||||
DBWriter.clearDownloadLog();
|
||||
return true;
|
||||
case R.id.refresh_item:
|
||||
|
@ -130,6 +130,8 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic
|
||||
protected void doTint(Context themedContext) {
|
||||
toolbar.getMenu().findItem(R.id.visit_website_item)
|
||||
.setIcon(ThemeUtils.getDrawableFromAttr(themedContext, R.attr.location_web_site));
|
||||
toolbar.getMenu().findItem(R.id.share_parent)
|
||||
.setIcon(ThemeUtils.getDrawableFromAttr(themedContext, R.attr.ic_share));
|
||||
}
|
||||
};
|
||||
iconTintManager.updateTint();
|
||||
@ -284,8 +286,13 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic
|
||||
}
|
||||
|
||||
private void refreshToolbarState() {
|
||||
boolean shareLinkVisible = feed != null && feed.getLink() != null;
|
||||
boolean downloadUrlVisible = feed != null && !feed.isLocalFeed();
|
||||
|
||||
toolbar.getMenu().findItem(R.id.reconnect_local_folder).setVisible(feed != null && feed.isLocalFeed());
|
||||
toolbar.getMenu().findItem(R.id.share_link_item).setVisible(feed != null && feed.getLink() != null);
|
||||
toolbar.getMenu().findItem(R.id.share_download_url_item).setVisible(downloadUrlVisible);
|
||||
toolbar.getMenu().findItem(R.id.share_link_item).setVisible(shareLinkVisible);
|
||||
toolbar.getMenu().findItem(R.id.share_parent).setVisible(downloadUrlVisible || shareLinkVisible);
|
||||
toolbar.getMenu().findItem(R.id.visit_website_item).setVisible(feed != null && feed.getLink() != null
|
||||
&& IntentUtils.isCallable(getContext(), new Intent(Intent.ACTION_VIEW, Uri.parse(feed.getLink()))));
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package de.danoeh.antennapod.fragment;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.Intent;
|
||||
import android.graphics.LightingColorFilter;
|
||||
@ -36,8 +35,6 @@ import com.joanzapata.iconify.widget.IconTextView;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.adapter.EpisodeItemListAdapter;
|
||||
import de.danoeh.antennapod.core.asynctask.FeedRemover;
|
||||
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
|
||||
import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
|
||||
import de.danoeh.antennapod.core.event.DownloadEvent;
|
||||
import de.danoeh.antennapod.core.event.DownloaderUpdate;
|
||||
@ -65,6 +62,7 @@ import de.danoeh.antennapod.core.util.ThemeUtils;
|
||||
import de.danoeh.antennapod.core.util.gui.MoreContentListFooterUtil;
|
||||
import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment;
|
||||
import de.danoeh.antennapod.dialog.FilterDialog;
|
||||
import de.danoeh.antennapod.dialog.RemoveFeedDialog;
|
||||
import de.danoeh.antennapod.dialog.RenameFeedDialog;
|
||||
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
|
||||
import de.danoeh.antennapod.menuhandler.FeedMenuHandler;
|
||||
@ -298,28 +296,8 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
|
||||
new RenameFeedDialog(getActivity(), feed).show();
|
||||
return true;
|
||||
case R.id.remove_item:
|
||||
final FeedRemover remover = new FeedRemover(
|
||||
getActivity(), feed) {
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
super.onPostExecute(result);
|
||||
((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null);
|
||||
}
|
||||
};
|
||||
int messageId = feed.isLocalFeed() ? R.string.feed_delete_confirmation_local_msg
|
||||
: R.string.feed_delete_confirmation_msg;
|
||||
ConfirmationDialog conDialog = new ConfirmationDialog(getActivity(),
|
||||
R.string.remove_feed_label,
|
||||
getString(messageId, feed.getTitle())) {
|
||||
|
||||
@Override
|
||||
public void onConfirmButtonPressed(
|
||||
DialogInterface dialog) {
|
||||
dialog.dismiss();
|
||||
remover.executeAsync();
|
||||
}
|
||||
};
|
||||
conDialog.createNewDialog().show();
|
||||
RemoveFeedDialog.show(getContext(), feed, () ->
|
||||
((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null));
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
@ -8,10 +8,8 @@ import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
import de.danoeh.antennapod.core.util.playback.Timeline;
|
||||
import de.danoeh.antennapod.view.ShownotesWebView;
|
||||
|
@ -17,7 +17,6 @@ import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.text.TextUtilsCompat;
|
||||
import androidx.core.util.ObjectsCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
@ -71,7 +70,6 @@ import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@ -120,6 +118,7 @@ public class ItemFragment extends Fragment {
|
||||
private View butAction2;
|
||||
private ItemActionButton actionButton1;
|
||||
private ItemActionButton actionButton2;
|
||||
private View noMediaLabel;
|
||||
|
||||
private Disposable disposable;
|
||||
private PlaybackController controller;
|
||||
@ -169,6 +168,7 @@ public class ItemFragment extends Fragment {
|
||||
butAction2Icon = layout.findViewById(R.id.butAction2Icon);
|
||||
butAction1Text = layout.findViewById(R.id.butAction1Text);
|
||||
butAction2Text = layout.findViewById(R.id.butAction2Text);
|
||||
noMediaLabel = layout.findViewById(R.id.noMediaLabel);
|
||||
|
||||
butAction1.setOnClickListener(v -> {
|
||||
if (actionButton1 instanceof StreamActionButton && !UserPreferences.isStreamOverDownload()
|
||||
@ -319,7 +319,9 @@ public class ItemFragment extends Fragment {
|
||||
if (media == null) {
|
||||
actionButton1 = new MarkAsPlayedActionButton(item);
|
||||
actionButton2 = new VisitWebsiteActionButton(item);
|
||||
noMediaLabel.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
noMediaLabel.setVisibility(View.GONE);
|
||||
if (media.getDuration() > 0) {
|
||||
txtvDuration.setText(Converter.getDurationStringLong(media.getDuration()));
|
||||
txtvDuration.setContentDescription(
|
||||
|
@ -26,19 +26,15 @@ import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.activity.PreferenceActivity;
|
||||
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.FeedListUpdateEvent;
|
||||
import de.danoeh.antennapod.core.event.QueueEvent;
|
||||
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
|
||||
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.DBWriter;
|
||||
import de.danoeh.antennapod.core.util.FeedItemUtil;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import de.danoeh.antennapod.dialog.RemoveFeedDialog;
|
||||
import de.danoeh.antennapod.dialog.SubscriptionsFilterDialog;
|
||||
import de.danoeh.antennapod.dialog.RenameFeedDialog;
|
||||
import io.reactivex.Observable;
|
||||
@ -192,41 +188,15 @@ public class NavDrawerFragment extends Fragment implements AdapterView.OnItemCli
|
||||
new RenameFeedDialog(getActivity(), feed).show();
|
||||
return true;
|
||||
case R.id.remove_item:
|
||||
final FeedRemover remover = new FeedRemover(getContext(), feed) {
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
super.onPostExecute(result);
|
||||
if (selectedNavListIndex == position) {
|
||||
if (getActivity() instanceof MainActivity) {
|
||||
((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null);
|
||||
} else {
|
||||
showMainActivity(EpisodesFragment.TAG);
|
||||
}
|
||||
RemoveFeedDialog.show(getContext(), feed, () -> {
|
||||
if (selectedNavListIndex == position) {
|
||||
if (getActivity() instanceof MainActivity) {
|
||||
((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null);
|
||||
} else {
|
||||
showMainActivity(EpisodesFragment.TAG);
|
||||
}
|
||||
}
|
||||
};
|
||||
int messageId = feed.isLocalFeed() ? R.string.feed_delete_confirmation_local_msg
|
||||
: R.string.feed_delete_confirmation_msg;
|
||||
ConfirmationDialog conDialog = new ConfirmationDialog(getContext(),
|
||||
R.string.remove_feed_label,
|
||||
getString(messageId, feed.getTitle())) {
|
||||
@Override
|
||||
public void onConfirmButtonPressed(DialogInterface dialog) {
|
||||
dialog.dismiss();
|
||||
long mediaId = PlaybackPreferences.getCurrentlyPlayingFeedMediaId();
|
||||
if (mediaId > 0 && FeedItemUtil.indexOfItemWithMediaId(feed.getItems(), mediaId) >= 0) {
|
||||
Log.d(TAG, "Currently playing episode is about to be deleted, skipping");
|
||||
remover.skipOnCompletion = true;
|
||||
int playerStatus = PlaybackPreferences.getCurrentPlayerStatus();
|
||||
if (playerStatus == PlaybackPreferences.PLAYER_STATUS_PLAYING) {
|
||||
IntentUtils.sendLocalBroadcast(getContext(),
|
||||
PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE);
|
||||
}
|
||||
}
|
||||
remover.executeAsync();
|
||||
}
|
||||
};
|
||||
conDialog.createNewDialog().show();
|
||||
});
|
||||
return true;
|
||||
default:
|
||||
return super.onContextItemSelected(item);
|
||||
|
@ -1,7 +1,6 @@
|
||||
package de.danoeh.antennapod.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuInflater;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
@ -90,7 +90,6 @@ public class OnlineSearchFragment extends Fragment {
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
// Inflate the layout for this fragment
|
||||
View root = inflater.inflate(R.layout.fragment_itunes_search, container, false);
|
||||
setupToolbar(root.findViewById(R.id.toolbar));
|
||||
root.findViewById(R.id.spinner_country).setVisibility(INVISIBLE);
|
||||
gridView = root.findViewById(R.id.gridView);
|
||||
adapter = new ItunesAdapter(getActivity(), new ArrayList<>());
|
||||
@ -110,6 +109,7 @@ public class OnlineSearchFragment extends Fragment {
|
||||
txtvEmpty = root.findViewById(android.R.id.empty);
|
||||
TextView txtvPoweredBy = root.findViewById(R.id.search_powered_by);
|
||||
txtvPoweredBy.setText(getString(R.string.search_powered_by, searchProvider.getName()));
|
||||
setupToolbar(root.findViewById(R.id.toolbar));
|
||||
return root;
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ import de.danoeh.antennapod.core.event.FeedItemEvent;
|
||||
import de.danoeh.antennapod.core.event.PlaybackHistoryEvent;
|
||||
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
|
||||
import de.danoeh.antennapod.core.event.PlayerStatusEvent;
|
||||
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
@ -180,6 +181,12 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI
|
||||
refreshToolbarState();
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) {
|
||||
loadItems();
|
||||
refreshToolbarState();
|
||||
}
|
||||
|
||||
private void onFragmentLoaded() {
|
||||
adapter.notifyDataSetChanged();
|
||||
refreshToolbarState();
|
||||
|
@ -46,6 +46,7 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView.
|
||||
private FeedDiscoverAdapter adapter;
|
||||
private GridView discoverGridLayout;
|
||||
private TextView errorTextView;
|
||||
private TextView poweredByTextView;
|
||||
private LinearLayout errorView;
|
||||
private Button errorRetry;
|
||||
|
||||
@ -63,6 +64,7 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView.
|
||||
errorTextView = root.findViewById(R.id.discover_error_txtV);
|
||||
errorRetry = root.findViewById(R.id.discover_error_retry_btn);
|
||||
errorRetry.setOnClickListener((listener) -> loadToplist());
|
||||
poweredByTextView = root.findViewById(R.id.discover_powered_by_itunes);
|
||||
|
||||
adapter = new FeedDiscoverAdapter((MainActivity) getActivity());
|
||||
discoverGridLayout.setAdapter(adapter);
|
||||
@ -110,6 +112,7 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView.
|
||||
discoverGridLayout.setVisibility(View.INVISIBLE);
|
||||
errorView.setVisibility(View.GONE);
|
||||
errorRetry.setVisibility(View.INVISIBLE);
|
||||
poweredByTextView.setVisibility(View.VISIBLE);
|
||||
|
||||
ItunesTopListLoader loader = new ItunesTopListLoader(getContext());
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences(ItunesTopListLoader.PREFS, MODE_PRIVATE);
|
||||
@ -122,6 +125,7 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView.
|
||||
progressBar.setVisibility(View.GONE);
|
||||
discoverGridLayout.setVisibility(View.INVISIBLE);
|
||||
errorRetry.setVisibility(View.INVISIBLE);
|
||||
poweredByTextView.setVisibility(View.INVISIBLE);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.ListFragment;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
|
@ -33,22 +33,18 @@ import java.util.concurrent.Callable;
|
||||
import de.danoeh.antennapod.R;
|
||||
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.FeedListUpdateEvent;
|
||||
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
|
||||
import de.danoeh.antennapod.core.feed.Feed;
|
||||
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
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.RemoveFeedDialog;
|
||||
import de.danoeh.antennapod.dialog.SubscriptionsFilterDialog;
|
||||
import de.danoeh.antennapod.dialog.FeedSortDialog;
|
||||
import de.danoeh.antennapod.dialog.RenameFeedDialog;
|
||||
@ -299,45 +295,13 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem
|
||||
new RenameFeedDialog(getActivity(), feed).show();
|
||||
return true;
|
||||
case R.id.remove_item:
|
||||
displayRemoveFeedDialog(feed);
|
||||
RemoveFeedDialog.show(getContext(), feed, null);
|
||||
return true;
|
||||
default:
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void displayRemoveFeedDialog(Feed feed) {
|
||||
final FeedRemover remover = new FeedRemover(getContext(), feed) {
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
super.onPostExecute(result);
|
||||
loadSubscriptions();
|
||||
}
|
||||
};
|
||||
|
||||
int messageId = feed.isLocalFeed() ? R.string.feed_delete_confirmation_local_msg
|
||||
: R.string.feed_delete_confirmation_msg;
|
||||
String message = getString(messageId, feed.getTitle());
|
||||
ConfirmationDialog dialog = new ConfirmationDialog(getContext(), R.string.remove_feed_label, message) {
|
||||
@Override
|
||||
public void onConfirmButtonPressed(DialogInterface clickedDialog) {
|
||||
clickedDialog.dismiss();
|
||||
long mediaId = PlaybackPreferences.getCurrentlyPlayingFeedMediaId();
|
||||
if (mediaId > 0 && FeedItemUtil.indexOfItemWithMediaId(feed.getItems(), mediaId) >= 0) {
|
||||
Log.d(TAG, "Currently playing episode is about to be deleted, skipping");
|
||||
remover.skipOnCompletion = true;
|
||||
int playerStatus = PlaybackPreferences.getCurrentPlayerStatus();
|
||||
if(playerStatus == PlaybackPreferences.PLAYER_STATUS_PLAYING) {
|
||||
IntentUtils.sendLocalBroadcast(getContext(), PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE);
|
||||
|
||||
}
|
||||
}
|
||||
remover.executeAsync();
|
||||
}
|
||||
};
|
||||
dialog.createNewDialog().show();
|
||||
}
|
||||
|
||||
private <T> void displayConfirmationDialog(@StringRes int title, @StringRes int message, Callable<? extends T> task) {
|
||||
ConfirmationDialog dialog = new ConfirmationDialog(getActivity(), title, message) {
|
||||
@Override
|
||||
|
@ -1,11 +1,14 @@
|
||||
package de.danoeh.antennapod.fragment.gpodnet;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
@ -15,6 +18,9 @@ import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.discovery.GpodnetPodcastSearcher;
|
||||
import de.danoeh.antennapod.fragment.OnlineSearchFragment;
|
||||
|
||||
/**
|
||||
* Main navigation hub for gpodder.net podcast directory
|
||||
@ -30,9 +36,7 @@ public class GpodnetMainFragment extends Fragment {
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
View root = inflater.inflate(R.layout.pager_fragment, container, false);
|
||||
Toolbar toolbar = root.findViewById(R.id.toolbar);
|
||||
toolbar.setTitle(R.string.gpodnet_main_label);
|
||||
toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack());
|
||||
setupToolbar(root.findViewById(R.id.toolbar));
|
||||
|
||||
ViewPager2 viewPager = root.findViewById(R.id.viewpager);
|
||||
GpodnetPagerAdapter pagerAdapter = new GpodnetPagerAdapter(this);
|
||||
@ -59,6 +63,33 @@ public class GpodnetMainFragment extends Fragment {
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupToolbar(Toolbar toolbar) {
|
||||
toolbar.setTitle(R.string.gpodnet_main_label);
|
||||
toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack());
|
||||
|
||||
toolbar.inflateMenu(R.menu.search);
|
||||
MenuItem searchItem = toolbar.getMenu().findItem(R.id.action_search);
|
||||
final SearchView sv = (SearchView) searchItem.getActionView();
|
||||
sv.setQueryHint(getString(R.string.gpodnet_search_hint));
|
||||
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
Activity activity = getActivity();
|
||||
if (activity != null) {
|
||||
searchItem.collapseActionView();
|
||||
((MainActivity) activity).loadChildFragment(
|
||||
OnlineSearchFragment.newInstance(GpodnetPodcastSearcher.class, query));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String s) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static class GpodnetPagerAdapter extends FragmentStateAdapter {
|
||||
|
||||
GpodnetPagerAdapter(@NonNull Fragment fragment) {
|
||||
@ -68,20 +99,14 @@ public class GpodnetMainFragment extends Fragment {
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment createFragment(int position) {
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putBoolean(PodcastListFragment.ARGUMENT_HIDE_TOOLBAR, true);
|
||||
switch (position) {
|
||||
case POS_TAGS:
|
||||
return new TagListFragment();
|
||||
case POS_TOPLIST:
|
||||
PodcastListFragment topListFragment = new PodcastTopListFragment();
|
||||
topListFragment.setArguments(arguments);
|
||||
return topListFragment;
|
||||
return new PodcastTopListFragment();
|
||||
default:
|
||||
case POS_SUGGESTIONS:
|
||||
PodcastListFragment suggestionsFragment = new SuggestionListFragment();
|
||||
suggestionsFragment.setArguments(arguments);
|
||||
return suggestionsFragment;
|
||||
return new SuggestionListFragment();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,12 +4,9 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
@ -33,7 +30,6 @@ import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast;
|
||||
* Displays a list of GPodnetPodcast-Objects in a GridView
|
||||
*/
|
||||
public abstract class PodcastListFragment extends Fragment {
|
||||
public static final String ARGUMENT_HIDE_TOOLBAR = "hideToolbar";
|
||||
private static final String TAG = "PodcastListFragment";
|
||||
|
||||
private GridView gridView;
|
||||
@ -44,8 +40,6 @@ public abstract class PodcastListFragment extends Fragment {
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View root = inflater.inflate(R.layout.gpodnet_podcast_list, container, false);
|
||||
setupToolbar(root.findViewById(R.id.toolbar));
|
||||
|
||||
gridView = root.findViewById(R.id.gridView);
|
||||
progressBar = root.findViewById(R.id.progressBar);
|
||||
txtvError = root.findViewById(R.id.txtvError);
|
||||
@ -59,37 +53,6 @@ public abstract class PodcastListFragment extends Fragment {
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupToolbar(Toolbar toolbar) {
|
||||
if (getArguments() != null && getArguments().getBoolean(ARGUMENT_HIDE_TOOLBAR, false)) {
|
||||
toolbar.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
toolbar.setTitle(R.string.gpodnet_main_label);
|
||||
toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack());
|
||||
toolbar.inflateMenu(R.menu.gpodder_podcasts);
|
||||
|
||||
MenuItem searchItem = toolbar.getMenu().findItem(R.id.action_search);
|
||||
final SearchView sv = (SearchView) searchItem.getActionView();
|
||||
sv.setQueryHint(getString(R.string.gpodnet_search_hint));
|
||||
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String s) {
|
||||
sv.clearFocus();
|
||||
MainActivity activity = (MainActivity)getActivity();
|
||||
if (activity != null) {
|
||||
activity.loadChildFragment(SearchListFragment.newInstance(s));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String s) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onPodcastSelected(GpodnetPodcast selection) {
|
||||
Log.d(TAG, "Selected podcast: " + selection.toString());
|
||||
Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class);
|
||||
|
@ -1,80 +0,0 @@
|
||||
package de.danoeh.antennapod.fragment.gpodnet;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.core.view.MenuItemCompat;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Performs a search on the gpodder.net directory and displays the results.
|
||||
*/
|
||||
public class SearchListFragment extends PodcastListFragment {
|
||||
private static final String ARG_QUERY = "query";
|
||||
|
||||
private String query;
|
||||
|
||||
public static SearchListFragment newInstance(String query) {
|
||||
SearchListFragment fragment = new SearchListFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_QUERY, query);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (getArguments() != null && getArguments().containsKey(ARG_QUERY)) {
|
||||
this.query = getArguments().getString(ARG_QUERY);
|
||||
} else {
|
||||
this.query = "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
// parent already inflated menu
|
||||
MenuItem searchItem = menu.findItem(R.id.action_search);
|
||||
final SearchView sv = (SearchView) searchItem.getActionView();
|
||||
sv.setQueryHint(getString(R.string.gpodnet_search_hint));
|
||||
sv.setQuery(query, false);
|
||||
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String s) {
|
||||
sv.clearFocus();
|
||||
changeQuery(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String s) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<GpodnetPodcast> loadPodcastData(GpodnetService service) throws GpodnetServiceException {
|
||||
return service.searchPodcasts(query, 0);
|
||||
}
|
||||
|
||||
private void changeQuery(String query) {
|
||||
Validate.notNull(query);
|
||||
|
||||
this.query = query;
|
||||
loadData();
|
||||
}
|
||||
}
|
@ -1,19 +1,11 @@
|
||||
package de.danoeh.antennapod.fragment.gpodnet;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import androidx.fragment.app.ListFragment;
|
||||
import androidx.core.view.MenuItemCompat;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import androidx.fragment.app.ListFragment;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.adapter.gpodnet.TagListAdapter;
|
||||
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
||||
@ -27,37 +19,6 @@ import java.util.List;
|
||||
public class TagListFragment extends ListFragment {
|
||||
private static final int COUNT = 50;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.gpodder_podcasts, menu);
|
||||
MenuItem searchItem = menu.findItem(R.id.action_search);
|
||||
final SearchView sv = (SearchView) searchItem.getActionView();
|
||||
sv.setQueryHint(getString(R.string.gpodnet_search_hint));
|
||||
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String s) {
|
||||
Activity activity = getActivity();
|
||||
if (activity != null) {
|
||||
sv.clearFocus();
|
||||
((MainActivity) activity).loadChildFragment(SearchListFragment.newInstance(s));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String s) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
@ -0,0 +1,302 @@
|
||||
package de.danoeh.antennapod.fragment.preferences;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ViewFlipper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
|
||||
import de.danoeh.antennapod.core.sync.SyncService;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
|
||||
import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetDevice;
|
||||
import de.danoeh.antennapod.core.util.FileNameGenerator;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Guides the user through the authentication process.
|
||||
*/
|
||||
public class GpodderAuthenticationFragment extends DialogFragment {
|
||||
public static final String TAG = "GpodnetAuthActivity";
|
||||
|
||||
private ViewFlipper viewFlipper;
|
||||
|
||||
private static final int STEP_DEFAULT = -1;
|
||||
private static final int STEP_HOSTNAME = 0;
|
||||
private static final int STEP_LOGIN = 1;
|
||||
private static final int STEP_DEVICE = 2;
|
||||
private static final int STEP_FINISH = 3;
|
||||
|
||||
private int currentStep = -1;
|
||||
|
||||
private GpodnetService service;
|
||||
private volatile String username;
|
||||
private volatile String password;
|
||||
private volatile GpodnetDevice selectedDevice;
|
||||
private List<GpodnetDevice> devices;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(getContext());
|
||||
dialog.setTitle(GpodnetService.DEFAULT_BASE_HOST);
|
||||
dialog.setNegativeButton(R.string.cancel_label, null);
|
||||
dialog.setCancelable(false);
|
||||
this.setCancelable(false);
|
||||
|
||||
View root = View.inflate(getContext(), R.layout.gpodnetauth_dialog, null);
|
||||
viewFlipper = root.findViewById(R.id.viewflipper);
|
||||
advance();
|
||||
dialog.setView(root);
|
||||
|
||||
return dialog.create();
|
||||
}
|
||||
|
||||
private void setupHostView(View view) {
|
||||
final Button selectHost = view.findViewById(R.id.chooseHostButton);
|
||||
final RadioGroup serverRadioGroup = view.findViewById(R.id.serverRadioGroup);
|
||||
final EditText serverUrlText = view.findViewById(R.id.serverUrlText);
|
||||
if (!GpodnetService.DEFAULT_BASE_HOST.equals(GpodnetPreferences.getHostname())) {
|
||||
serverUrlText.setText(GpodnetPreferences.getHostname());
|
||||
}
|
||||
final TextInputLayout serverUrlTextInput = view.findViewById(R.id.serverUrlTextInput);
|
||||
serverRadioGroup.setOnCheckedChangeListener((group, checkedId) -> {
|
||||
serverUrlTextInput.setVisibility(checkedId == R.id.customServerRadio ? View.VISIBLE : View.GONE);
|
||||
});
|
||||
selectHost.setOnClickListener(v -> {
|
||||
if (serverRadioGroup.getCheckedRadioButtonId() == R.id.customServerRadio) {
|
||||
GpodnetPreferences.setHostname(serverUrlText.getText().toString());
|
||||
} else {
|
||||
GpodnetPreferences.setHostname(GpodnetService.DEFAULT_BASE_HOST);
|
||||
}
|
||||
service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetPreferences.getHostname());
|
||||
getDialog().setTitle(GpodnetPreferences.getHostname());
|
||||
advance();
|
||||
});
|
||||
}
|
||||
|
||||
private void setupLoginView(View view) {
|
||||
final EditText username = view.findViewById(R.id.etxtUsername);
|
||||
final EditText password = view.findViewById(R.id.etxtPassword);
|
||||
final Button login = view.findViewById(R.id.butLogin);
|
||||
final TextView txtvError = view.findViewById(R.id.credentialsError);
|
||||
final ProgressBar progressBar = view.findViewById(R.id.progBarLogin);
|
||||
final TextView createAccount = view.findViewById(R.id.createAccountButton);
|
||||
|
||||
createAccount.setPaintFlags(createAccount.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||
createAccount.setOnClickListener(v -> IntentUtils.openInBrowser(getContext(), "https://gpodder.net/register/"));
|
||||
|
||||
password.setOnEditorActionListener((v, actionID, event) ->
|
||||
actionID == EditorInfo.IME_ACTION_GO && login.performClick());
|
||||
|
||||
login.setOnClickListener(v -> {
|
||||
final String usernameStr = username.getText().toString();
|
||||
final String passwordStr = password.getText().toString();
|
||||
|
||||
if (usernameHasUnwantedChars(usernameStr)) {
|
||||
txtvError.setText(R.string.gpodnetsync_username_characters_error);
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
|
||||
login.setEnabled(false);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
txtvError.setVisibility(View.GONE);
|
||||
InputMethodManager inputManager = (InputMethodManager) getContext()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
inputManager.hideSoftInputFromWindow(login.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
|
||||
Completable.fromAction(() -> {
|
||||
service.authenticate(usernameStr, passwordStr);
|
||||
devices = service.getDevices();
|
||||
GpodderAuthenticationFragment.this.username = usernameStr;
|
||||
GpodderAuthenticationFragment.this.password = passwordStr;
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(() -> {
|
||||
login.setEnabled(true);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
advance();
|
||||
}, error -> {
|
||||
login.setEnabled(true);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
txtvError.setText(error.getCause().getMessage());
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void setupDeviceView(View view) {
|
||||
final EditText deviceName = view.findViewById(R.id.deviceName);
|
||||
final LinearLayout devicesContainer = view.findViewById(R.id.devicesContainer);
|
||||
deviceName.setText(generateDeviceName());
|
||||
|
||||
MaterialButton createDeviceButton = view.findViewById(R.id.createDeviceButton);
|
||||
createDeviceButton.setOnClickListener(v -> createDevice(view));
|
||||
|
||||
for (GpodnetDevice device : devices) {
|
||||
View row = View.inflate(getContext(), R.layout.gpodnetauth_device_row, null);
|
||||
Button selectDeviceButton = row.findViewById(R.id.selectDeviceButton);
|
||||
selectDeviceButton.setOnClickListener(v -> {
|
||||
selectedDevice = device;
|
||||
advance();
|
||||
});
|
||||
selectDeviceButton.setText(device.getCaption());
|
||||
devicesContainer.addView(row);
|
||||
}
|
||||
}
|
||||
|
||||
private void createDevice(View view) {
|
||||
final EditText deviceName = view.findViewById(R.id.deviceName);
|
||||
final TextView txtvError = view.findViewById(R.id.deviceSelectError);
|
||||
final ProgressBar progBarCreateDevice = view.findViewById(R.id.progbarCreateDevice);
|
||||
|
||||
String deviceNameStr = deviceName.getText().toString();
|
||||
if (isDeviceInList(deviceNameStr)) {
|
||||
return;
|
||||
}
|
||||
progBarCreateDevice.setVisibility(View.VISIBLE);
|
||||
txtvError.setVisibility(View.GONE);
|
||||
deviceName.setEnabled(false);
|
||||
|
||||
Observable.fromCallable(() -> {
|
||||
String deviceId = generateDeviceId(deviceNameStr);
|
||||
service.configureDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE);
|
||||
return new GpodnetDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0);
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(device -> {
|
||||
progBarCreateDevice.setVisibility(View.GONE);
|
||||
selectedDevice = device;
|
||||
advance();
|
||||
}, error -> {
|
||||
deviceName.setEnabled(true);
|
||||
progBarCreateDevice.setVisibility(View.GONE);
|
||||
txtvError.setText(error.getMessage());
|
||||
txtvError.setVisibility(View.VISIBLE);
|
||||
});
|
||||
}
|
||||
|
||||
private String generateDeviceName() {
|
||||
String baseName = getString(R.string.gpodnetauth_device_name_default, Build.MODEL);
|
||||
String name = baseName;
|
||||
int num = 1;
|
||||
while (isDeviceInList(name)) {
|
||||
name = baseName + " (" + num + ")";
|
||||
num++;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private String generateDeviceId(String name) {
|
||||
// devices names must be of a certain form:
|
||||
// https://gpoddernet.readthedocs.org/en/latest/api/reference/general.html#devices
|
||||
return FileNameGenerator.generateFileName(name).replaceAll("\\W", "_").toLowerCase(Locale.US);
|
||||
}
|
||||
|
||||
private boolean isDeviceInList(String name) {
|
||||
if (devices == null) {
|
||||
return false;
|
||||
}
|
||||
String id = generateDeviceId(name);
|
||||
for (GpodnetDevice device : devices) {
|
||||
if (device.getId().equals(id) || device.getCaption().equals(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private GpodnetDevice findDevice(String id) {
|
||||
if (devices == null) {
|
||||
return null;
|
||||
}
|
||||
for (GpodnetDevice device : devices) {
|
||||
if (device.getId().equals(id)) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void setupFinishView(View view) {
|
||||
final Button sync = view.findViewById(R.id.butSyncNow);
|
||||
|
||||
sync.setOnClickListener(v -> {
|
||||
dismiss();
|
||||
SyncService.sync(getContext());
|
||||
});
|
||||
}
|
||||
|
||||
private void writeLoginCredentials() {
|
||||
GpodnetPreferences.setUsername(username);
|
||||
GpodnetPreferences.setPassword(password);
|
||||
GpodnetPreferences.setDeviceID(selectedDevice.getId());
|
||||
}
|
||||
|
||||
private void advance() {
|
||||
if (currentStep < STEP_FINISH) {
|
||||
|
||||
View view = viewFlipper.getChildAt(currentStep + 1);
|
||||
if (currentStep == STEP_DEFAULT) {
|
||||
setupHostView(view);
|
||||
} else if (currentStep == STEP_HOSTNAME) {
|
||||
setupLoginView(view);
|
||||
} else if (currentStep == STEP_LOGIN) {
|
||||
if (username == null || password == null) {
|
||||
throw new IllegalStateException("Username and password must not be null here");
|
||||
} else {
|
||||
setupDeviceView(view);
|
||||
}
|
||||
} else if (currentStep == STEP_DEVICE) {
|
||||
if (selectedDevice == null) {
|
||||
throw new IllegalStateException("Device must not be null here");
|
||||
} else {
|
||||
writeLoginCredentials();
|
||||
setupFinishView(view);
|
||||
}
|
||||
}
|
||||
if (currentStep != STEP_DEFAULT) {
|
||||
viewFlipper.showNext();
|
||||
}
|
||||
currentStep++;
|
||||
} else {
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean usernameHasUnwantedChars(String username) {
|
||||
Pattern special = Pattern.compile("[!@#$%&*()+=|<>?{}\\[\\]~]");
|
||||
Matcher containsUnwantedChars = special.matcher(username);
|
||||
return containsUnwantedChars.find();
|
||||
}
|
||||
}
|
@ -1,15 +1,12 @@
|
||||
package de.danoeh.antennapod.fragment.preferences;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import androidx.core.text.HtmlCompat;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
import android.text.Spanned;
|
||||
import android.text.format.DateUtils;
|
||||
import android.widget.Toast;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.PreferenceActivity;
|
||||
@ -17,19 +14,16 @@ import de.danoeh.antennapod.core.event.SyncServiceEvent;
|
||||
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
||||
import de.danoeh.antennapod.core.sync.SyncService;
|
||||
import de.danoeh.antennapod.dialog.AuthenticationDialog;
|
||||
import de.danoeh.antennapod.dialog.GpodnetSetHostnameDialog;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
|
||||
public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
|
||||
private static final String PREF_GPODNET_LOGIN = "pref_gpodnet_authenticate";
|
||||
private static final String PREF_GPODNET_SETLOGIN_INFORMATION = "pref_gpodnet_setlogin_information";
|
||||
private static final String PREF_GPODNET_SYNC = "pref_gpodnet_sync";
|
||||
private static final String PREF_GPODNET_FORCE_FULL_SYNC = "pref_gpodnet_force_full_sync";
|
||||
private static final String PREF_GPODNET_LOGOUT = "pref_gpodnet_logout";
|
||||
private static final String PREF_GPODNET_HOSTNAME = "pref_gpodnet_hostname";
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
@ -54,6 +48,7 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
|
||||
public void syncStatusChanged(SyncServiceEvent event) {
|
||||
updateGpodnetPreferenceScreen();
|
||||
if (!GpodnetPreferences.loggedIn()) {
|
||||
return;
|
||||
}
|
||||
@ -69,6 +64,10 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
|
||||
private void setupGpodderScreen() {
|
||||
final Activity activity = getActivity();
|
||||
|
||||
findPreference(PREF_GPODNET_LOGIN).setOnPreferenceClickListener(preference -> {
|
||||
new GpodderAuthenticationFragment().show(getChildFragmentManager(), GpodderAuthenticationFragment.TAG);
|
||||
return true;
|
||||
});
|
||||
findPreference(PREF_GPODNET_SETLOGIN_INFORMATION)
|
||||
.setOnPreferenceClickListener(preference -> {
|
||||
AuthenticationDialog dialog = new AuthenticationDialog(activity,
|
||||
@ -97,11 +96,6 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
|
||||
updateGpodnetPreferenceScreen();
|
||||
return true;
|
||||
});
|
||||
findPreference(PREF_GPODNET_HOSTNAME).setOnPreferenceClickListener(preference -> {
|
||||
GpodnetSetHostnameDialog.createDialog(activity).setOnDismissListener(
|
||||
dialog -> updateGpodnetPreferenceScreen());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void updateGpodnetPreferenceScreen() {
|
||||
@ -122,7 +116,6 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
|
||||
} else {
|
||||
findPreference(PREF_GPODNET_LOGOUT).setSummary(null);
|
||||
}
|
||||
findPreference(PREF_GPODNET_HOSTNAME).setSummary(GpodnetPreferences.getHostname());
|
||||
}
|
||||
|
||||
private void updateLastGpodnetSyncReport(boolean successful, long lastTime) {
|
||||
|
@ -1,8 +1,11 @@
|
||||
package de.danoeh.antennapod.fragment.preferences;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import com.bytehamster.lib.preferencesearch.SearchConfiguration;
|
||||
import com.bytehamster.lib.preferencesearch.SearchPreference;
|
||||
@ -20,7 +23,7 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat {
|
||||
private static final String PREF_SCREEN_NETWORK = "prefScreenNetwork";
|
||||
private static final String PREF_SCREEN_GPODDER = "prefScreenGpodder";
|
||||
private static final String PREF_SCREEN_STORAGE = "prefScreenStorage";
|
||||
private static final String PREF_FAQ = "prefFaq";
|
||||
private static final String PREF_DOCUMENTATION = "prefDocumentation";
|
||||
private static final String PREF_VIEW_FORUM = "prefViewForum";
|
||||
private static final String PREF_SEND_BUG_REPORT = "prefSendBugReport";
|
||||
private static final String PREF_CATEGORY_PROJECT = "project";
|
||||
@ -35,10 +38,18 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat {
|
||||
setupSearch();
|
||||
|
||||
// If you are writing a spin-off, please update the details on screens like "About" and "Report bug"
|
||||
// and afterwards remove the following lines.
|
||||
// and afterwards remove the following lines. Please keep in mind that AntennaPod is licensed under the GPL.
|
||||
// This means that your application needs to be open-source under the GPL, too.
|
||||
// It must also include a prominent copyright notice.
|
||||
String packageName = getContext().getPackageName();
|
||||
if (!"de.danoeh.antennapod".equals(packageName) && !"de.danoeh.antennapod.debug".equals(packageName)) {
|
||||
findPreference(PREF_CATEGORY_PROJECT).setVisible(false);
|
||||
Preference copyrightNotice = new Preference(getContext());
|
||||
copyrightNotice.setSummary("This application is based on AntennaPod."
|
||||
+ " The AntennaPod team does NOT provide support for this unofficial version."
|
||||
+ " If you can read this message, the developers of this modification"
|
||||
+ " violate the GNU General Public License (GPL).");
|
||||
findPreference(PREF_CATEGORY_PROJECT).getParent().addPreference(copyrightNotice);
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,10 +81,16 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat {
|
||||
return true;
|
||||
});
|
||||
findPreference(PREF_NOTIFICATION).setOnPreferenceClickListener(preference -> {
|
||||
((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_notifications);
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getActivity().getPackageName());
|
||||
startActivity(intent);
|
||||
} else {
|
||||
((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_notifications);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
findPreference(PREF_ABOUT).setOnPreferenceClickListener(
|
||||
preference -> {
|
||||
getParentFragmentManager().beginTransaction().replace(R.id.content, new AboutFragment())
|
||||
@ -88,8 +105,8 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat {
|
||||
return true;
|
||||
}
|
||||
);
|
||||
findPreference(PREF_FAQ).setOnPreferenceClickListener(preference -> {
|
||||
IntentUtils.openInBrowser(getContext(), "https://antennapod.org/faq.html");
|
||||
findPreference(PREF_DOCUMENTATION).setOnPreferenceClickListener(preference -> {
|
||||
IntentUtils.openInBrowser(getContext(), "https://antennapod.org/documentation/");
|
||||
return true;
|
||||
});
|
||||
findPreference(PREF_VIEW_FORUM).setOnPreferenceClickListener(preference -> {
|
||||
|
@ -15,7 +15,6 @@ import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class NetworkPreferencesFragment extends PreferenceFragmentCompat {
|
||||
@ -97,8 +96,7 @@ public class NetworkPreferencesFragment extends PreferenceFragmentCompat {
|
||||
private void setParallelDownloadsText(int downloads) {
|
||||
final Resources res = getActivity().getResources();
|
||||
|
||||
String s = String.format(Locale.getDefault(), "%d%s",
|
||||
downloads, res.getString(R.string.parallel_downloads_suffix));
|
||||
String s = res.getString(R.string.parallel_downloads, downloads);
|
||||
findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS).setSummary(s);
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ public class AboutFragment extends PreferenceFragmentCompat {
|
||||
return true;
|
||||
});
|
||||
findPreference("about_privacy_policy").setOnPreferenceClickListener((preference) -> {
|
||||
IntentUtils.openInBrowser(getContext(), "https://antennapod.org/privacy.html");
|
||||
IntentUtils.openInBrowser(getContext(), "https://antennapod.org/privacy/");
|
||||
return true;
|
||||
});
|
||||
findPreference("about_licenses").setOnPreferenceClickListener((preference) -> {
|
||||
|
@ -6,8 +6,6 @@ import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.ListFragment;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.PreferenceActivity;
|
||||
import de.danoeh.antennapod.adapter.SimpleIconListAdapter;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.SingleOnSubscribe;
|
||||
|
@ -6,8 +6,6 @@ import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.ListFragment;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.PreferenceActivity;
|
||||
import de.danoeh.antennapod.adapter.SimpleIconListAdapter;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.SingleOnSubscribe;
|
||||
|
@ -6,8 +6,6 @@ import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.ListFragment;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.PreferenceActivity;
|
||||
import de.danoeh.antennapod.adapter.SimpleIconListAdapter;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.SingleOnSubscribe;
|
||||
|
@ -48,65 +48,43 @@ public class FeedItemMenuHandler {
|
||||
if (menu == null || selectedItem == null) {
|
||||
return false;
|
||||
}
|
||||
boolean hasMedia = selectedItem.getMedia() != null;
|
||||
boolean isPlaying = hasMedia && selectedItem.getState() == FeedItem.State.PLAYING;
|
||||
|
||||
if (!isPlaying) {
|
||||
setItemVisibility(menu, R.id.skip_episode_item, false);
|
||||
}
|
||||
boolean isInQueue = selectedItem.isTagged(FeedItem.TAG_QUEUE);
|
||||
if (!isInQueue) {
|
||||
setItemVisibility(menu, R.id.remove_from_queue_item, false);
|
||||
}
|
||||
if (!(!isInQueue && selectedItem.getMedia() != null)) {
|
||||
setItemVisibility(menu, R.id.add_to_queue_item, false);
|
||||
}
|
||||
if (!ShareUtils.hasLinkToShare(selectedItem)) {
|
||||
setItemVisibility(menu, R.id.visit_website_item, false);
|
||||
}
|
||||
|
||||
boolean fileDownloaded = hasMedia && selectedItem.getMedia().fileExists();
|
||||
final boolean hasMedia = selectedItem.getMedia() != null;
|
||||
final boolean isPlaying = hasMedia && selectedItem.getState() == FeedItem.State.PLAYING;
|
||||
final boolean isInQueue = selectedItem.isTagged(FeedItem.TAG_QUEUE);
|
||||
final boolean fileDownloaded = hasMedia && selectedItem.getMedia().fileExists();
|
||||
final boolean isFavorite = selectedItem.isTagged(FeedItem.TAG_FAVORITE);
|
||||
|
||||
setItemVisibility(menu, R.id.skip_episode_item, isPlaying);
|
||||
setItemVisibility(menu, R.id.remove_from_queue_item, isInQueue);
|
||||
setItemVisibility(menu, R.id.add_to_queue_item, !isInQueue && selectedItem.getMedia() != null);
|
||||
setItemVisibility(menu, R.id.visit_website_item, !selectedItem.getFeed().isLocalFeed()
|
||||
&& ShareUtils.hasLinkToShare(selectedItem));
|
||||
setItemVisibility(menu, R.id.share_item, !selectedItem.getFeed().isLocalFeed());
|
||||
setItemVisibility(menu, R.id.remove_new_flag_item, selectedItem.isNew());
|
||||
if (selectedItem.isPlayed()) {
|
||||
setItemVisibility(menu, R.id.mark_read_item, false);
|
||||
} else {
|
||||
setItemVisibility(menu, R.id.mark_unread_item, false);
|
||||
}
|
||||
|
||||
if (selectedItem.getMedia() == null || selectedItem.getMedia().getPosition() == 0) {
|
||||
setItemVisibility(menu, R.id.reset_position, false);
|
||||
}
|
||||
setItemVisibility(menu, R.id.mark_read_item, !selectedItem.isPlayed());
|
||||
setItemVisibility(menu, R.id.mark_unread_item, selectedItem.isPlayed());
|
||||
setItemVisibility(menu, R.id.reset_position, hasMedia && selectedItem.getMedia().getPosition() != 0);
|
||||
|
||||
if (!UserPreferences.isEnableAutodownload() || fileDownloaded || selectedItem.getFeed().isLocalFeed()) {
|
||||
setItemVisibility(menu, R.id.activate_auto_download, false);
|
||||
setItemVisibility(menu, R.id.deactivate_auto_download, false);
|
||||
} else if (selectedItem.getAutoDownload()) {
|
||||
setItemVisibility(menu, R.id.activate_auto_download, false);
|
||||
} else {
|
||||
setItemVisibility(menu, R.id.deactivate_auto_download, false);
|
||||
setItemVisibility(menu, R.id.activate_auto_download, !selectedItem.getAutoDownload());
|
||||
setItemVisibility(menu, R.id.deactivate_auto_download, selectedItem.getAutoDownload());
|
||||
}
|
||||
|
||||
// Display proper strings when item has no media
|
||||
if (!hasMedia && !selectedItem.isPlayed()) {
|
||||
if (hasMedia) {
|
||||
setItemTitle(menu, R.id.mark_read_item, R.string.mark_read_label);
|
||||
setItemTitle(menu, R.id.mark_unread_item, R.string.mark_unread_label);
|
||||
} else {
|
||||
setItemTitle(menu, R.id.mark_read_item, R.string.mark_read_no_media_label);
|
||||
}
|
||||
|
||||
if (!hasMedia && selectedItem.isPlayed()) {
|
||||
setItemTitle(menu, R.id.mark_unread_item, R.string.mark_unread_label_no_media);
|
||||
}
|
||||
|
||||
boolean isFavorite = selectedItem.isTagged(FeedItem.TAG_FAVORITE);
|
||||
setItemVisibility(menu, R.id.add_to_favorites_item, !isFavorite);
|
||||
setItemVisibility(menu, R.id.remove_from_favorites_item, isFavorite);
|
||||
|
||||
setItemVisibility(menu, R.id.remove_item, fileDownloaded);
|
||||
|
||||
if (selectedItem.getFeed().isLocalFeed()) {
|
||||
setItemVisibility(menu, R.id.visit_website_item, false);
|
||||
setItemVisibility(menu, R.id.share_item, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -134,7 +112,7 @@ public class FeedItemMenuHandler {
|
||||
* @param id The id of the string that is going to be replaced.
|
||||
* @param noMedia The id of the new String that is going to be used.
|
||||
* */
|
||||
public static void setItemTitle(Menu menu, int id, int noMedia){
|
||||
public static void setItemTitle(Menu menu, int id, int noMedia) {
|
||||
MenuItem item = menu.findItem(id);
|
||||
if (item != null) {
|
||||
item.setTitle(noMedia);
|
||||
|
@ -4,7 +4,6 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -11,7 +11,8 @@ import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.util.ThemeUtils;
|
||||
|
||||
public class CircularProgressBar extends View {
|
||||
private static final float EPSILON = 0.005f;
|
||||
public static final float MINIMUM_PERCENTAGE = 0.005f;
|
||||
public static final float MAXIMUM_PERCENTAGE = 1 - MINIMUM_PERCENTAGE;
|
||||
|
||||
private final Paint paintBackground = new Paint();
|
||||
private final Paint paintProgress = new Paint();
|
||||
@ -74,11 +75,11 @@ public class CircularProgressBar extends View {
|
||||
bounds.set(padding, padding, getWidth() - padding, getHeight() - padding);
|
||||
canvas.drawArc(bounds, 0, 360, false, paintBackground);
|
||||
|
||||
if (percentage > EPSILON && 1 - percentage > EPSILON) {
|
||||
if (MINIMUM_PERCENTAGE <= percentage && percentage <= MAXIMUM_PERCENTAGE) {
|
||||
canvas.drawArc(bounds, -90, percentage * 360, false, paintProgress);
|
||||
}
|
||||
|
||||
if (Math.abs(percentage - targetPercentage) > EPSILON) {
|
||||
if (Math.abs(percentage - targetPercentage) > MINIMUM_PERCENTAGE) {
|
||||
float speed = 0.02f;
|
||||
if (Math.abs(targetPercentage - percentage) < 0.1 && targetPercentage > percentage) {
|
||||
speed = 0.006f;
|
||||
|
@ -1,124 +0,0 @@
|
||||
package de.danoeh.antennapod.view;
|
||||
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.text.TextUtilsCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class PagerIndicatorView extends View {
|
||||
private final Paint paint = new Paint();
|
||||
private float position = 0;
|
||||
private int numPages = 0;
|
||||
private int disabledPage = -1;
|
||||
private int circleColor = 0;
|
||||
private int circleColorHighlight = -1;
|
||||
private boolean isLocaleRtl = false;
|
||||
|
||||
public PagerIndicatorView(Context context) {
|
||||
super(context);
|
||||
setup();
|
||||
}
|
||||
|
||||
public PagerIndicatorView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setup();
|
||||
}
|
||||
|
||||
public PagerIndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
setup();
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
isLocaleRtl = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault())
|
||||
== ViewCompat.LAYOUT_DIRECTION_RTL;
|
||||
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
|
||||
int[] colorAttrs = new int[]{android.R.attr.textColorSecondary};
|
||||
TypedArray a = getContext().obtainStyledAttributes(colorAttrs);
|
||||
circleColorHighlight = a.getColor(0, 0xffffffff);
|
||||
circleColor = (Integer) new ArgbEvaluator().evaluate(0.8f, 0x00ffffff, circleColorHighlight);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Visual and logical position distinction only happens in RTL locales (e.g. Persian)
|
||||
* where pages positions are flipped thus it does nothing in LTR locales (e.g. English)
|
||||
*/
|
||||
private float logicalPositionToVisual(float position) {
|
||||
return isLocaleRtl ? numPages - 1 - position : position;
|
||||
}
|
||||
|
||||
public void setViewPager(ViewPager2 pager) {
|
||||
numPages = pager.getAdapter().getItemCount();
|
||||
pager.getAdapter().registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
numPages = pager.getAdapter().getItemCount();
|
||||
invalidate();
|
||||
}
|
||||
});
|
||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
PagerIndicatorView.this.position = logicalPositionToVisual(
|
||||
position + positionOffset);
|
||||
invalidate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setDisabledPage(int disabledPage) {
|
||||
this.disabledPage = (int) logicalPositionToVisual(disabledPage);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
for (int i = 0; i < numPages; i++) {
|
||||
if ((int) Math.floor(position) == i) {
|
||||
// This is the current dot
|
||||
drawCircle(canvas, i, (float) (1 - (position - Math.floor(position))));
|
||||
} else if ((int) Math.ceil(position) == i) {
|
||||
// This is the next dot
|
||||
drawCircle(canvas, i, (float) (position - Math.floor(position)));
|
||||
} else {
|
||||
drawCircle(canvas, i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void drawCircle(Canvas canvas, int position, float frac) {
|
||||
float availableHeight = canvas.getHeight() - getPaddingTop() - getPaddingBottom();
|
||||
float circleRadiusSmall = availableHeight * 0.26f;
|
||||
float circleRadiusBig = availableHeight * 0.35f;
|
||||
float circleRadiusDelta = (circleRadiusBig - circleRadiusSmall);
|
||||
float start = 0.5f * (canvas.getWidth() - numPages * 1.5f * availableHeight);
|
||||
paint.setStrokeWidth(availableHeight * 0.3f);
|
||||
|
||||
if (position == disabledPage) {
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
} else {
|
||||
paint.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||
}
|
||||
|
||||
paint.setColor((Integer) new ArgbEvaluator().evaluate(frac, circleColor, circleColorHighlight));
|
||||
canvas.drawCircle(start + (position * 1.5f + 0.75f) * availableHeight, 0.5f * availableHeight + getPaddingTop(),
|
||||
circleRadiusSmall + frac * circleRadiusDelta, paint);
|
||||
}
|
||||
}
|
31
app/src/main/play/listings/ca/full-description.txt
Normal file
31
app/src/main/play/listings/ca/full-description.txt
Normal file
@ -0,0 +1,31 @@
|
||||
AntennaPod és un reproductor i organitzador de podcasts que et dona accés instantani a milions de podcasts tant de pagament com gratuïts, des de podcasters independents fins a grans publicacions com la BBC, NPR i CNN. Afegeix, importa i exporta els seus feeds sense problemes usant la base de dates de podcasts de iTunes, arxius OPML o simples URLs RSS.
|
||||
Descarrega, transmet o posa en cola episodis i disfruta'ls com tu vulgues amb velocitats de reproducció ajustables, suport per a capítols i un temporitzador per a dormir.
|
||||
Estalvia esforç, bateria i us de dades mòbils amb controls d'automatització per a descarregar (hores específiques, intervals i xarxes Wi-Fi) i esborrar episodis (basant-se en els teus favorits i els ajustos de retard).
|
||||
|
||||
Fet per entusiastes dels podcasts, AntennaPod es lliure en tots els sentits de la paraula: codi obert, sense cost, sense anuncis.
|
||||
|
||||
<b>Importa, organitza i reprodueix</b>
|
||||
• Organitza la reproducció desde qualsevol lloc: widget a la pantalla principal, notificacions de sistema i controls d'auricular amb fil o bluetooth.
|
||||
• Afegeix i importa feeds via iTunes, gPodder.net, fitxers OPML o enllaços RSS o Atom.
|
||||
• Disfruta escoltant a la teua manera amb velocitat de reproducció ajustable, suport per a capitols, memòria de posició de reproducció i un avançat temporitzador per a dormir (que pots resetetjar agitant el mòbil)
|
||||
• Accedix a feeds i episodis protegits amb contrasenya
|
||||
|
||||
<b>Organitza, comparteix i aprecia</b>
|
||||
• Recorda als millors dels millors marcant episodis com a favorits.
|
||||
• Troba un episodi usant el historial de reproducció o buscant títols i notes.
|
||||
• Comparteix episodis i feeds a través d'opcions avançades de medis socials i email, els serveis de gPodder.net i via l'exportació OPML.
|
||||
|
||||
<b>Controla el sistema</b>
|
||||
• Pren control de les descarregues automàtiques: tria feeds, exclou xarxes mòbils, selecciona xarxes WiFi específiques, requereix que el telèfon estiga carregant i selecciona hores o intervals.
|
||||
• Gestioneu l'emmagatzematge ajustant la quantitat d'episodis en emmagatzematge temporal, l'esborrat intel·ligent i triant la vostra ubicació preferida.
|
||||
• Adapteu-vos al vostre entorn fent servir el tema clar o el fosc.
|
||||
• Feu còpies de seguretat de les vostres subscripcions amb la integració amb gPodder.net i l'exportació a OPML.
|
||||
|
||||
<b>Uniu-vos a la comunitat d'AntennaPod!</b>
|
||||
L'AntennaPod el desenvolupen voluntaris. Podeu col·laborar, amb codi o comentaris.
|
||||
|
||||
Els amigables membres del nostre fòrum estaran contents d'ajudar-vos amb qualsevol pregunta. Esteu convidats a parlar de les features i de podcasting en general, a més.
|
||||
https://forum.antennapod.org/
|
||||
|
||||
Transifex és el lloc on podeu ajudar amb les traduccions.
|
||||
https://www.transifex.com/antennapod/antennapod
|
1
app/src/main/play/listings/ca/short-description.txt
Normal file
1
app/src/main/play/listings/ca/short-description.txt
Normal file
@ -0,0 +1 @@
|
||||
Reproductor i organitzador de podcasts, fàcil d'usar, flexible i de codi obert.
|
1
app/src/main/play/listings/ca/title.txt
Normal file
1
app/src/main/play/listings/ca/title.txt
Normal file
@ -0,0 +1 @@
|
||||
AntennaPod
|
31
app/src/main/play/listings/cs-CZ/full-description.txt
Normal file
31
app/src/main/play/listings/cs-CZ/full-description.txt
Normal file
@ -0,0 +1,31 @@
|
||||
AntennaPod je správce a přehrávač podcastů, co vám umožňuje okamžitý přístup k milionům podcastů (placené i zdarma) od nezávislých autorů, přes velké zahraniční korporace jako BBC, NPR a CNN, až po české DVTV nebo Český rozhlas. Své podcasty můžete jednoduše přidávat, importovat i exportovat pomocí iTunes, OPML souborů nebo RSS.
|
||||
Stahujte, streamujte nebo si vytvořte frontu epizod a užijte si poslech tak, jak ho máte rádi s nastavitelnou rychlostí přehrávání, podporou kapitol a s časovačem vypnutí.
|
||||
Ušetři si námahu, baterku i mobilní data s pomocí robustní automatické kontroly nad stahováním epizod (urči časy, intervaly a WIFI sítě) a mazáním epizod (na základě oblíbenosti a nastavení zpoždění).
|
||||
|
||||
Vytvořeno nadšenci do podcastů, AntennaPod je otevřený software (OSS), zdarma a bez reklam.
|
||||
|
||||
<b>Importujte, zorganizujte a přehrávejte</b>
|
||||
• Ovládejte přehrávání odkudkoli: z widgetu na domovské obrazovce, z oznámení nebo pomocí tlačítek na sluchátkách včetně Bluetooth
|
||||
• Přidejte a importujte podcasty přes iTunes anebo gPodder.net, OPML soubory a RSS anebo Atom odkazy
|
||||
• Užijte si poslech s nastavitelnou rychlostí přehrávání, podporou kapitol, zapamatování poslední pozice přehrávání a pokročilým časovačem vypnutí (restart zatřesením, snížení hlasitosti)
|
||||
• Přistupujte k zaheslovaným podcastům a epizodám
|
||||
|
||||
<b>Udržovat přehled, sdílet & ocenit</b>
|
||||
• Udržujte si seznam toho nejlepšího z nejlepšího přidáním epizod do oblíbených
|
||||
• Vyhledej tu správnou epizodu ve své historii přehrávání nebo prohledáním jmen a popisů epizod
|
||||
• Sdílej epizody a kanály pomocí pokročilých nastavení pro sociální média a email, gPodder.net službu nebo OPML export
|
||||
|
||||
<b>Ovládat systém</b>
|
||||
• Převezmi kontrolu nad automatickým stahováním: vybírej kanály, vyluč mobilní sítě, vyber specifické WIFI sítě, vyžaduj stav nabíjení telefonu a nastav časy nebo intervaly
|
||||
• Spravujte využití úložiště nastavením množství uložených epizod, chytrého mazání a výběrem místa uložení
|
||||
• Přizpůsobte si aplikaci svému prostředí pomocí světlého nebo tmavého motivu
|
||||
• Zálohujte své sbírky pomocí služby gPodder.net nebo exportem OPML souborů
|
||||
|
||||
<b>Přidejte se do komunity AntennaPodu!</b>
|
||||
AntennaPod je aktivně vyvíjen dobrovolníky. Můžete přispět také svým kódem nebo komentáři!
|
||||
|
||||
Naše přátelská členská základna vám ráda zodpoví jakékoli dotazy. Zveme vás k diskuzi o AntennaPodu nebo i podcastech obecně.
|
||||
https://forum.antennapod.org/
|
||||
|
||||
Na Transifexu můžete pomoct s překladem:
|
||||
https://www.transifex.com/antennapod/antennapod
|
1
app/src/main/play/listings/cs-CZ/short-description.txt
Normal file
1
app/src/main/play/listings/cs-CZ/short-description.txt
Normal file
@ -0,0 +1 @@
|
||||
Jednoduchý a flexibilní open-source program pro správu a poslech podcastů
|
1
app/src/main/play/listings/cs-CZ/title.txt
Normal file
1
app/src/main/play/listings/cs-CZ/title.txt
Normal file
@ -0,0 +1 @@
|
||||
AntennaPod
|
Binary file not shown.
After Width: | Height: | Size: 590 KiB |
Binary file not shown.
After Width: | Height: | Size: 592 KiB |
@ -24,8 +24,8 @@ Creado por entusiastas del pódcast, AntennaPod es libre en todos los sentidos:
|
||||
<b>¡Únete a la comunidad AntennaPod!</b>
|
||||
AntennaPod es desarrollado por voluntarios. ¡Tú también puedes contribuir, con tu código o con tus comentarios!
|
||||
|
||||
Visita GitHub para solicitar características nuevas, reportar fallos y contribuir con código:
|
||||
https://www.github.com/AntennaPod/AntennaPod
|
||||
Nuestros amables miembros del foro te ayudarán con cualquier duda que tengas. Estás invitado a discutir sobre las características y el podcasting en general.
|
||||
https://forum.antennapod.org/
|
||||
|
||||
Ayuda con las traducciones en Transifex:
|
||||
https://www.transifex.com/antennapod/antennapod
|
@ -1,7 +1,7 @@
|
||||
We are proud to release version 2.0 with a new logo and refreshed user interface.
|
||||
Thank you to 6420 users who participated in the vote for the new logo!
|
||||
|
||||
- Support for chapter images (only new episodes, by @ByteHamster)
|
||||
- Skip intro and ending per feed (by @tonytamsf)
|
||||
- Option to show notifications after episodes have been auto-downloaded (by @shortspider)
|
||||
- Bug fixes and improvements (by @ebraminio, @tonytamsf, @JessieVela, @ByteHamster and more)
|
||||
- A long-standing wish of many: playing local files! In the 'Add podcast' screen simply tap 'Add local folder' and select a location on your phone! (@ByteHamster, @igoralmeida & @damoasda)
|
||||
- Pick a country for the 'Discover' screen (@tonytamsf)
|
||||
- Keyboard shortcuts (@asdoi)
|
||||
- Search the PodcastIndex.org database (@edwinhere)
|
||||
- Pull to refresh (@asdoi)
|
||||
- Playback speed & filter dialogs (@ByteHamster & @bws9000)
|
||||
- Smooth sleep timer volume (@olivoto)
|
||||
|
@ -22,7 +22,7 @@
|
||||
android:id="@+id/widget_config_preview"
|
||||
layout="@layout/player_widget"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:layout_height="96dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="16dp" />
|
||||
</FrameLayout>
|
||||
@ -68,13 +68,38 @@
|
||||
android:max="100"
|
||||
android:progress="100" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/ckRewind"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Rewind" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/ckFastForward"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Forward" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/ckSkip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Skip" />
|
||||
</LinearLayout>
|
||||
<Button
|
||||
android:id="@+id/butConfirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/widget_create_button" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -35,11 +35,11 @@
|
||||
android:layout_marginRight="8dp"
|
||||
android:contentDescription="@string/search_podcast_hint"
|
||||
app:srcCompat="?attr/action_search"
|
||||
android:id="@+id/search_icon"
|
||||
android:id="@+id/searchButton"
|
||||
android:scaleType="center"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/combinedFeedSearchBox"
|
||||
android:id="@+id/combinedFeedSearchEditText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
@ -87,7 +87,7 @@
|
||||
android:textColor="?android:attr/textColorPrimary"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn_add_via_url"
|
||||
android:id="@+id/addViaUrlButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:drawableStartCompat="?attr/feed"
|
||||
@ -96,7 +96,7 @@
|
||||
android:text="@string/add_podcast_by_url"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn_add_local_folder"
|
||||
android:id="@+id/addLocalFolderButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:drawableStartCompat="?attr/ic_folder"
|
||||
@ -105,7 +105,7 @@
|
||||
android:text="@string/add_local_folder"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn_search_itunes"
|
||||
android:id="@+id/searchItunesButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:drawableStartCompat="?attr/action_search"
|
||||
@ -114,7 +114,7 @@
|
||||
android:text="@string/search_itunes_label"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn_search_fyyd"
|
||||
android:id="@+id/searchFyydButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:drawableStartCompat="?attr/action_search"
|
||||
@ -123,7 +123,7 @@
|
||||
android:text="@string/search_fyyd_label"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn_search_gpodder"
|
||||
android:id="@+id/searchGPodderButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:drawableStartCompat="?attr/action_search"
|
||||
@ -132,7 +132,7 @@
|
||||
android:text="@string/browse_gpoddernet_label"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn_search_podcastindex"
|
||||
android:id="@+id/searchPodcastIndexButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:drawableStartCompat="?attr/action_search"
|
||||
@ -141,7 +141,7 @@
|
||||
android:text="@string/search_podcastindex_label"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn_opml_import"
|
||||
android:id="@+id/opmlImportButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:drawableStartCompat="?attr/av_download"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user