Compare commits
615 Commits
Author | SHA1 | Date |
---|---|---|
André | 38f56d6d9b | |
André | 43d487f7e9 | |
André | f8c864f553 | |
ByteHamster | 0a768e6286 | |
ByteHamster | 1fc212ee88 | |
ByteHamster | 155d769fca | |
ByteHamster | baeb0d8ced | |
ByteHamster | fc1c13f4a5 | |
hades | 84b6f442fc | |
ByteHamster | dd8bf381c4 | |
Tony Tam | e856a9f118 | |
Tony Tam | 7c4f19c979 | |
Tony Tam | 27e9bf36b1 | |
Tony Tam | 8adbad9b66 | |
ByteHamster | 5f5d744e71 | |
ByteHamster | aa23656770 | |
ByteHamster | 568c0928c5 | |
ByteHamster | 59c5042a65 | |
ByteHamster | 8d3eb6aae9 | |
ByteHamster | 084723ad76 | |
ByteHamster | 53ce6cd71a | |
ByteHamster | a61f548792 | |
flofriday | 2827f41430 | |
flofriday | 6f572faa77 | |
Simon Conrad | ba14510b80 | |
ByteHamster | 87bfe1ea8c | |
ByteHamster | cb1a03cd8d | |
ByteHamster | 19396c1e17 | |
hades | 292a21f8f8 | |
flofriday | 3ed5b8af8c | |
ByteHamster | a8dfe6f123 | |
ByteHamster | b877344a7e | |
ByteHamster | 1505c50b1b | |
ByteHamster | 257c3bca5e | |
ByteHamster | 35817876bf | |
ByteHamster | 0341accef5 | |
flofriday | c063c59af3 | |
0x082c8bf1 | f69822582d | |
ByteHamster | d9d48674ed | |
ByteHamster | 4d79419e8e | |
ByteHamster | dbbb21bd3b | |
flofriday | 4cf362393a | |
flofriday | 4bc0b38280 | |
flofriday | 7b048ed579 | |
flofriday | c56facd141 | |
hades | 841bda020f | |
ByteHamster | 0aa8e85003 | |
Tom Hense | 2f58b4b360 | |
ByteHamster | 5e7858ef7e | |
ByteHamster | 2043e71299 | |
ByteHamster | 91bcf4b400 | |
ByteHamster | 8037bd2239 | |
ByteHamster | e9b3cc34fe | |
ByteHamster | d6b2a49caa | |
ByteHamster | f3bca9d9e4 | |
ByteHamster | 04fab47072 | |
ByteHamster | 456159e85f | |
ByteHamster | 25e4703da4 | |
ByteHamster | 863d4c3b61 | |
ByteHamster | 58db8f1032 | |
ByteHamster | 80ea632da3 | |
ByteHamster | 1a92db4706 | |
ByteHamster | d9e84f8c38 | |
ByteHamster | bd4e9e19d7 | |
ByteHamster | e578f4ca93 | |
ByteHamster | fc40da28a7 | |
ByteHamster | e4bac5ea71 | |
Fredrik Wallén | 00d6df6261 | |
ByteHamster | 687db0f5ed | |
Taco | b6a4049ff4 | |
ByteHamster | 92ab575b15 | |
ByteHamster | 2143ab1351 | |
ByteHamster | 0288d4e51e | |
ByteHamster | e894ff1ccb | |
ByteHamster | 613a9896e9 | |
ByteHamster | a846e417b0 | |
ByteHamster | edb440a5a9 | |
ByteHamster | 4e47691e70 | |
ByteHamster | 86ff7f540b | |
loucasal | bf1bd56186 | |
ByteHamster | d76b6f63ee | |
ByteHamster | 8accb54685 | |
ByteHamster | 2fd73b148d | |
ByteHamster | 6f3a9b1676 | |
ByteHamster | 0c8c9a89a3 | |
ByteHamster | f9dd837362 | |
ByteHamster | 8f553f08f0 | |
ByteHamster | 5ede21d676 | |
ByteHamster | 13a985ca1e | |
ByteHamster | 1dbda2fb8a | |
ByteHamster | 130da46f5d | |
ByteHamster | 160089d3ff | |
ByteHamster | 69b24699a3 | |
ByteHamster | 15eab50223 | |
ByteHamster | 5c6000155c | |
ByteHamster | 4078b3475e | |
ByteHamster | 7b390f1c92 | |
ByteHamster | 701b1ce339 | |
ByteHamster | 084b9c2317 | |
ByteHamster | 84b0a79d8c | |
ByteHamster | 5218e06904 | |
ByteHamster | 79856b7931 | |
ByteHamster | a065d3fc33 | |
ByteHamster | f6b45e7162 | |
ByteHamster | f20ce1fc69 | |
ByteHamster | 376c83d200 | |
ByteHamster | 69f0daa2e8 | |
ByteHamster | ab64807f64 | |
ByteHamster | bd17373c18 | |
ByteHamster | 0a6b7ed699 | |
ByteHamster | c71e86f427 | |
ByteHamster | f0e685c5a9 | |
ByteHamster | ac8e8137bb | |
ByteHamster | 27aa5cba96 | |
ByteHamster | 542d50cba7 | |
ByteHamster | 4bc90897b6 | |
ByteHamster | 53f68ca260 | |
ByteHamster | 55845c46a1 | |
ByteHamster | d40b9ef59b | |
ByteHamster | 2d77b1f118 | |
Taco | b84a05bd4e | |
ByteHamster | 0cbd97b5cb | |
ByteHamster | 17f5a5d1b8 | |
Tony Tam | 8dc8cc64a8 | |
Taco | 48c0ccb4a2 | |
ByteHamster | da21d92f96 | |
ByteHamster | afc21f46a9 | |
ByteHamster | 7d89b18afb | |
Taco | 030226f288 | |
ByteHamster | 2f3f1fd186 | |
ByteHamster | 8177875674 | |
ByteHamster | 0848364810 | |
ByteHamster | 44e123105c | |
ByteHamster | 635e6c8267 | |
ByteHamster | 2e9fcc044f | |
ByteHamster | 5c98a33ed2 | |
ByteHamster | baa58ac17f | |
ByteHamster | 095a6b3e9d | |
ByteHamster | 393a8cebd3 | |
Taco | b18e5f0de6 | |
Taco | f1fe1b573f | |
ByteHamster | 48e8197e3f | |
ByteHamster | aaf225c7af | |
ByteHamster | 755ccc42ec | |
ByteHamster | 39e2d6e230 | |
Taco | e1ef2a643a | |
ByteHamster | e8807bb329 | |
Taco | b2718a9a12 | |
peking_ling | b4a6203e1a | |
ByteHamster | 7c14534179 | |
ByteHamster | 68ec4e2527 | |
TacoTheDank | 522288260c | |
TacoTheDank | c2ccc28b95 | |
TacoTheDank | 6f582e4c52 | |
ByteHamster | 5e8960f4bc | |
ByteHamster | cae848b505 | |
ByteHamster | 6c0f9eec62 | |
Taco | 40da13e014 | |
ByteHamster | c21edc8b79 | |
Taco | c06a3a6d27 | |
quails4Eva | 60f3d77eb2 | |
ByteHamster | 3c77d43e76 | |
ByteHamster | ee99ef934c | |
peking_ling | fa9dd8cb5a | |
ByteHamster | 33569e8992 | |
Matej Drobnič | 7332c04631 | |
ByteHamster | a7068cc24a | |
mueller-ma | 9cfbae183c | |
ByteHamster | 82c93bf7ee | |
Taco | ef4af0d29d | |
loucasal | 55c72097b0 | |
ByteHamster | 45a05ed332 | |
ByteHamster | 3b2e7420cd | |
ByteHamster | 22f36bc9c0 | |
ByteHamster | 7a40a505f3 | |
ByteHamster | dc63386e89 | |
ByteHamster | e5f564be94 | |
ByteHamster | d7572e4de4 | |
ByteHamster | d9ebf42167 | |
ByteHamster | 0d29e44de5 | |
mueller-ma | 556597a173 | |
ByteHamster | c7c5ab567b | |
ByteHamster | c07ae17962 | |
Matej Drobnič | 0f5600932d | |
ByteHamster | f0e96a2692 | |
ueen | 34fb2050b2 | |
ByteHamster | 6e2a8b86a7 | |
ueen | b1e6da935b | |
ByteHamster | 0361e05ca8 | |
ByteHamster | 9eac993e45 | |
ByteHamster | c8230b7034 | |
ByteHamster | 3410d79eb2 | |
ByteHamster | bf67218422 | |
ByteHamster | 4a782e457c | |
quails4Eva | c5093c9ff9 | |
ueen | f1e91f9d8b | |
ByteHamster | b2ea588b54 | |
satish-vanjara | de8bc4ad30 | |
ueen | c81157f0e6 | |
ByteHamster | 8c7d567a0c | |
ByteHamster | 28edb71fd6 | |
ByteHamster | 9db26b7bab | |
Tony Tam | 7508e15ab1 | |
Matej Drobnič | f476086114 | |
ByteHamster | 55f83eb9e1 | |
ByteHamster | 4e4b6062ac | |
ByteHamster | d39ddaa113 | |
ByteHamster | b066c6e23c | |
ByteHamster | db88dc10e6 | |
ByteHamster | 3852d50f92 | |
ByteHamster | ae4205c6d3 | |
ByteHamster | 2e76dc8d0c | |
ByteHamster | c1712fe2f5 | |
ByteHamster | 1caffa70f7 | |
ByteHamster | 58081fe5bf | |
ByteHamster | 636d705e8f | |
ByteHamster | 37ad5d490b | |
ByteHamster | ee554d0306 | |
Andrey Gusev | b792eaa18e | |
ByteHamster | 6177cc2460 | |
caoilTe O'Connor | 95f431fec9 | |
peking_ling | 45480f4e2c | |
Tony Tam | c7d6cd358c | |
Erik Johnson | 637230e382 | |
ByteHamster | 0bb4870820 | |
ByteHamster | 10672f8086 | |
ByteHamster | 1e3761984a | |
ByteHamster | 956a455f84 | |
ByteHamster | 46c3d4e8c1 | |
Tony Tam | 7bfb53cc00 | |
ByteHamster | 8af06a9f25 | |
Matej Drobnič | 4d627cc3af | |
ByteHamster | 47761bf98f | |
ByteHamster | 0a6a3d6854 | |
ByteHamster | 34c7fd576f | |
ByteHamster | 1d415c9f7f | |
ByteHamster | f7a13065a9 | |
Harshad Vedartham | 2c3fb5610a | |
Vinod Patil | 691ed73910 | |
Bhaskar Kaura | 01f1927770 | |
ByteHamster | 4931734d94 | |
Erik Johnson | 8a011badd3 | |
Matej Drobnič | 346365b8d0 | |
ByteHamster | fa75317bce | |
ByteHamster | 69be89881a | |
ByteHamster | 0b7403e1dd | |
Vinod Patil | 61669d32fa | |
ByteHamster | 8d4270ab87 | |
ByteHamster | eb8267a4ae | |
Taco | e9d190da1b | |
Keunes | 77483913d4 | |
ByteHamster | 0efa91a0b1 | |
ByteHamster | 475e0f5128 | |
ByteHamster | 58484d5790 | |
ByteHamster | 2ee2cb6702 | |
ByteHamster | c732ecba8b | |
ByteHamster | c38b263458 | |
ByteHamster | 3fae29b375 | |
ByteHamster | da200f6139 | |
ByteHamster | 4dc1196c39 | |
ByteHamster | 7cb0ba8156 | |
ByteHamster | 7e8ac3aeb6 | |
blair | e466bba013 | |
Rahmat Ramadhan | 922395a448 | |
ByteHamster | 7229cb40e9 | |
caoilTe O'Connor | 574ec1434c | |
ByteHamster | 0e52f08aa5 | |
caoilTe O'Connor | 705aae44ba | |
ByteHamster | 8073de55af | |
ByteHamster | c680f84a0f | |
ByteHamster | b933c0eb71 | |
ByteHamster | 955fca6e38 | |
ByteHamster | 37c29a6372 | |
ByteHamster | 1e7c347cd2 | |
ByteHamster | e4df6222c2 | |
ByteHamster | ce7cffdbf3 | |
ByteHamster | b7491e5e71 | |
Matej Drobnič | 8ebf153970 | |
ByteHamster | 9ed5485ae3 | |
Keunes | 3564484c2c | |
ByteHamster | 1baa8e688b | |
ByteHamster | 087770026f | |
ByteHamster | b5cd973a86 | |
ByteHamster | 91d5238f08 | |
ByteHamster | 823907bf1f | |
ByteHamster | 4c9db040fe | |
ByteHamster | 3ce3219a3b | |
ByteHamster | 056d262ab5 | |
ByteHamster | 18ab4ab8c6 | |
ByteHamster | 4182f83367 | |
ByteHamster | 4f6b563e3f | |
ByteHamster | 49ac7a83b8 | |
ByteHamster | ca9358234f | |
ByteHamster | 9f8edd0e9d | |
ByteHamster | 9be6562b4e | |
ByteHamster | 5ae766b1a1 | |
ByteHamster | 196ff13442 | |
ByteHamster | 019e3574c4 | |
ByteHamster | 1a0134d5f2 | |
ByteHamster | c99ed8b48a | |
ByteHamster | fa12968ae5 | |
ByteHamster | f1f3674230 | |
ByteHamster | 8d1eb62f0b | |
ByteHamster | 75c3c4cf24 | |
ByteHamster | 6999a944bb | |
ByteHamster | 23d4cf5632 | |
Manjeet Yadav | 192d71c7ab | |
femmdi | de3f6aab91 | |
peking_ling | 8b7d3cabac | |
Jonathan Zopf | 7b5d366536 | |
ebraminio | d51e937e96 | |
ebraminio | 10c70dd5f1 | |
ByteHamster | 7d1259a39a | |
mueller-ma | 3da7fcf8f0 | |
TacoTheDank | cf4345564c | |
TacoTheDank | 90d6095dad | |
Jonathan Zopf | 194067deea | |
ByteHamster | c9d74e7942 | |
peking_ling | c759eed50d | |
ByteHamster | b8a1c1f49a | |
mueller-ma | da16f13e8b | |
ByteHamster | eaae6585d6 | |
ByteHamster | 6d7bfef8a5 | |
ByteHamster | 4c286931cd | |
ByteHamster | f8be7d596d | |
mueller-ma | 967e289f91 | |
mueller-ma | 446b938b3a | |
ByteHamster | 1bc053186d | |
mueller-ma | aab19f3a5c | |
ByteHamster | e2bbc3ef17 | |
mueller-ma | 5a74279ce8 | |
ByteHamster | b063f0508f | |
Keunes | 358e64b079 | |
ByteHamster | a877809bad | |
Keunes | ca0be76fdc | |
Rob Pilling | e0227f9b16 | |
mueller-ma | 0bdf9d9e28 | |
ByteHamster | 4cdc5d14d9 | |
ByteHamster | 2a169d9df6 | |
ByteHamster | 8396a34670 | |
ByteHamster | e10a8b534b | |
ByteHamster | 84e1ff248f | |
ByteHamster | 2021e0e915 | |
ByteHamster | 1541af0fd5 | |
ByteHamster | 8ea0d1907b | |
ByteHamster | 39d309e906 | |
ByteHamster | da9bb8d578 | |
GitStart | a828660b44 | |
ByteHamster | 7ed78887c4 | |
ByteHamster | a08f387c56 | |
ByteHamster | 596bdaed3f | |
ByteHamster | e9ba45e2bd | |
ByteHamster | 9b989fed43 | |
mueller-ma | 3e101cca2a | |
ByteHamster | 038847177e | |
ByteHamster | b706ab9776 | |
Taco | 78f65349d5 | |
ByteHamster | 8c9b61e599 | |
ByteHamster | d5321a147b | |
Andrzej Węgłowski | 548f9e021e | |
ByteHamster | ee69e8c66b | |
Keunes | 214bf974cf | |
ByteHamster | 4f7f49e1e7 | |
ByteHamster | 07b59d8b32 | |
ByteHamster | 2c0b970044 | |
ByteHamster | 86c11584b5 | |
ByteHamster | 835f007b67 | |
ByteHamster | 870fe2be56 | |
ByteHamster | 3ddd7f2f80 | |
ByteHamster | 834426cb14 | |
ByteHamster | 6b6753ad84 | |
ByteHamster | 95b97b6f49 | |
ByteHamster | 0b3e664057 | |
ByteHamster | d8d94878a2 | |
ByteHamster | 24d1a06662 | |
GitStart | 581e71b306 | |
ByteHamster | 5e75c968ad | |
ByteHamster | ccea00e405 | |
ByteHamster | ebcb5e2a7c | |
ByteHamster | 9cd59a6720 | |
ByteHamster | 3e077e5653 | |
ByteHamster | 34553475d9 | |
ByteHamster | 5f00294c29 | |
ByteHamster | 06347a3df9 | |
ByteHamster | 8be62b6d0e | |
ByteHamster | 6d72d7cebf | |
GitStart | 658c47f7a7 | |
Andrew Booze | 59253db2e4 | |
ByteHamster | 7753c500db | |
Keunes | e20d11e130 | |
ByteHamster | 28844af6ff | |
peking_ling | 240737e3ac | |
Erik Johnson | 9fed944392 | |
Tony Tam | 5c79bc7c45 | |
GitStart | 25ddd73f24 | |
femmdi | 50eb1e9cf9 | |
ByteHamster | 2b22d4b697 | |
ByteHamster | e58e2d0639 | |
ByteHamster | a5d4864776 | |
ByteHamster | 2833812238 | |
ByteHamster | 437f094936 | |
ByteHamster | c98194f519 | |
ByteHamster | cfb9745246 | |
ByteHamster | 997860fe52 | |
ByteHamster | 4e1a3be122 | |
ByteHamster | 5b6fe580e0 | |
ByteHamster | caf49c5da8 | |
Jared234 | 7a2f4771ec | |
GitStart | 8248bc6bb1 | |
ByteHamster | 22e6a0c40f | |
GitStart | 4096aaf47e | |
ByteHamster | f9839aba99 | |
ByteHamster | f9076cc8e3 | |
Victor Häggqvist | 52ddf47e36 | |
ByteHamster | 08ee701dd7 | |
ByteHamster | 8819487699 | |
ByteHamster | 530165206b | |
ByteHamster | db5d47967a | |
GitStart | f9e344e215 | |
ByteHamster | 7af00f7e83 | |
ByteHamster | 731adeaf2c | |
ByteHamster | efcb710703 | |
ByteHamster | e261514c5b | |
ByteHamster | 04a8ee5787 | |
GitStart | 73a6ff1f60 | |
ByteHamster | 12793de604 | |
ByteHamster | 78bce635c4 | |
ByteHamster | 6e7d1f1994 | |
ByteHamster | 98107419e0 | |
GitStart | 13439e1a48 | |
ByteHamster | cb2cc7a357 | |
ByteHamster | bb43cd4613 | |
ByteHamster | 94b50b37f1 | |
ByteHamster | 08fdedb236 | |
Keunes | f995fd96df | |
ByteHamster | 63e9d7f696 | |
ByteHamster | c5b34114cd | |
ByteHamster | cf057acdf7 | |
ByteHamster | d7bfe89b13 | |
ByteHamster | 941ebbdc2b | |
ByteHamster | 461dcb8c11 | |
ByteHamster | ba9da3b74c | |
Vishnu Sanal T | 88289d02ae | |
Vishnu Sanal T | 97889a46ed | |
Patrick Demers | ebfda200e0 | |
ByteHamster | 025944d6ab | |
ByteHamster | 0776f232d3 | |
ByteHamster | 670f26bb0e | |
ByteHamster | fbfd7c43ac | |
ByteHamster | c478b49b1e | |
ByteHamster | 3acec11322 | |
ByteHamster | 80a91d9da0 | |
ByteHamster | a7e5f2f4ae | |
ByteHamster | d8d6f1c72f | |
ByteHamster | 10ee446f4e | |
ByteHamster | 1d251492b0 | |
ByteHamster | f66e3dd661 | |
ByteHamster | aa6b7b86f8 | |
ByteHamster | 95eae1519a | |
ByteHamster | ef97411fbb | |
ByteHamster | b670cf6111 | |
ByteHamster | effe70a412 | |
ByteHamster | 690eb6af8d | |
ByteHamster | 7101ea41f0 | |
ByteHamster | 1c2742e123 | |
ByteHamster | 39ec95c3ad | |
ByteHamster | ac4409bcf4 | |
ByteHamster | b1237094b2 | |
ByteHamster | 32ffb2d1e9 | |
ByteHamster | ace0724e5d | |
ByteHamster | 742f6f3e8a | |
ByteHamster | 4513711981 | |
ByteHamster | 2d3740e7ad | |
ByteHamster | e4b6f70339 | |
ByteHamster | ea7059f688 | |
ByteHamster | 94b2a4288b | |
ByteHamster | 655e3c6e4e | |
ByteHamster | f5adc4e824 | |
ByteHamster | 46d27a86e1 | |
ByteHamster | bc3b717911 | |
Tong Liu | bec1eaa679 | |
Jared234 | f91d536ab9 | |
ByteHamster | 97ab1725db | |
ByteHamster | 63c5e2dc72 | |
ByteHamster | d5e80b089b | |
ByteHamster | 1c08543430 | |
ByteHamster | 1ee85b5bb0 | |
ByteHamster | 75a795e3d7 | |
ByteHamster | 2d115a0ec5 | |
ByteHamster | 6e9325b549 | |
ByteHamster | b4026a9a82 | |
ByteHamster | 6c4c51994d | |
ByteHamster | 6c1bf9db05 | |
ByteHamster | 3973f450be | |
ByteHamster | 96231c4ee1 | |
Ricardo Borges Jr | d62ea313d7 | |
ByteHamster | 807e09ecdd | |
LukasBrilla5 | d585e37e11 | |
ByteHamster | 63ba5c458f | |
ByteHamster | 085147723e | |
ByteHamster | 410b8f1539 | |
ByteHamster | d8a2dd5f83 | |
ByteHamster | 0101f1244e | |
ByteHamster | 65d58fdd9b | |
ByteHamster | b140d7297a | |
ByteHamster | d61745be86 | |
Vishnu Sanal T | e4d4c69519 | |
ByteHamster | be8c8cef4d | |
ByteHamster | 11292b598c | |
ByteHamster | ae3971a58f | |
ByteHamster | 323149642a | |
ByteHamster | d08b9e196e | |
ByteHamster | 546c8841db | |
ByteHamster | 70a847f6ba | |
ByteHamster | 5b8cee0de0 | |
ByteHamster | cd9845ed4c | |
ByteHamster | 6c1ec57bc1 | |
ByteHamster | c1fbb53805 | |
ByteHamster | 9b06bf0dc5 | |
ByteHamster | 6921d7162e | |
ByteHamster | 1e336ac0f8 | |
ByteHamster | a836745079 | |
ByteHamster | a29041cd4d | |
ByteHamster | 17f2ebd7f2 | |
Vishnu Sanal T | c171ab6823 | |
ByteHamster | fe2195f051 | |
ByteHamster | e260056f08 | |
Tong Liu | a87d660698 | |
ByteHamster | edcf831346 | |
ByteHamster | b9ded7ea3b | |
ByteHamster | a15d94c94c | |
ByteHamster | 892b499687 | |
ByteHamster | ed53f0904b | |
ByteHamster | 2e54fa6981 | |
Lukmannudin | 655b880c46 | |
ByteHamster | cac231a461 | |
ByteHamster | c7e41c31b6 | |
ByteHamster | 9624d8ce9e | |
ByteHamster | 25dd4902ba | |
Lukmannudin | e6613807c0 | |
Simon Rusinov | 4c30d8ff7f | |
ByteHamster | 8ff9dd829a | |
ByteHamster | 7d0b0e57ee | |
Lukmannudin | 5dc3699361 | |
ByteHamster | 504002c48f | |
ByteHamster | 232a026651 | |
ByteHamster | 0facf7ce6a | |
ByteHamster | b0b95f0a05 | |
ByteHamster | 4014951e9c | |
ByteHamster | 2a47d3136f | |
ByteHamster | 2add262a6d | |
ByteHamster | e4419579d7 | |
ByteHamster | 9783f5dc25 | |
Erik Johnson | 873ffa9cef | |
ByteHamster | 0cb47ac6d5 | |
ByteHamster | 7e7e945185 | |
ByteHamster | a621551658 | |
ByteHamster | 18e5e89d12 | |
ByteHamster | f1381a9358 | |
Keunes | 1113bd71de | |
ByteHamster | b90bfbd4dc | |
ByteHamster | 0aa50b8d23 | |
ByteHamster | b605901c52 | |
TacoTheDank | 07b4a237f6 | |
ByteHamster | 097a491504 | |
ByteHamster | bd0f54dbf6 | |
ByteHamster | 261c7982de | |
ByteHamster | cbff160bd5 | |
ByteHamster | 37b49b1e38 | |
ByteHamster | 2740816bb8 | |
ByteHamster | a524b81060 | |
ByteHamster | e5d2d1b6ef | |
ByteHamster | cbfa0181f4 | |
ByteHamster | 8426e32fe8 | |
ByteHamster | ac8114342c | |
ByteHamster | a12854a96b | |
cliambrown | a528e8adfd | |
Erik Johnson | fcce8e9e0e | |
ByteHamster | 5baa13b53d | |
ByteHamster | 6940c1a3c5 | |
ByteHamster | c26d2d289c | |
TacoTheDank | bbea9c990c | |
TacoTheDank | 3c0d9a6d05 | |
TacoTheDank | 9599281fdb | |
ByteHamster | 6f67d6905a | |
ByteHamster | ad9de4467b | |
ByteHamster | 5ace16b31b | |
ByteHamster | 927af053c5 | |
ByteHamster | 54bf4d149f | |
Erik Johnson | 539d0c928d | |
ByteHamster | a63948ec6d | |
ByteHamster | d8ecda1b62 | |
ByteHamster | 6f3d4f277d | |
ByteHamster | 45e625d988 | |
ByteHamster | 1207660787 | |
ByteHamster | 836e2199bc | |
ByteHamster | c42ed1d187 | |
ByteHamster | 4c88a1aa69 | |
ByteHamster | 0bf6f2f1fd | |
ByteHamster | a67df09d29 | |
ByteHamster | 0ea69e4063 | |
ByteHamster | f6a338f1d1 | |
ByteHamster | 6a0f646506 | |
ByteHamster | 58515df6ec | |
ByteHamster | 77104c9038 | |
ByteHamster | ec92722c04 | |
ByteHamster | 28a397c897 | |
ByteHamster | 38dcfa9d35 | |
Erik Johnson | 732462438a | |
ByteHamster | 7042b8d616 | |
ByteHamster | a265d17f54 | |
ByteHamster | 6e199de7ab | |
ByteHamster | 0c8c860040 | |
ByteHamster | d3b5b0e14e | |
Dhruv Patidar | 8e994165e6 |
|
@ -0,0 +1,7 @@
|
|||
# Settings in .editorconfig should match checkstyle config
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
max_line_length = 120
|
|
@ -11,7 +11,9 @@ body:
|
|||
attributes:
|
||||
label: Checklist
|
||||
options:
|
||||
- label: I have used the search function for [open](https://github.com/AntennaPod/AntennaPod/issues) **and** [closed](https://github.com/AntennaPod/AntennaPod/issues?q=is%3Aissue+is%3Aclosed) issues to see if someone else has already submitted the same bug report.
|
||||
- label: I have used the search function for [**OPEN**](https://github.com/AntennaPod/AntennaPod/issues) issues to see if someone else has already submitted the same bug report.
|
||||
required: true
|
||||
- label: I have **also** used the search function for [**CLOSED**](https://github.com/AntennaPod/AntennaPod/issues?q=is%3Aissue+is%3Aclosed) issues to see if the problem is already solved and just waiting to be released.
|
||||
required: true
|
||||
- label: I will describe the problem with as much detail as possible.
|
||||
required: true
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: AntennaPod Forum
|
||||
- name: Help & Support
|
||||
url: https://forum.antennapod.org/
|
||||
about: Reduce developer's workload by asking other users.
|
||||
about: Reduce developer's support workload by asking other users on our forum.
|
||||
- name: F-Droid Release
|
||||
url: https://antennapod.org/documentation/general/f-droid
|
||||
about: Waiting for an update to appear on F-Droid? No need to create an issue, please just be patient!
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
name: Feature request
|
||||
description: Request a new feature or enhancement
|
||||
labels: ["Needs: Triage", "Type: Feature request"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Checklist
|
||||
options:
|
||||
- label: I have used the search function for [open](https://github.com/AntennaPod/AntennaPod/issues) **and** [closed](https://github.com/AntennaPod/AntennaPod/issues?q=is%3Aissue+is%3Aclosed) issues to see if someone else has already submitted the same feature request.
|
||||
- label: I have used the search function for [**OPEN**](https://github.com/AntennaPod/AntennaPod/issues) issues to see if someone else has already submitted the same feature request.
|
||||
required: true
|
||||
- label: I have **also** used the search function for [**CLOSED**](https://github.com/AntennaPod/AntennaPod/issues?q=is%3Aissue+is%3Aclosed) issues to see if the feature was already implemented and is just waiting to be released, or if the feature was rejected.
|
||||
required: true
|
||||
- label: I will describe the problem with as much detail as possible.
|
||||
required: true
|
||||
|
|
|
@ -1 +1,15 @@
|
|||
<!-- Please make sure that you have read our contribution guidelines: https://github.com/AntennaPod/AntennaPod/blob/develop/CONTRIBUTING.md#submit-a-pull-request -->
|
||||
### Description
|
||||
|
||||
|
||||
### Checklist
|
||||
<!--
|
||||
To help us keep the issue tracker clean and work as efficient as possible,
|
||||
please make sure that you have done all of the following.
|
||||
You can tick the boxes below by placing an x inside the brackets like this: [x]
|
||||
-->
|
||||
- [ ] I have read the contribution guidelines: https://github.com/AntennaPod/AntennaPod/blob/develop/CONTRIBUTING.md#submit-a-pull-request
|
||||
- [ ] I have performed a self-review of my code
|
||||
- [ ] I have run the automated code checks using `./gradlew checkstyle spotbugsPlayDebug spotbugsDebug :app:lintPlayDebug`
|
||||
- [ ] My code follows the style guidelines of the AntennaPod project: https://github.com/AntennaPod/AntennaPod/wiki/Code-style
|
||||
- [ ] I have mentioned the corresponding issue and the relevant keyword (e.g., "Closes: #xy") in the description (see https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)
|
||||
- [ ] If it is a core feature, I have added automated tests
|
||||
|
|
|
@ -2,69 +2,57 @@ name: Checks
|
|||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
types: [ opened, synchronize, reopened ]
|
||||
push:
|
||||
branches: [master, develop]
|
||||
branches: [ master, develop ]
|
||||
|
||||
jobs:
|
||||
code-style:
|
||||
name: "Code Style"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Checkstyle
|
||||
run: |
|
||||
curl -s -L https://github.com/checkstyle/checkstyle/releases/download/checkstyle-10.3.1/checkstyle-10.3.1-all.jar > checkstyle.jar
|
||||
find . -name "*\.java" | xargs java -Dconfig_loc=config/checkstyle -jar checkstyle.jar -c config/checkstyle/checkstyle.xml
|
||||
- name: Find PR Base Commit
|
||||
id: vars
|
||||
run: |
|
||||
git fetch origin develop
|
||||
echo "::set-output name=branchBaseCommit::$(git merge-base origin/develop HEAD)"
|
||||
- name: Diff-Checkstyle
|
||||
run: |
|
||||
curl -s -L https://github.com/yangziwen/diff-check/releases/download/0.0.7/diff-checkstyle.jar > diff-checkstyle.jar
|
||||
java -Dconfig_loc=config/checkstyle -jar diff-checkstyle.jar -c config/checkstyle/checkstyle-new-code.xml --git-dir . --base-rev ${{ steps.vars.outputs.branchBaseCommit }}
|
||||
- name: XML of changed files
|
||||
run: |
|
||||
curl -s -L https://github.com/ByteHamster/android-xml-formatter/releases/download/1.1.0/android-xml-formatter.jar > android-xml-formatter.jar
|
||||
git diff --name-only ${{ steps.vars.outputs.branchBaseCommit }} --diff-filter=AM | { grep "res/layout/" || true; } | xargs java -jar android-xml-formatter.jar
|
||||
test $(git diff | wc -l) -eq 0 || (echo -e "\n\n===== Found XML code style violations! See output below how to fix them. =====\n\n" && git --no-pager diff --color=always && false)
|
||||
|
||||
wrapper-validation:
|
||||
name: "Gradle Wrapper Validation"
|
||||
needs: code-style
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: gradle/wrapper-validation-action@v2
|
||||
|
||||
static-analysis:
|
||||
name: "Static Code Analysis"
|
||||
needs: code-style
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: gradle-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
|
||||
- name: Lint
|
||||
run: ./gradlew lintPlayRelease lintRelease
|
||||
- name: SpotBugs
|
||||
run: ./gradlew spotbugsPlayDebug spotbugsDebug 2>&1 | grep -i "spotbugs"
|
||||
- name: Configure parallel build
|
||||
run: echo "org.gradle.parallel=true" >> local.properties
|
||||
- name: XML code style
|
||||
run: |
|
||||
curl -s -L https://github.com/ByteHamster/android-xml-formatter/releases/download/1.1.0/android-xml-formatter.jar > android-xml-formatter.jar
|
||||
find . -wholename "*/res/layout/*.xml" | xargs java -jar android-xml-formatter.jar
|
||||
test $(git diff | wc -l) -eq 0 || (echo -e "\n\n===== Found XML code style violations! See output below how to fix them. =====\n\n" && git --no-pager diff --color=always && false)
|
||||
- name: Checkstyle, Lint, SpotBugs
|
||||
run: ./gradlew checkstyle :app:lintPlayDebug spotbugsPlayDebug spotbugsDebug
|
||||
- name: Generate readable error messages for GitHub
|
||||
if: failure()
|
||||
run: |
|
||||
git diff --name-only | xargs -I '{}' echo "::error file={},line=1,endLine=1,title=XML Format::Run android-xml-formatter.jar on this file or view CI output to see how it should be formatted."
|
||||
python .github/workflows/errorPrinter.py
|
||||
|
||||
unit-test:
|
||||
name: "Unit Test: ${{ matrix.variant }}"
|
||||
needs: code-style
|
||||
needs: static-analysis
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
|
@ -83,14 +71,21 @@ jobs:
|
|||
execute-tests: false
|
||||
upload-artifact: false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: gradle-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
|
||||
- name: Configure parallel build
|
||||
run: echo "org.gradle.parallel=true" >> local.properties
|
||||
- name: Create temporary release keystore
|
||||
run: keytool -noprompt -genkey -v -keystore "app/keystore" -alias alias -storepass password -keypass password -keyalg RSA -validity 10 -dname "CN=antennapod.org, OU=dummy, O=dummy, L=dummy, S=dummy, C=US"
|
||||
- name: Build
|
||||
|
@ -98,7 +93,7 @@ jobs:
|
|||
- name: Test
|
||||
if: matrix.execute-tests == true
|
||||
run: ./gradlew test${{ matrix.variant }}UnitTest test${{ matrix.base-variant }}UnitTest
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: matrix.upload-artifact == true
|
||||
with:
|
||||
name: app-play-debug.apk
|
||||
|
@ -106,46 +101,34 @@ jobs:
|
|||
|
||||
emulator-test:
|
||||
name: "Emulator Test"
|
||||
needs: code-style
|
||||
runs-on: macOS-latest
|
||||
needs: static-analysis
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
api-level: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: gradle-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
|
||||
- name: Configure parallel build
|
||||
run: echo "org.gradle.parallel=true" >> local.properties
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew assemblePlayDebugAndroidTest
|
||||
- name: Cache AVD
|
||||
uses: actions/cache@v3
|
||||
id: avd-cache
|
||||
with:
|
||||
path: |
|
||||
~/.android/avd/*
|
||||
~/.android/adb*
|
||||
key: avd-${{ hashFiles('.github/workflows/*') }}
|
||||
- name: Create AVD and generate snapshot for caching
|
||||
if: steps.avd-cache.outputs.cache-hit != 'true'
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: ${{ env.api-level }}
|
||||
target: aosp_atd
|
||||
channel: canary
|
||||
force-avd-creation: false
|
||||
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
||||
disable-animations: true
|
||||
script: echo "Generated AVD snapshot for caching."
|
||||
- name: Enable KVM group perms
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
- name: Android Emulator test
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
|
@ -155,8 +138,8 @@ jobs:
|
|||
force-avd-creation: false
|
||||
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
||||
disable-animations: true
|
||||
script: zsh .github/workflows/runEmulatorTests.sh
|
||||
- uses: actions/upload-artifact@v3
|
||||
script: bash .github/workflows/runEmulatorTests.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: test-report
|
||||
|
|
|
@ -13,15 +13,15 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/stale@v5
|
||||
with:
|
||||
days-before-stale: 15
|
||||
days-before-close: 15
|
||||
only-labels: 'Awaiting reply'
|
||||
stale-issue-label: 'Still awaiting reply'
|
||||
stale-pr-label: 'Still awaiting reply'
|
||||
stale-issue-message: "This issue will be closed when we don't get a reply within 15 days."
|
||||
stale-pr-message: "This PR will be closed when we don't get a reply within 15 days."
|
||||
labels-to-remove-when-unstale: 'Awaiting reply'
|
||||
close-issue-label: "Close reason: no reply"
|
||||
close-pr-label: "Close reason: no reply"
|
||||
close-issue-message: "This issue was closed because we didn't get a reply for 30 days."
|
||||
close-pr-message: "This PR was closed because we didn't get a reply for 30 days."
|
||||
days-before-stale: 7
|
||||
days-before-close: 7
|
||||
only-labels: 'Needs: Reply'
|
||||
stale-issue-label: 'Needs: Reply still'
|
||||
stale-pr-label: 'Needs: Reply still'
|
||||
stale-issue-message: "This issue will be closed when we don't get a reply within 7 days."
|
||||
stale-pr-message: "This PR will be closed when we don't get a reply within 7 days."
|
||||
labels-to-remove-when-unstale: 'Needs: Reply'
|
||||
close-issue-label: "Close reason: No reply"
|
||||
close-pr-label: "Close reason: No reply"
|
||||
close-issue-message: "This issue was closed because we didn't get a reply for 14 days."
|
||||
close-pr-message: "This PR was closed because we didn't get a reply for 14 days."
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
#!/bin/python
|
||||
from xml.dom import minidom
|
||||
import os.path
|
||||
import glob
|
||||
from pathlib import Path
|
||||
|
||||
if os.path.isfile('app/build/reports/lint-results-playDebug.xml'):
|
||||
dom = minidom.parse('app/build/reports/lint-results-playDebug.xml')
|
||||
issues = dom.getElementsByTagName('issue')
|
||||
for issue in issues:
|
||||
locations = issue.getElementsByTagName('location')
|
||||
for location in locations:
|
||||
print(location.attributes['file'].value + ":" + location.attributes['line'].value + " " + issue.attributes['summary'].value)
|
||||
print("::error file=" + location.attributes['file'].value
|
||||
+ ",line=" + location.attributes['line'].value
|
||||
+ ",endLine=" + location.attributes['line'].value
|
||||
+ ",title=Lint::" + issue.attributes['summary'].value + ". " + issue.attributes['explanation'].value.replace('\n', ' '))
|
||||
print()
|
||||
|
||||
if os.path.isfile('build/reports/checkstyle/checkstyle.xml'):
|
||||
dom = minidom.parse('build/reports/checkstyle/checkstyle.xml')
|
||||
files = dom.getElementsByTagName('file')
|
||||
for f in files:
|
||||
errors = f.getElementsByTagName('error')
|
||||
for error in errors:
|
||||
print(f.attributes['name'].value + ":" + error.attributes['line'].value + " " + error.attributes['message'].value)
|
||||
print("::error file=" + f.attributes['name'].value
|
||||
+ ",line=" + error.attributes['line'].value
|
||||
+ ",endLine=" + error.attributes['line'].value
|
||||
+ ",title=Checkstyle::" + error.attributes['message'].value)
|
||||
print()
|
||||
|
||||
|
||||
for filename in glob.iglob('**/build/reports/spotbugs/*.xml', recursive=True):
|
||||
filenamePath = Path(filename)
|
||||
dom = minidom.parse(filename)
|
||||
instance = dom.getElementsByTagName('BugInstance')
|
||||
for inst in instance:
|
||||
lines = inst.getElementsByTagName('SourceLine')
|
||||
longMessage = inst.getElementsByTagName('LongMessage')[0].firstChild.nodeValue
|
||||
for line in lines:
|
||||
if "primary" in line.attributes:
|
||||
print(line.attributes['sourcepath'].value + ": " + longMessage)
|
||||
print("::error file=" + str(filenamePath.parent.parent.parent.parent.absolute()) + "/src/main/java/" + line.attributes['sourcepath'].value
|
||||
+ ",line=" + line.attributes['start'].value
|
||||
+ ",endLine=" + line.attributes['end'].value
|
||||
+ ",title=SpotBugs::" + longMessage.replace('\n', ' '))
|
||||
print()
|
|
@ -1,11 +1,10 @@
|
|||
#!/bin/zsh
|
||||
#!/bin/bash
|
||||
|
||||
set -o pipefail
|
||||
|
||||
runTests() {
|
||||
./gradlew connectedPlayDebugAndroidTest connectedDebugAndroidTest \
|
||||
-Pandroid.testInstrumentationRunnerArguments.notAnnotation=de.test.antennapod.IgnoreOnCi \
|
||||
| grep -v "V/InstrumentationResultParser: INSTRUMENTATION_STATUS"
|
||||
-Pandroid.testInstrumentationRunnerArguments.notAnnotation=de.test.antennapod.IgnoreOnCi
|
||||
}
|
||||
|
||||
# Retry tests to make them less flaky
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
[submodule "app/src/main/play"]
|
||||
path = app/src/main/play
|
||||
url = https://github.com/AntennaPod/StoreMetadata.git
|
||||
branch = main
|
|
@ -57,6 +57,7 @@ trans.de = app/src/main/play/listings/de-DE/full-description.txt
|
|||
trans.el = app/src/main/play/listings/el-GR/full-description.txt
|
||||
trans.es = app/src/main/play/listings/es-ES/full-description.txt
|
||||
trans.et = app/src/main/play/listings/et/full-description.txt
|
||||
trans.eu = app/src/main/play/listings/eu-ES/full-description.txt
|
||||
trans.fa = app/src/main/play/listings/fa/full-description.txt
|
||||
trans.fi = app/src/main/play/listings/fi-FI/full-description.txt
|
||||
trans.fr = app/src/main/play/listings/fr-FR/full-description.txt
|
||||
|
@ -98,6 +99,7 @@ trans.de = app/src/main/play/listings/de-DE/short-description.txt
|
|||
trans.el = app/src/main/play/listings/el-GR/short-description.txt
|
||||
trans.es = app/src/main/play/listings/es-ES/short-description.txt
|
||||
trans.et = app/src/main/play/listings/et/short-description.txt
|
||||
trans.eu = app/src/main/play/listings/eu-ES/short-description.txt
|
||||
trans.fa = app/src/main/play/listings/fa/short-description.txt
|
||||
trans.fi = app/src/main/play/listings/fi-FI/short-description.txt
|
||||
trans.fr = app/src/main/play/listings/fr-FR/short-description.txt
|
||||
|
|
|
@ -28,25 +28,41 @@ If you would like to translate the app into another language or improve an exist
|
|||
|
||||
Submit a pull request
|
||||
---------------------
|
||||
- If you want to work on a feature that has been requested or fix a bug that has been reported on the "issues" page, add a comment to it so that other people know that you are working on it.
|
||||
- Fork the repository.
|
||||
- Almost all changes of AntennaPod are done on the `develop` branch. If a new version of AntennaPod is released, the `develop` branch is merged into `master`. As a result, the `master` branch probably doesn't contain the latest changes when you are reading this. Please make sure that you are branching from `develop`! Otherwise, there might be a lot of merge-conflicts when merging your changes into `develop` and therefore it might take longer to review your pull-request. Exceptions are urgent issues that need to be fixed in the production version.
|
||||
- If your pull request fixes a bug that has been reported or implements a feature that has been requested in another issue, try to mention it in the message, so that it can be closed once your pull request has been merged. If you use special keywords in the [commit comment](https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue) or [pull request text](https://github.blog/2013-05-14-closing-issues-via-pull-requests/), GitHub will close the issue(s) automatically.
|
||||
- If possible, add unit tests for your pull request and make sure that they pass.
|
||||
- Please do not upgrade dependencies or build tools unless you have a good reason for it. Doing so can easily introduce bugs that are hard to track down.
|
||||
- If you plan to do a change that touches many files (10+), please ask beforehand. This usually causes merge conflicts for other developers.
|
||||
- Please follow our code style. You can use Checkstyle within Android Studio using our [configuration file](https://github.com/AntennaPod/AntennaPod/blob/develop/config/checkstyle/checkstyle-new-code.xml).
|
||||
- Please only change the English string resources. Translations are handled on [Transifex](https://www.transifex.com/antennapod/antennapod/).
|
||||
- Before you work on the code
|
||||
- Make sure that there is an issue *without* the `Needs: Triage` or `Needs: Decision` label for the feature you want to implement or bug you want to fix.
|
||||
- Add a comment to the issue so that other people know that you are working on it.
|
||||
- You don't need to ask for permission to work on something, just indicate that you are doing so.
|
||||
- If you want to discuss the approach to take, feel free to ask in the issue or join a [community call](https://antennapod.org/events/community-meeting).
|
||||
- Fork the repository
|
||||
- Create a new branch for your contribution
|
||||
- This makes opening possible additional pull requests easier.
|
||||
- As a base, use the `develop` branch.
|
||||
- Almost all changes of AntennaPod are done on the `develop` branch. If a new version of AntennaPod is released, the `develop` branch is merged into `master`. As a result, the `master` branch probably doesn't contain the latest changes when you are reading this. Otherwise, there might be a lot of merge-conflicts when merging your changes into `develop` and therefore it might take longer to review your pull-request.
|
||||
- Get coding :)
|
||||
- If possible, add unit tests for your pull request and make sure that they pass.
|
||||
- Please do not upgrade dependencies or build tools unless you have a good reason for it. Doing so can easily introduce bugs that are hard to track down.
|
||||
- Please follow our code style. You can use Checkstyle within Android Studio using our [configuration file](https://github.com/AntennaPod/AntennaPod/blob/develop/config/checkstyle/checkstyle.xml).
|
||||
- To check the code style locally, run `./gradlew checkstyle spotbugsPlayDebug spotbugsDebug :app:lintPlayDebug`
|
||||
- Please only change the English string resources. Translations are handled on [Transifex](https://www.transifex.com/antennapod/antennapod/).
|
||||
- Open the PR
|
||||
- Mention the corresponding issue in the pull request text, so that it can be closed once your pull request has been merged. If you use [special keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue), GitHub will close the issue(s) automatically.
|
||||
|
||||
|
||||
Building From Source
|
||||
--------------------------
|
||||
1. Fork this repository
|
||||
1. Download Android Studio
|
||||
1. In Android Studio
|
||||
1. File » New » Project from version control
|
||||
2. Enter the remote url of the forked repo
|
||||
2. Wait for a long time until all progress bars go away
|
||||
3. Press the Play button
|
||||
1. Download AntennaPod
|
||||
1. Option A: Using the git command line (recommended)
|
||||
1. Use `git clone <url>` with the remote url of your forked repo.
|
||||
The AntennaPod repo contains a large submodule with app store metadata like screenshots.
|
||||
You **do not need that** for normal development.
|
||||
1. In Android Studio: File » New » Project from existing sources
|
||||
1. Option B: From Android Studio
|
||||
1. File » New » Project from version control
|
||||
1. Enter the remote url of the forked repo
|
||||
1. Wait for a long time until all progress bars go away
|
||||
1. Press the Play button
|
||||
|
||||
Testing and Verifying
|
||||
--------------------------
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -8,14 +8,14 @@ This is the official repository of AntennaPod, the easy-to-use, flexible and ope
|
|||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid"
|
||||
height="70">](https://f-droid.org/app/de.danoeh.antennapod)
|
||||
|
||||
<img src="https://raw.githubusercontent.com/AntennaPod/AntennaPod/develop/app/src/main/play/listings/en-US/graphics/phone-screenshots/00.png" alt="Screenshot 0" height="200"> <img src="https://raw.githubusercontent.com/AntennaPod/AntennaPod/develop/app/src/main/play/listings/en-US/graphics/phone-screenshots/01.png" alt="Screenshot 1" height="200"> <img src="https://raw.githubusercontent.com/AntennaPod/AntennaPod/develop/app/src/main/play/listings/en-US/graphics/phone-screenshots/02.png" alt="Screenshot 2" height="200"> <img src="https://raw.githubusercontent.com/AntennaPod/AntennaPod/develop/app/src/main/play/listings/en-US/graphics/phone-screenshots/03.png" alt="Screenshot 3" height="200"> <img src="https://raw.githubusercontent.com/AntennaPod/AntennaPod/develop/app/src/main/play/listings/en-US/graphics/phone-screenshots/04.png" alt="Screenshot 4" height="200"> <img src="https://raw.githubusercontent.com/AntennaPod/AntennaPod/develop/app/src/main/play/listings/en-US/graphics/phone-screenshots/05.png" alt="Screenshot 5" height="200">
|
||||
|
||||
<img src="https://raw.githubusercontent.com/AntennaPod/StoreMetadata/main/listings/en-US/graphics/phone-screenshots/00.png" alt="Screenshot 0" height="200"> <img src="https://raw.githubusercontent.com/AntennaPod/StoreMetadata/main/listings/en-US/graphics/phone-screenshots/01.png" alt="Screenshot 1" height="200"> <img src="https://raw.githubusercontent.com/AntennaPod/StoreMetadata/main/listings/en-US/graphics/phone-screenshots/02.png" alt="Screenshot 2" height="200"> <img src="https://raw.githubusercontent.com/AntennaPod/StoreMetadata/main/listings/en-US/graphics/phone-screenshots/03.png" alt="Screenshot 3" height="200"> <img src="https://raw.githubusercontent.com/AntennaPod/StoreMetadata/main/listings/en-US/graphics/phone-screenshots/04.png" alt="Screenshot 4" height="200"> <img src="https://raw.githubusercontent.com/AntennaPod/StoreMetadata/main/listings/en-US/graphics/phone-screenshots/05.png" alt="Screenshot 5" height="200">
|
||||
|
||||
|
||||
## Feedback
|
||||
You can use the [AntennaPod Forum](https://forum.antennapod.org/) for discussions about the app or just podcasting in general.
|
||||
|
||||
Bug reports and feature requests can be submitted [here](https://github.com/AntennaPod/AntennaPod/issues) (please read the [instructions](https://github.com/AntennaPod/AntennaPod/blob/master/CONTRIBUTING.md) on how to report a bug and how to submit a feature request first!).
|
||||
Bug reports and feature requests can be submitted [here](https://github.com/AntennaPod/AntennaPod/issues) (please read the [instructions](https://github.com/AntennaPod/AntennaPod/blob/develop/CONTRIBUTING.md) on how to report a bug and how to submit a feature request first!).
|
||||
|
||||
We also hold regular community calls to discuss anything AntennaPod-related. [Come join the next call](https://forum.antennapod.org/t/monthly-community-call/1869)!
|
||||
|
||||
|
@ -27,10 +27,11 @@ AntennaPod has many users and we don't want them to run into trouble when we add
|
|||
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/).
|
||||
|
||||
|
||||
## Building AntennaPod
|
||||
|
||||
Information on how to build AntennaPod can be found in the [wiki](https://github.com/AntennaPod/AntennaPod/wiki/Building-AntennaPod).
|
||||
You can build AntennaPod just like any other Android project. Refer to the [instructions](https://github.com/AntennaPod/AntennaPod/blob/develop/CONTRIBUTING.md) for more details.
|
||||
|
||||
|
|
|
@ -1,29 +1,19 @@
|
|||
plugins {
|
||||
id('com.android.application')
|
||||
id('com.github.triplet.play') version '3.7.0-agp4.2' apply false
|
||||
id('com.github.triplet.play') version '3.9.0' apply false
|
||||
}
|
||||
apply from: "../common.gradle"
|
||||
apply from: "../playFlavor.gradle"
|
||||
|
||||
android {
|
||||
namespace "de.danoeh.antennapod"
|
||||
|
||||
defaultConfig {
|
||||
// Version code schema:
|
||||
// "1.2.3-beta4" -> 1020304
|
||||
// "1.2.3" -> 1020395
|
||||
versionCode 2070195
|
||||
versionName "2.7.1"
|
||||
|
||||
def commit = ""
|
||||
try {
|
||||
def hashStdOut = new ByteArrayOutputStream()
|
||||
exec {
|
||||
commandLine "git", "rev-parse", "--short", "HEAD"
|
||||
standardOutput = hashStdOut
|
||||
}
|
||||
commit = hashStdOut.toString().trim()
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
buildConfigField "String", "COMMIT_HASH", ('"' + (commit.isEmpty() ? "Unknown commit" : commit) + '"')
|
||||
versionCode 3040095
|
||||
versionName "3.4.0"
|
||||
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
|
@ -56,40 +46,51 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'ObsoleteLintCustomCheck', 'CheckResult', 'UnusedAttribute', 'BatteryLife', 'InflateParams',
|
||||
'RestrictedApi', 'TrustAllX509TrustManager', 'ExportedReceiver', 'AllowBackup', 'VectorDrawableCompat',
|
||||
'StaticFieldLeak', 'UseCompoundDrawables', 'NestedWeights', 'Overdraw', 'UselessParent', 'TextFields',
|
||||
'AlwaysShowAction', 'Autofill', 'ClickableViewAccessibility', 'ContentDescription',
|
||||
lint {
|
||||
disable 'CheckResult', 'MissingMediaBrowserServiceIntentFilter', 'UnusedAttribute', 'InflateParams',
|
||||
'RestrictedApi', 'ExportedReceiver', 'NotifyDataSetChanged', 'UseCompoundDrawables', 'NestedWeights',
|
||||
'Overdraw', 'UselessParent', 'TextFields', 'AlwaysShowAction', 'Autofill', 'ClickableViewAccessibility',
|
||||
'KeyboardInaccessibleWidget', 'LabelFor', 'SetTextI18n', 'HardcodedText', 'RelativeOverlap',
|
||||
'RtlCompat', 'RtlHardcoded', 'MissingMediaBrowserServiceIntentFilter', 'VectorPath',
|
||||
'InvalidPeriodicWorkRequestInterval'
|
||||
'RtlHardcoded', 'RtlEnabled', 'ContentDescription'
|
||||
}
|
||||
|
||||
aaptOptions {
|
||||
additionalParameters "--no-version-vectors"
|
||||
}
|
||||
|
||||
dexOptions {
|
||||
jumboMode true
|
||||
androidResources {
|
||||
additionalParameters += ["--no-version-vectors"]
|
||||
generateLocaleConfig true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(":core")
|
||||
implementation project(":event")
|
||||
implementation project(':model')
|
||||
implementation project(':net:common')
|
||||
implementation project(':net:discovery')
|
||||
implementation project(':net:sync:gpoddernet')
|
||||
implementation project(':net:sync:model')
|
||||
implementation project(':net:download:service-interface')
|
||||
implementation project(':net:download:service')
|
||||
implementation project(':net:ssl')
|
||||
implementation project(':net:sync:service')
|
||||
implementation project(':parser:feed')
|
||||
implementation project(':parser:transcript')
|
||||
implementation project(':playback:base')
|
||||
implementation project(':playback:cast')
|
||||
implementation project(':storage:database')
|
||||
implementation project(':storage:importexport')
|
||||
implementation project(':storage:preferences')
|
||||
implementation project(':ui:app-start-intent')
|
||||
implementation project(':ui:common')
|
||||
implementation project(':ui:discovery')
|
||||
implementation project(':ui:echo')
|
||||
implementation project(':ui:episodes')
|
||||
implementation project(':ui:glide')
|
||||
implementation project(':ui:i18n')
|
||||
implementation project(':ui:notifications')
|
||||
implementation project(':ui:widget')
|
||||
implementation project(':ui:preferences')
|
||||
implementation project(':ui:statistics')
|
||||
implementation project(':net:sync:service-interface')
|
||||
implementation project(':playback:service')
|
||||
implementation project(':ui:chapters')
|
||||
implementation project(':ui:transcript')
|
||||
|
||||
annotationProcessor "androidx.annotation:annotation:$annotationVersion"
|
||||
implementation "androidx.appcompat:appcompat:$appcompatVersion"
|
||||
|
@ -98,7 +99,6 @@ dependencies {
|
|||
implementation "androidx.fragment:fragment:$fragmentVersion"
|
||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
implementation "androidx.media:media:$mediaVersion"
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation "androidx.palette:palette:$paletteVersion"
|
||||
implementation "androidx.preference:preference:$preferenceVersion"
|
||||
implementation "androidx.recyclerview:recyclerview:$recyclerViewVersion"
|
||||
|
@ -110,31 +110,23 @@ dependencies {
|
|||
implementation "commons-io:commons-io:$commonsioVersion"
|
||||
implementation "org.jsoup:jsoup:$jsoupVersion"
|
||||
implementation "com.github.bumptech.glide:glide:$glideVersion"
|
||||
annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"
|
||||
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
|
||||
implementation "com.squareup.okhttp3:okhttp-urlconnection:$okhttpVersion"
|
||||
implementation "com.squareup.okio:okio:$okioVersion"
|
||||
implementation "org.greenrobot:eventbus:$eventbusVersion"
|
||||
annotationProcessor "org.greenrobot:eventbus-annotation-processor:$eventbusVersion"
|
||||
implementation "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion"
|
||||
implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
|
||||
|
||||
implementation "com.joanzapata.iconify:android-iconify-fontawesome:$iconifyVersion"
|
||||
implementation "com.joanzapata.iconify:android-iconify-material:$iconifyVersion"
|
||||
implementation 'com.leinardi.android:speed-dial:3.2.0'
|
||||
implementation "com.github.AntennaPod:AntennaPod-AudioPlayer:$audioPlayerVersion"
|
||||
implementation 'com.github.ByteHamster:SearchPreference:v2.0.0'
|
||||
implementation 'com.github.skydoves:balloon:1.4.0'
|
||||
implementation 'com.github.ByteHamster:SearchPreference:v2.5.0'
|
||||
implementation 'com.github.skydoves:balloon:1.5.3'
|
||||
implementation 'com.github.xabaras:RecyclerViewSwipeDecorator:1.3'
|
||||
implementation 'com.annimon:stream:1.2.2'
|
||||
|
||||
// Non-free dependencies:
|
||||
playImplementation 'com.google.android.play:core:1.8.0'
|
||||
compileOnly "com.google.android.wearable:wearable:$wearableSupportVersion"
|
||||
testImplementation "androidx.test:core:$testCoreVersion"
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
testImplementation "org.robolectric:robolectric:$robolectricVersion"
|
||||
|
||||
androidTestImplementation "org.awaitility:awaitility:$awaitilityVersion"
|
||||
androidTestImplementation 'com.nanohttpd:nanohttpd:2.1.1'
|
||||
androidTestImplementation "com.jayway.android.robotium:robotium-solo:$robotiumSoloVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
|
||||
|
@ -150,13 +142,3 @@ if (project.hasProperty("antennaPodPlayPublisherCredentials")) {
|
|||
serviceAccountCredentials.set(file(antennaPodPlayPublisherCredentials))
|
||||
}
|
||||
}
|
||||
|
||||
task copyLicense(type: Copy) {
|
||||
from "../LICENSE"
|
||||
into "src/main/assets/"
|
||||
rename { String fileName ->
|
||||
fileName + ".txt"
|
||||
}
|
||||
}
|
||||
|
||||
preBuild.dependsOn copyLicense
|
||||
|
|
|
@ -28,10 +28,6 @@
|
|||
public *;
|
||||
}
|
||||
|
||||
# for okhttp
|
||||
-dontwarn okhttp3.**
|
||||
-dontwarn okio.**
|
||||
|
||||
# android-iconify
|
||||
-keep class com.joanzapata.** { *; }
|
||||
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
package de.danoeh.antennapod.core.service.download;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Consumer;
|
||||
import de.danoeh.antennapod.model.download.DownloadStatus;
|
||||
|
||||
public class StubDownloader extends Downloader {
|
||||
|
||||
private final long downloadTime;
|
||||
|
||||
@NonNull
|
||||
private final Consumer<DownloadStatus> onDownloadComplete;
|
||||
|
||||
public StubDownloader(@NonNull DownloadRequest request, long downloadTime, @NonNull Consumer<DownloadStatus> onDownloadComplete) {
|
||||
super(request);
|
||||
this.downloadTime = downloadTime;
|
||||
this.onDownloadComplete = onDownloadComplete;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void download() {
|
||||
try {
|
||||
Thread.sleep(downloadTime);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
onDownloadComplete.accept(result);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DownloadRequest getDownloadRequest() {
|
||||
return super.getDownloadRequest();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DownloadStatus getResult() {
|
||||
return super.getResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return super.isFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
super.cancel();
|
||||
result.setCancelled();
|
||||
}
|
||||
}
|
|
@ -18,16 +18,14 @@ import androidx.test.espresso.util.HumanReadables;
|
|||
import androidx.test.espresso.util.TreeIterables;
|
||||
import android.view.View;
|
||||
|
||||
import de.danoeh.antennapod.playback.service.PlaybackService;
|
||||
import de.danoeh.antennapod.storage.database.PodDBAdapter;
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
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.dialog.RatingDialog;
|
||||
import de.danoeh.antennapod.fragment.NavDrawerFragment;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.ui.screen.drawer.NavDrawerFragment;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.awaitility.core.ConditionTimeoutException;
|
||||
import org.hamcrest.Matcher;
|
||||
|
@ -38,6 +36,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.doesNotExist;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
|
@ -46,6 +45,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription
|
|||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
|
||||
public class EspressoTestUtils {
|
||||
/**
|
||||
|
@ -145,6 +145,21 @@ public class EspressoTestUtils {
|
|||
};
|
||||
}
|
||||
|
||||
public static void waitForViewToDisappear(Matcher<? super View> matcher, long maxWaitingTimeMs) {
|
||||
long endTime = System.currentTimeMillis() + maxWaitingTimeMs;
|
||||
while (System.currentTimeMillis() <= endTime) {
|
||||
try {
|
||||
onView(allOf(matcher, isDisplayed())).check(matches(not(doesNotExist())));
|
||||
Thread.sleep(100);
|
||||
} catch (NoMatchingViewException ex) {
|
||||
return; // view has disappeared
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("timeout exceeded"); // or whatever exception you want
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all app databases.
|
||||
*/
|
||||
|
@ -167,17 +182,18 @@ public class EspressoTestUtils {
|
|||
.edit()
|
||||
.putString(UserPreferences.PREF_UPDATE_INTERVAL, "0")
|
||||
.commit();
|
||||
|
||||
RatingDialog.init(InstrumentationRegistry.getInstrumentation().getTargetContext());
|
||||
RatingDialog.saveRated();
|
||||
}
|
||||
|
||||
public static void setLastNavFragment(String tag) {
|
||||
public static void setLaunchScreen(String tag) {
|
||||
InstrumentationRegistry.getInstrumentation().getTargetContext()
|
||||
.getSharedPreferences(NavDrawerFragment.PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putString(NavDrawerFragment.PREF_LAST_FRAGMENT_TAG, tag)
|
||||
.commit();
|
||||
PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getInstrumentation().getTargetContext())
|
||||
.edit()
|
||||
.putString(UserPreferences.PREF_DEFAULT_PAGE, UserPreferences.DEFAULT_PAGE_REMEMBER)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void clearDatabase() {
|
||||
|
@ -220,21 +236,6 @@ public class EspressoTestUtils {
|
|||
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
|
||||
}
|
||||
|
||||
public static void tryKillDownloadService() {
|
||||
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
context.stopService(new Intent(context, DownloadService.class));
|
||||
try {
|
||||
// Android has no reliable way to stop a service instantly.
|
||||
// Calling stopSelf marks allows the system to destroy the service but the actual call
|
||||
// to onDestroy takes until the next GC of the system, which we can not influence.
|
||||
// Try to wait for the service at least a bit.
|
||||
Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> !DownloadService.isRunning);
|
||||
} catch (ConditionTimeoutException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
|
||||
}
|
||||
|
||||
public static Matcher<View> actionBarOverflow() {
|
||||
return allOf(isDisplayed(), withContentDescription("More options"));
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.fragment.AllEpisodesFragment;
|
||||
import de.danoeh.antennapod.ui.screen.AllEpisodesFragment;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
import de.test.antennapod.ui.UITestUtils;
|
||||
import org.hamcrest.Matcher;
|
||||
|
@ -48,7 +48,7 @@ public class ShareDialogTest {
|
|||
context = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
EspressoTestUtils.clearPreferences();
|
||||
EspressoTestUtils.clearDatabase();
|
||||
EspressoTestUtils.setLastNavFragment(AllEpisodesFragment.TAG);
|
||||
EspressoTestUtils.setLaunchScreen(AllEpisodesFragment.TAG);
|
||||
UITestUtils uiTestUtils = new UITestUtils(context);
|
||||
uiTestUtils.setup();
|
||||
uiTestUtils.addLocalFeedData(true);
|
||||
|
@ -59,7 +59,7 @@ public class ShareDialogTest {
|
|||
onDrawerItem(withText(R.string.episodes_label)).perform(click());
|
||||
|
||||
Matcher<View> allEpisodesMatcher;
|
||||
allEpisodesMatcher = Matchers.allOf(withId(android.R.id.list), isDisplayed(), hasMinimumChildCount(2));
|
||||
allEpisodesMatcher = Matchers.allOf(withId(R.id.recyclerView), isDisplayed(), hasMinimumChildCount(2));
|
||||
onView(isRoot()).perform(waitForView(allEpisodesMatcher, 1000));
|
||||
onView(allEpisodesMatcher).perform(actionOnItemAtPosition(0, click()));
|
||||
onView(first(EspressoTestUtils.actionBarOverflow())).perform(click());
|
||||
|
|
|
@ -3,45 +3,39 @@ package de.test.antennapod.playback;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.playback.service.PlaybackController;
|
||||
import de.danoeh.antennapod.storage.preferences.PlaybackPreferences;
|
||||
import de.danoeh.antennapod.storage.database.DBReader;
|
||||
import de.danoeh.antennapod.storage.database.DBWriter;
|
||||
import de.danoeh.antennapod.storage.database.LongList;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedItemFilter;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.model.feed.SortOrder;
|
||||
import de.danoeh.antennapod.playback.base.PlayerStatus;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.ui.appstartintent.MediaButtonStarter;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
import de.test.antennapod.IgnoreOnCi;
|
||||
import de.test.antennapod.ui.UITestUtils;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
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.IntentUtils;
|
||||
import de.danoeh.antennapod.core.util.LongList;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
import de.test.antennapod.IgnoreOnCi;
|
||||
import de.test.antennapod.ui.UITestUtils;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
|
||||
|
@ -68,31 +62,20 @@ import static org.junit.Assert.assertTrue;
|
|||
*/
|
||||
@LargeTest
|
||||
@IgnoreOnCi
|
||||
@RunWith(Parameterized.class)
|
||||
public class PlaybackTest {
|
||||
@Rule
|
||||
public ActivityTestRule<MainActivity> activityTestRule = new ActivityTestRule<>(MainActivity.class, false, false);
|
||||
|
||||
@Parameterized.Parameter(value = 0)
|
||||
public String playerToUse;
|
||||
private UITestUtils uiTestUtils;
|
||||
protected Context context;
|
||||
private PlaybackController controller;
|
||||
|
||||
@Parameterized.Parameters(name = "{0}")
|
||||
public static Collection<Object[]> initParameters() {
|
||||
return Arrays.asList(new Object[][] { { "exoplayer" }, { "builtin" }, { "sonic" } });
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
context = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
EspressoTestUtils.clearPreferences();
|
||||
EspressoTestUtils.clearDatabase();
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs.edit().putString(UserPreferences.PREF_MEDIA_PLAYER, playerToUse).apply();
|
||||
|
||||
uiTestUtils = new UITestUtils(context);
|
||||
uiTestUtils.setup();
|
||||
}
|
||||
|
@ -241,18 +224,19 @@ public class PlaybackTest {
|
|||
}
|
||||
|
||||
private void skipEpisode() {
|
||||
IntentUtils.sendLocalBroadcast(context, PlaybackService.ACTION_SKIP_CURRENT_EPISODE);
|
||||
context.sendBroadcast(MediaButtonStarter.createIntent(context, KeyEvent.KEYCODE_MEDIA_NEXT));
|
||||
}
|
||||
|
||||
protected void pauseEpisode() {
|
||||
IntentUtils.sendLocalBroadcast(context, PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE);
|
||||
context.sendBroadcast(MediaButtonStarter.createIntent(context, KeyEvent.KEYCODE_MEDIA_PAUSE));
|
||||
}
|
||||
|
||||
protected void startLocalPlayback() {
|
||||
openNavDrawer();
|
||||
onDrawerItem(withText(R.string.episodes_label)).perform(click());
|
||||
|
||||
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10, FeedItemFilter.unfiltered());
|
||||
final List<FeedItem> episodes = DBReader.getEpisodes(0, 10,
|
||||
FeedItemFilter.unfiltered(), SortOrder.DATE_NEW_OLD);
|
||||
Matcher<View> allEpisodesMatcher = allOf(withId(R.id.recyclerView), isDisplayed(), hasMinimumChildCount(2));
|
||||
onView(isRoot()).perform(waitForView(allEpisodesMatcher, 1000));
|
||||
onView(allEpisodesMatcher).perform(actionOnItemAtPosition(0, clickChildViewWithId(R.id.secondaryActionButton)));
|
||||
|
@ -287,7 +271,8 @@ public class PlaybackTest {
|
|||
uiTestUtils.addLocalFeedData(true);
|
||||
DBWriter.clearQueue().get();
|
||||
activityTestRule.launchActivity(new Intent());
|
||||
final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10, FeedItemFilter.unfiltered());
|
||||
final List<FeedItem> episodes = DBReader.getEpisodes(0, 10,
|
||||
FeedItemFilter.unfiltered(), SortOrder.DATE_NEW_OLD);
|
||||
|
||||
startLocalPlayback();
|
||||
FeedMedia media = episodes.get(0).getMedia();
|
||||
|
|
|
@ -1,214 +0,0 @@
|
|||
package de.test.antennapod.service.download;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Consumer;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import de.danoeh.antennapod.core.service.download.DownloadRequestCreator;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.awaitility.core.ConditionTimeoutException;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadRequest;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadService;
|
||||
import de.danoeh.antennapod.model.download.DownloadStatus;
|
||||
import de.danoeh.antennapod.core.service.download.Downloader;
|
||||
import de.danoeh.antennapod.core.service.download.DownloaderFactory;
|
||||
import de.danoeh.antennapod.core.service.download.StubDownloader;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
|
||||
import static de.test.antennapod.util.event.DownloadEventListener.withDownloadEventListener;
|
||||
import static de.test.antennapod.util.event.FeedItemEventListener.withFeedItemEventListener;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* @see HttpDownloaderTest for the test of actual download (and saving the file).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class DownloadServiceTest {
|
||||
private FeedMedia testMedia11 = null;
|
||||
|
||||
private DownloaderFactory origFactory = null;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
EspressoTestUtils.clearDatabase();
|
||||
EspressoTestUtils.clearPreferences();
|
||||
origFactory = DownloadService.getDownloaderFactory();
|
||||
Feed testFeed = setUpTestFeeds();
|
||||
testMedia11 = testFeed.getItemAtIndex(0).getMedia();
|
||||
}
|
||||
|
||||
private Feed setUpTestFeeds() throws Exception {
|
||||
// To avoid complication in case of test failures, leaving behind orphaned
|
||||
// media files: add a timestamp so that each test run will have its own directory for media files.
|
||||
Feed feed = new Feed("url", null, "Test Feed title 1 " + System.currentTimeMillis());
|
||||
List<FeedItem> items = new ArrayList<>();
|
||||
feed.setItems(items);
|
||||
FeedItem item1 = new FeedItem(0, "Item 1-1", "Item 1-1", "url", new Date(), FeedItem.NEW, feed);
|
||||
items.add(item1);
|
||||
FeedMedia media1 = new FeedMedia(0, item1, 123, 1, 1, "audio/mp3", null, "http://example.com/episode.mp3", false, null, 0, 0);
|
||||
item1.setMedia(media1);
|
||||
|
||||
DBWriter.setFeedItem(item1).get();
|
||||
return feed;
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
DownloadService.setDownloaderFactory(origFactory);
|
||||
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
DownloadService.cancelAll(context);
|
||||
context.stopService(new Intent(context, DownloadService.class));
|
||||
EspressoTestUtils.tryKillDownloadService();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEventsGeneratedCaseMediaDownloadSuccess_noEnqueue() throws Exception {
|
||||
doTestEventsGeneratedCaseMediaDownloadSuccess(false, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEventsGeneratedCaseMediaDownloadSuccess_withEnqueue() throws Exception {
|
||||
// enqueue itself generates additional FeedItem event
|
||||
doTestEventsGeneratedCaseMediaDownloadSuccess(true, 2);
|
||||
}
|
||||
|
||||
private void doTestEventsGeneratedCaseMediaDownloadSuccess(boolean enqueueDownloaded,
|
||||
int numEventsExpected)
|
||||
throws Exception {
|
||||
// create a stub download that returns successful
|
||||
//
|
||||
// OPEN: Ideally, I'd like the download time long enough so that multiple in-progress DownloadEvents
|
||||
// are generated (to simulate typical download), but it'll make download time quite long (1-2 seconds)
|
||||
// to do so
|
||||
DownloadService.setDownloaderFactory(new StubDownloaderFactory(50, DownloadStatus::setSuccessful));
|
||||
|
||||
UserPreferences.setEnqueueDownloadedEpisodes(enqueueDownloaded);
|
||||
withFeedItemEventListener(feedItemEventListener -> {
|
||||
try {
|
||||
assertEquals(0, feedItemEventListener.getEvents().size());
|
||||
assertFalse("The media in test should not yet been downloaded",
|
||||
DBReader.getFeedMedia(testMedia11.getId()).isDownloaded());
|
||||
|
||||
DownloadService.download(InstrumentationRegistry.getInstrumentation().getTargetContext(), false,
|
||||
DownloadRequestCreator.create(testMedia11).build());
|
||||
Awaitility.await()
|
||||
.atMost(5000, TimeUnit.MILLISECONDS)
|
||||
.until(() -> feedItemEventListener.getEvents().size() >= numEventsExpected);
|
||||
assertTrue("After media download has completed, FeedMedia object in db should indicate so.",
|
||||
DBReader.getFeedMedia(testMedia11.getId()).isDownloaded());
|
||||
assertEquals("The FeedItem should have been " + (enqueueDownloaded ? "" : "not ") + "enqueued",
|
||||
enqueueDownloaded,
|
||||
DBReader.getQueueIDList().contains(testMedia11.getItem().getId()));
|
||||
} catch (ConditionTimeoutException cte) {
|
||||
fail("The expected FeedItemEvent (for media download complete) has not been posted. "
|
||||
+ cte.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelDownload_UndoEnqueue_Normal() throws Exception {
|
||||
doTestCancelDownload_UndoEnqueue(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelDownload_UndoEnqueue_AlreadyInQueue() throws Exception {
|
||||
doTestCancelDownload_UndoEnqueue(true);
|
||||
}
|
||||
|
||||
private void doTestCancelDownload_UndoEnqueue(boolean itemAlreadyInQueue) throws Exception {
|
||||
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
// let download take longer to ensure the test can cancel the download in time
|
||||
DownloadService.setDownloaderFactory(
|
||||
new StubDownloaderFactory(30000, DownloadStatus::setSuccessful));
|
||||
UserPreferences.setEnqueueDownloadedEpisodes(true);
|
||||
UserPreferences.setEnableAutodownload(false);
|
||||
|
||||
final long item1Id = testMedia11.getItem().getId();
|
||||
if (itemAlreadyInQueue) {
|
||||
// simulate item already in queue condition
|
||||
DBWriter.addQueueItem(context, false, item1Id).get();
|
||||
assertTrue(DBReader.getQueueIDList().contains(item1Id));
|
||||
} else {
|
||||
assertFalse(DBReader.getQueueIDList().contains(item1Id));
|
||||
}
|
||||
|
||||
withFeedItemEventListener(feedItemEventListener -> {
|
||||
DownloadService.download(InstrumentationRegistry.getInstrumentation().getTargetContext(), false,
|
||||
DownloadRequestCreator.create(testMedia11).build());
|
||||
withDownloadEventListener(downloadEventListener ->
|
||||
Awaitility.await("download is actually running")
|
||||
.atMost(5000, TimeUnit.MILLISECONDS)
|
||||
.until(() -> downloadEventListener.getLatestEvent() != null
|
||||
&& downloadEventListener.getLatestEvent().update.mediaIds.length > 0
|
||||
&& downloadEventListener.getLatestEvent().update.mediaIds[0] == testMedia11.getId()));
|
||||
|
||||
if (itemAlreadyInQueue) {
|
||||
assertEquals("download service receives the request - no event is expected before cancel is issued",
|
||||
0, feedItemEventListener.getEvents().size());
|
||||
} else {
|
||||
Awaitility.await("item enqueue event")
|
||||
.atMost(2000, TimeUnit.MILLISECONDS)
|
||||
.until(() -> feedItemEventListener.getEvents().size() >= 1);
|
||||
}
|
||||
DownloadService.cancel(context, testMedia11.getDownload_url());
|
||||
final int totalNumEventsExpected = itemAlreadyInQueue ? 1 : 3;
|
||||
Awaitility.await("item dequeue event + download termination event")
|
||||
.atMost(2000, TimeUnit.MILLISECONDS)
|
||||
.until(() -> feedItemEventListener.getEvents().size() >= totalNumEventsExpected);
|
||||
assertFalse("The download should have been canceled",
|
||||
DBReader.getFeedMedia(testMedia11.getId()).isDownloaded());
|
||||
if (itemAlreadyInQueue) {
|
||||
assertTrue("The FeedItem should still be in the queue after the download is cancelled."
|
||||
+ " It's there before download.",
|
||||
DBReader.getQueueIDList().contains(item1Id));
|
||||
} else {
|
||||
assertFalse("The FeedItem should not be in the queue after the download is cancelled.",
|
||||
DBReader.getQueueIDList().contains(item1Id));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static class StubDownloaderFactory implements DownloaderFactory {
|
||||
private final long downloadTime;
|
||||
|
||||
@NonNull
|
||||
private final Consumer<DownloadStatus> onDownloadComplete;
|
||||
|
||||
StubDownloaderFactory(long downloadTime, @NonNull Consumer<DownloadStatus> onDownloadComplete) {
|
||||
this.downloadTime = downloadTime;
|
||||
this.onDownloadComplete = onDownloadComplete;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Downloader create(@NonNull DownloadRequest request) {
|
||||
return new StubDownloader(request, downloadTime, onDownloadComplete);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -7,12 +7,12 @@ import android.util.Log;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import de.danoeh.antennapod.model.feed.FeedFile;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadRequest;
|
||||
import de.danoeh.antennapod.model.download.DownloadStatus;
|
||||
import de.danoeh.antennapod.core.service.download.Downloader;
|
||||
import de.danoeh.antennapod.core.service.download.HttpDownloader;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.net.download.service.feed.remote.Downloader;
|
||||
import de.danoeh.antennapod.net.download.service.feed.remote.HttpDownloader;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.model.download.DownloadRequest;
|
||||
import de.danoeh.antennapod.model.download.DownloadResult;
|
||||
import de.danoeh.antennapod.model.download.DownloadError;
|
||||
import de.test.antennapod.util.service.download.HTTPBin;
|
||||
import org.junit.After;
|
||||
|
@ -60,32 +60,33 @@ public class HttpDownloaderTest {
|
|||
urlAuth = httpServer.getBaseUrl() + "/basic-auth/user/passwd";
|
||||
}
|
||||
|
||||
private FeedFileImpl setupFeedFile(String downloadUrl, String title, boolean deleteExisting) {
|
||||
FeedFileImpl feedfile = new FeedFileImpl(downloadUrl);
|
||||
private Feed setupFeedFile(String downloadUrl, String title, boolean deleteExisting) {
|
||||
Feed feedfile = new Feed(downloadUrl, "");
|
||||
String fileUrl = new File(destDir, title).getAbsolutePath();
|
||||
File file = new File(fileUrl);
|
||||
if (deleteExisting) {
|
||||
Log.d(TAG, "Deleting file: " + file.delete());
|
||||
}
|
||||
feedfile.setFile_url(fileUrl);
|
||||
feedfile.setLocalFileUrl(fileUrl);
|
||||
return feedfile;
|
||||
}
|
||||
|
||||
private Downloader download(String url, String title, boolean expectedResult) {
|
||||
return download(url, title, expectedResult, true, null, null, true);
|
||||
return download(url, title, expectedResult, true, null, null);
|
||||
}
|
||||
|
||||
private Downloader download(String url, String title, boolean expectedResult, boolean deleteExisting, String username, String password, boolean deleteOnFail) {
|
||||
FeedFile feedFile = setupFeedFile(url, title, deleteExisting);
|
||||
DownloadRequest request = new DownloadRequest(feedFile.getFile_url(), url, title, 0, feedFile.getTypeAsInt(), username, password, deleteOnFail, null, false);
|
||||
private Downloader download(String url, String title, boolean expectedResult, boolean deleteExisting,
|
||||
String username, String password) {
|
||||
Feed feedFile = setupFeedFile(url, title, deleteExisting);
|
||||
DownloadRequest request = new DownloadRequest(feedFile.getLocalFileUrl(), url, title, 0, Feed.FEEDFILETYPE_FEED,
|
||||
username, password, null, false);
|
||||
Downloader downloader = new HttpDownloader(request);
|
||||
downloader.call();
|
||||
DownloadStatus status = downloader.getResult();
|
||||
DownloadResult status = downloader.getResult();
|
||||
assertNotNull(status);
|
||||
assertEquals(expectedResult, status.isSuccessful());
|
||||
assertTrue(status.isDone());
|
||||
// the file should not exist if the download has failed and deleteExisting was true
|
||||
assertTrue(!deleteExisting || new File(feedFile.getFile_url()).exists() == expectedResult);
|
||||
assertTrue(!deleteExisting || new File(feedFile.getLocalFileUrl()).exists() == expectedResult);
|
||||
return downloader;
|
||||
}
|
||||
|
||||
|
@ -112,8 +113,9 @@ public class HttpDownloaderTest {
|
|||
@Test
|
||||
public void testCancel() {
|
||||
final String url = httpServer.getBaseUrl() + "/delay/3";
|
||||
FeedFileImpl feedFile = setupFeedFile(url, "delay", true);
|
||||
final Downloader downloader = new HttpDownloader(new DownloadRequest(feedFile.getFile_url(), url, "delay", 0, feedFile.getTypeAsInt(), null, null, true, null, false));
|
||||
Feed feedFile = setupFeedFile(url, "delay", true);
|
||||
final Downloader downloader = new HttpDownloader(new DownloadRequest(feedFile.getLocalFileUrl(),
|
||||
url, "delay", 0, Feed.FEEDFILETYPE_FEED, null, null, null, false));
|
||||
Thread t = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -127,16 +129,13 @@ public class HttpDownloaderTest {
|
|||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
DownloadStatus result = downloader.getResult();
|
||||
assertTrue(result.isDone());
|
||||
DownloadResult result = downloader.getResult();
|
||||
assertFalse(result.isSuccessful());
|
||||
assertTrue(result.isCancelled());
|
||||
assertFalse(new File(feedFile.getFile_url()).exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteOnFailShouldDelete() {
|
||||
Downloader downloader = download(url404, "testDeleteOnFailShouldDelete", false, true, null, null, true);
|
||||
Downloader downloader = download(url404, "testDeleteOnFailShouldDelete", false, true, null, null);
|
||||
assertFalse(new File(downloader.getDownloadRequest().getDestination()).exists());
|
||||
}
|
||||
|
||||
|
@ -146,42 +145,18 @@ public class HttpDownloaderTest {
|
|||
File dest = new File(destDir, filename);
|
||||
dest.delete();
|
||||
assertTrue(dest.createNewFile());
|
||||
Downloader downloader = download(url404, filename, false, false, null, null, false);
|
||||
Downloader downloader = download(url404, filename, false, false, null, null);
|
||||
assertTrue(new File(downloader.getDownloadRequest().getDestination()).exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticationShouldSucceed() throws InterruptedException {
|
||||
download(urlAuth, "testAuthSuccess", true, true, "user", "passwd", true);
|
||||
download(urlAuth, "testAuthSuccess", true, true, "user", "passwd");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticationShouldFail() {
|
||||
Downloader downloader = download(urlAuth, "testAuthSuccess", false, true, "user", "Wrong passwd", true);
|
||||
Downloader downloader = download(urlAuth, "testAuthSuccess", false, true, "user", "Wrong passwd");
|
||||
assertEquals(DownloadError.ERROR_UNAUTHORIZED, downloader.getResult().getReason());
|
||||
}
|
||||
|
||||
/* TODO: replace with smaller test file
|
||||
public void testUrlWithSpaces() {
|
||||
download("http://acedl.noxsolutions.com/ace/Don't Call Salman Rushdie Sneezy in Finland.mp3", "testUrlWithSpaces", true);
|
||||
}
|
||||
*/
|
||||
|
||||
private static class FeedFileImpl extends FeedFile {
|
||||
public FeedFileImpl(String download_url) {
|
||||
super(null, download_url, false);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getHumanReadableIdentifier() {
|
||||
return download_url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTypeAsInt() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import androidx.test.filters.MediumTest;
|
|||
import de.danoeh.antennapod.model.feed.VolumeAdaptionSetting;
|
||||
import de.danoeh.antennapod.playback.base.PlaybackServiceMediaPlayer;
|
||||
import de.danoeh.antennapod.playback.base.PlayerStatus;
|
||||
import de.danoeh.antennapod.playback.service.internal.LocalPSMP;
|
||||
import de.danoeh.antennapod.storage.database.PodDBAdapter;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
@ -27,7 +28,6 @@ import de.danoeh.antennapod.model.feed.Feed;
|
|||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.model.feed.FeedPreferences;
|
||||
import de.danoeh.antennapod.core.service.playback.LocalPSMP;
|
||||
import de.danoeh.antennapod.model.playback.Playable;
|
||||
import de.test.antennapod.util.service.download.HTTPBin;
|
||||
import org.junit.After;
|
||||
|
@ -98,7 +98,7 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
|
||||
private void checkPSMPInfo(LocalPSMP.PSMPInfo info) {
|
||||
try {
|
||||
switch (info.playerStatus) {
|
||||
switch (info.getPlayerStatus()) {
|
||||
case PLAYING:
|
||||
case PAUSED:
|
||||
case PREPARED:
|
||||
|
@ -106,11 +106,13 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
case INITIALIZED:
|
||||
case INITIALIZING:
|
||||
case SEEKING:
|
||||
assertNotNull(info.playable);
|
||||
assertNotNull(info.getPlayable());
|
||||
break;
|
||||
case STOPPED:
|
||||
case ERROR:
|
||||
assertNull(info.playable);
|
||||
assertNull(info.getPlayable());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (AssertionFailedError e) {
|
||||
|
@ -128,13 +130,16 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
}
|
||||
|
||||
private Playable writeTestPlayable(String downloadUrl, String fileUrl) {
|
||||
Feed f = new Feed(0, null, "f", "l", "d", null, null, null, null, "i", null, null, "l", false);
|
||||
FeedPreferences prefs = new FeedPreferences(f.getId(), false, FeedPreferences.AutoDeleteAction.NO, VolumeAdaptionSetting.OFF, null, null);
|
||||
Feed f = new Feed(0, null, "f", "l", "d", null, null, null, null,
|
||||
"i", null, null, "l", System.currentTimeMillis());
|
||||
FeedPreferences prefs = new FeedPreferences(f.getId(), false, FeedPreferences.AutoDeleteAction.NEVER,
|
||||
VolumeAdaptionSetting.OFF, FeedPreferences.NewEpisodesAction.NOTHING, null, null);
|
||||
f.setPreferences(prefs);
|
||||
f.setItems(new ArrayList<>());
|
||||
FeedItem i = new FeedItem(0, "t", "i", "l", new Date(), FeedItem.UNPLAYED, f);
|
||||
f.getItems().add(i);
|
||||
FeedMedia media = new FeedMedia(0, i, 0, 0, 0, "audio/wav", fileUrl, downloadUrl, fileUrl != null, null, 0, 0);
|
||||
FeedMedia media = new FeedMedia(0, i, 0, 0, 0, "audio/wav", fileUrl, downloadUrl,
|
||||
fileUrl == null ? 0 : System.currentTimeMillis(), null, 0, 0);
|
||||
i.setMedia(media);
|
||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||
adapter.open();
|
||||
|
@ -154,15 +159,16 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
try {
|
||||
checkPSMPInfo(newInfo);
|
||||
if (newInfo.playerStatus == PlayerStatus.ERROR)
|
||||
if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
|
||||
throw new IllegalStateException("MediaPlayer error");
|
||||
}
|
||||
if (countDownLatch.getCount() == 0) {
|
||||
fail();
|
||||
} else if (countDownLatch.getCount() == 2) {
|
||||
assertEquals(PlayerStatus.INITIALIZING, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.INITIALIZING, newInfo.getPlayerStatus());
|
||||
countDownLatch.countDown();
|
||||
} else {
|
||||
assertEquals(PlayerStatus.INITIALIZED, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.INITIALIZED, newInfo.getPlayerStatus());
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
} catch (AssertionFailedError e) {
|
||||
|
@ -179,7 +185,7 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
throw assertionError;
|
||||
assertTrue(res);
|
||||
|
||||
assertSame(PlayerStatus.INITIALIZED, psmp.getPSMPInfo().playerStatus);
|
||||
assertSame(PlayerStatus.INITIALIZED, psmp.getPSMPInfo().getPlayerStatus());
|
||||
assertFalse(psmp.isStartWhenPrepared());
|
||||
callback.cancel();
|
||||
psmp.shutdown();
|
||||
|
@ -195,15 +201,16 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
try {
|
||||
checkPSMPInfo(newInfo);
|
||||
if (newInfo.playerStatus == PlayerStatus.ERROR)
|
||||
if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
|
||||
throw new IllegalStateException("MediaPlayer error");
|
||||
}
|
||||
if (countDownLatch.getCount() == 0) {
|
||||
fail();
|
||||
} else if (countDownLatch.getCount() == 2) {
|
||||
assertEquals(PlayerStatus.INITIALIZING, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.INITIALIZING, newInfo.getPlayerStatus());
|
||||
countDownLatch.countDown();
|
||||
} else {
|
||||
assertEquals(PlayerStatus.INITIALIZED, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.INITIALIZED, newInfo.getPlayerStatus());
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
} catch (AssertionFailedError e) {
|
||||
|
@ -221,7 +228,7 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
throw assertionError;
|
||||
assertTrue(res);
|
||||
|
||||
assertSame(PlayerStatus.INITIALIZED, psmp.getPSMPInfo().playerStatus);
|
||||
assertSame(PlayerStatus.INITIALIZED, psmp.getPSMPInfo().getPlayerStatus());
|
||||
assertTrue(psmp.isStartWhenPrepared());
|
||||
callback.cancel();
|
||||
psmp.shutdown();
|
||||
|
@ -237,18 +244,19 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
try {
|
||||
checkPSMPInfo(newInfo);
|
||||
if (newInfo.playerStatus == PlayerStatus.ERROR)
|
||||
if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
|
||||
throw new IllegalStateException("MediaPlayer error");
|
||||
}
|
||||
if (countDownLatch.getCount() == 0) {
|
||||
fail();
|
||||
} else if (countDownLatch.getCount() == 4) {
|
||||
assertEquals(PlayerStatus.INITIALIZING, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.INITIALIZING, newInfo.getPlayerStatus());
|
||||
} else if (countDownLatch.getCount() == 3) {
|
||||
assertEquals(PlayerStatus.INITIALIZED, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.INITIALIZED, newInfo.getPlayerStatus());
|
||||
} else if (countDownLatch.getCount() == 2) {
|
||||
assertEquals(PlayerStatus.PREPARING, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.PREPARING, newInfo.getPlayerStatus());
|
||||
} else if (countDownLatch.getCount() == 1) {
|
||||
assertEquals(PlayerStatus.PREPARED, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.PREPARED, newInfo.getPlayerStatus());
|
||||
}
|
||||
countDownLatch.countDown();
|
||||
} catch (AssertionFailedError e) {
|
||||
|
@ -264,7 +272,7 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
if (assertionError != null)
|
||||
throw assertionError;
|
||||
assertTrue(res);
|
||||
assertSame(PlayerStatus.PREPARED, psmp.getPSMPInfo().playerStatus);
|
||||
assertSame(PlayerStatus.PREPARED, psmp.getPSMPInfo().getPlayerStatus());
|
||||
callback.cancel();
|
||||
|
||||
psmp.shutdown();
|
||||
|
@ -280,21 +288,21 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
try {
|
||||
checkPSMPInfo(newInfo);
|
||||
if (newInfo.playerStatus == PlayerStatus.ERROR)
|
||||
if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
|
||||
throw new IllegalStateException("MediaPlayer error");
|
||||
}
|
||||
if (countDownLatch.getCount() == 0) {
|
||||
fail();
|
||||
|
||||
} else if (countDownLatch.getCount() == 5) {
|
||||
assertEquals(PlayerStatus.INITIALIZING, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.INITIALIZING, newInfo.getPlayerStatus());
|
||||
} else if (countDownLatch.getCount() == 4) {
|
||||
assertEquals(PlayerStatus.INITIALIZED, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.INITIALIZED, newInfo.getPlayerStatus());
|
||||
} else if (countDownLatch.getCount() == 3) {
|
||||
assertEquals(PlayerStatus.PREPARING, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.PREPARING, newInfo.getPlayerStatus());
|
||||
} else if (countDownLatch.getCount() == 2) {
|
||||
assertEquals(PlayerStatus.PREPARED, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.PREPARED, newInfo.getPlayerStatus());
|
||||
} else if (countDownLatch.getCount() == 1) {
|
||||
assertEquals(PlayerStatus.PLAYING, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.PLAYING, newInfo.getPlayerStatus());
|
||||
}
|
||||
countDownLatch.countDown();
|
||||
} catch (AssertionFailedError e) {
|
||||
|
@ -310,7 +318,7 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
if (assertionError != null)
|
||||
throw assertionError;
|
||||
assertTrue(res);
|
||||
assertSame(PlayerStatus.PLAYING, psmp.getPSMPInfo().playerStatus);
|
||||
assertSame(PlayerStatus.PLAYING, psmp.getPSMPInfo().getPlayerStatus());
|
||||
callback.cancel();
|
||||
psmp.shutdown();
|
||||
}
|
||||
|
@ -325,15 +333,16 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
try {
|
||||
checkPSMPInfo(newInfo);
|
||||
if (newInfo.playerStatus == PlayerStatus.ERROR)
|
||||
if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
|
||||
throw new IllegalStateException("MediaPlayer error");
|
||||
}
|
||||
if (countDownLatch.getCount() == 0) {
|
||||
fail();
|
||||
} else if (countDownLatch.getCount() == 2) {
|
||||
assertEquals(PlayerStatus.INITIALIZING, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.INITIALIZING, newInfo.getPlayerStatus());
|
||||
countDownLatch.countDown();
|
||||
} else {
|
||||
assertEquals(PlayerStatus.INITIALIZED, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.INITIALIZED, newInfo.getPlayerStatus());
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
} catch (AssertionFailedError e) {
|
||||
|
@ -349,7 +358,7 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
if (assertionError != null)
|
||||
throw assertionError;
|
||||
assertTrue(res);
|
||||
assertSame(PlayerStatus.INITIALIZED, psmp.getPSMPInfo().playerStatus);
|
||||
assertSame(PlayerStatus.INITIALIZED, psmp.getPSMPInfo().getPlayerStatus());
|
||||
assertFalse(psmp.isStartWhenPrepared());
|
||||
callback.cancel();
|
||||
psmp.shutdown();
|
||||
|
@ -365,15 +374,16 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
try {
|
||||
checkPSMPInfo(newInfo);
|
||||
if (newInfo.playerStatus == PlayerStatus.ERROR)
|
||||
if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
|
||||
throw new IllegalStateException("MediaPlayer error");
|
||||
}
|
||||
if (countDownLatch.getCount() == 0) {
|
||||
fail();
|
||||
} else if (countDownLatch.getCount() == 2) {
|
||||
assertEquals(PlayerStatus.INITIALIZING, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.INITIALIZING, newInfo.getPlayerStatus());
|
||||
countDownLatch.countDown();
|
||||
} else {
|
||||
assertEquals(PlayerStatus.INITIALIZED, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.INITIALIZED, newInfo.getPlayerStatus());
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
} catch (AssertionFailedError e) {
|
||||
|
@ -389,7 +399,7 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
if (assertionError != null)
|
||||
throw assertionError;
|
||||
assertTrue(res);
|
||||
assertSame(PlayerStatus.INITIALIZED, psmp.getPSMPInfo().playerStatus);
|
||||
assertSame(PlayerStatus.INITIALIZED, psmp.getPSMPInfo().getPlayerStatus());
|
||||
assertTrue(psmp.isStartWhenPrepared());
|
||||
callback.cancel();
|
||||
psmp.shutdown();
|
||||
|
@ -405,18 +415,19 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
try {
|
||||
checkPSMPInfo(newInfo);
|
||||
if (newInfo.playerStatus == PlayerStatus.ERROR)
|
||||
if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
|
||||
throw new IllegalStateException("MediaPlayer error");
|
||||
}
|
||||
if (countDownLatch.getCount() == 0) {
|
||||
fail();
|
||||
} else if (countDownLatch.getCount() == 4) {
|
||||
assertEquals(PlayerStatus.INITIALIZING, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.INITIALIZING, newInfo.getPlayerStatus());
|
||||
} else if (countDownLatch.getCount() == 3) {
|
||||
assertEquals(PlayerStatus.INITIALIZED, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.INITIALIZED, newInfo.getPlayerStatus());
|
||||
} else if (countDownLatch.getCount() == 2) {
|
||||
assertEquals(PlayerStatus.PREPARING, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.PREPARING, newInfo.getPlayerStatus());
|
||||
} else if (countDownLatch.getCount() == 1) {
|
||||
assertEquals(PlayerStatus.PREPARED, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.PREPARED, newInfo.getPlayerStatus());
|
||||
}
|
||||
countDownLatch.countDown();
|
||||
} catch (AssertionFailedError e) {
|
||||
|
@ -432,7 +443,7 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
if (assertionError != null)
|
||||
throw assertionError;
|
||||
assertTrue(res);
|
||||
assertSame(PlayerStatus.PREPARED, psmp.getPSMPInfo().playerStatus);
|
||||
assertSame(PlayerStatus.PREPARED, psmp.getPSMPInfo().getPlayerStatus());
|
||||
callback.cancel();
|
||||
psmp.shutdown();
|
||||
}
|
||||
|
@ -447,20 +458,21 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
try {
|
||||
checkPSMPInfo(newInfo);
|
||||
if (newInfo.playerStatus == PlayerStatus.ERROR)
|
||||
if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
|
||||
throw new IllegalStateException("MediaPlayer error");
|
||||
}
|
||||
if (countDownLatch.getCount() == 0) {
|
||||
fail();
|
||||
} else if (countDownLatch.getCount() == 5) {
|
||||
assertEquals(PlayerStatus.INITIALIZING, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.INITIALIZING, newInfo.getPlayerStatus());
|
||||
} else if (countDownLatch.getCount() == 4) {
|
||||
assertEquals(PlayerStatus.INITIALIZED, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.INITIALIZED, newInfo.getPlayerStatus());
|
||||
} else if (countDownLatch.getCount() == 3) {
|
||||
assertEquals(PlayerStatus.PREPARING, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.PREPARING, newInfo.getPlayerStatus());
|
||||
} else if (countDownLatch.getCount() == 2) {
|
||||
assertEquals(PlayerStatus.PREPARED, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.PREPARED, newInfo.getPlayerStatus());
|
||||
} else if (countDownLatch.getCount() == 1) {
|
||||
assertEquals(PlayerStatus.PLAYING, newInfo.playerStatus);
|
||||
assertEquals(PlayerStatus.PLAYING, newInfo.getPlayerStatus());
|
||||
}
|
||||
|
||||
} catch (AssertionFailedError e) {
|
||||
|
@ -478,7 +490,7 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
if (assertionError != null)
|
||||
throw assertionError;
|
||||
assertTrue(res);
|
||||
assertSame(PlayerStatus.PLAYING, psmp.getPSMPInfo().playerStatus);
|
||||
assertSame(PlayerStatus.PLAYING, psmp.getPSMPInfo().getPlayerStatus());
|
||||
callback.cancel();
|
||||
psmp.shutdown();
|
||||
}
|
||||
|
@ -492,20 +504,23 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
@Override
|
||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
checkPSMPInfo(newInfo);
|
||||
if (newInfo.playerStatus == PlayerStatus.ERROR) {
|
||||
if (assertionError == null)
|
||||
assertionError = new UnexpectedStateChange(newInfo.playerStatus);
|
||||
if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
|
||||
if (assertionError == null) {
|
||||
assertionError = new UnexpectedStateChange(newInfo.getPlayerStatus());
|
||||
}
|
||||
} else if (initialState != PlayerStatus.PLAYING) {
|
||||
if (assertionError == null)
|
||||
assertionError = new UnexpectedStateChange(newInfo.playerStatus);
|
||||
if (assertionError == null) {
|
||||
assertionError = new UnexpectedStateChange(newInfo.getPlayerStatus());
|
||||
}
|
||||
} else {
|
||||
switch (newInfo.playerStatus) {
|
||||
switch (newInfo.getPlayerStatus()) {
|
||||
case PAUSED:
|
||||
if (latchCount == countDownLatch.getCount())
|
||||
countDownLatch.countDown();
|
||||
else {
|
||||
if (assertionError == null)
|
||||
assertionError = new UnexpectedStateChange(newInfo.playerStatus);
|
||||
if (assertionError == null) {
|
||||
assertionError = new UnexpectedStateChange(newInfo.getPlayerStatus());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case INITIALIZED:
|
||||
|
@ -513,9 +528,11 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
countDownLatch.countDown();
|
||||
} else if (countDownLatch.getCount() < latchCount) {
|
||||
if (assertionError == null)
|
||||
assertionError = new UnexpectedStateChange(newInfo.playerStatus);
|
||||
assertionError = new UnexpectedStateChange(newInfo.getPlayerStatus());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -605,13 +622,15 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
@Override
|
||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
checkPSMPInfo(newInfo);
|
||||
if (newInfo.playerStatus == PlayerStatus.ERROR) {
|
||||
if (assertionError == null)
|
||||
assertionError = new UnexpectedStateChange(newInfo.playerStatus);
|
||||
} else if (newInfo.playerStatus == PlayerStatus.PLAYING) {
|
||||
if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
|
||||
if (assertionError == null) {
|
||||
assertionError = new UnexpectedStateChange(newInfo.getPlayerStatus());
|
||||
}
|
||||
} else if (newInfo.getPlayerStatus() == PlayerStatus.PLAYING) {
|
||||
if (countDownLatch.getCount() == 0) {
|
||||
if (assertionError == null)
|
||||
assertionError = new UnexpectedStateChange(newInfo.playerStatus);
|
||||
if (assertionError == null) {
|
||||
assertionError = new UnexpectedStateChange(newInfo.getPlayerStatus());
|
||||
}
|
||||
} else {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
|
@ -662,13 +681,15 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
@Override
|
||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
checkPSMPInfo(newInfo);
|
||||
if (newInfo.playerStatus == PlayerStatus.ERROR) {
|
||||
if (assertionError == null)
|
||||
assertionError = new UnexpectedStateChange(newInfo.playerStatus);
|
||||
if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
|
||||
if (assertionError == null) {
|
||||
assertionError = new UnexpectedStateChange(newInfo.getPlayerStatus());
|
||||
}
|
||||
} else {
|
||||
if (initialState == PlayerStatus.INITIALIZED && newInfo.playerStatus == PlayerStatus.PREPARED) {
|
||||
if (initialState == PlayerStatus.INITIALIZED && newInfo.getPlayerStatus()
|
||||
== PlayerStatus.PREPARED) {
|
||||
countDownLatch.countDown();
|
||||
} else if (initialState != PlayerStatus.INITIALIZED && initialState == newInfo.playerStatus) {
|
||||
} else if (initialState != PlayerStatus.INITIALIZED && initialState == newInfo.getPlayerStatus()) {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
@ -691,7 +712,7 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
|
||||
boolean res = countDownLatch.await(timeoutSeconds, TimeUnit.SECONDS);
|
||||
if (initialState != PlayerStatus.INITIALIZED) {
|
||||
assertEquals(initialState, psmp.getPSMPInfo().playerStatus);
|
||||
assertEquals(initialState, psmp.getPSMPInfo().getPlayerStatus());
|
||||
}
|
||||
|
||||
if (assertionError != null)
|
||||
|
@ -733,13 +754,15 @@ public class PlaybackServiceMediaPlayerTest {
|
|||
@Override
|
||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
checkPSMPInfo(newInfo);
|
||||
if (newInfo.playerStatus == PlayerStatus.ERROR) {
|
||||
if (assertionError == null)
|
||||
assertionError = new UnexpectedStateChange(newInfo.playerStatus);
|
||||
if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
|
||||
if (assertionError == null) {
|
||||
assertionError = new UnexpectedStateChange(newInfo.getPlayerStatus());
|
||||
}
|
||||
} else {
|
||||
if (newInfo.playerStatus == initialState) {
|
||||
if (newInfo.getPlayerStatus() == initialState) {
|
||||
countDownLatch.countDown();
|
||||
} else if (countDownLatch.getCount() < latchCount && newInfo.playerStatus == PlayerStatus.INITIALIZED) {
|
||||
} else if (countDownLatch.getCount() < latchCount && newInfo.getPlayerStatus()
|
||||
== PlayerStatus.INITIALIZED) {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,10 @@ import androidx.test.annotation.UiThreadTest;
|
|||
import androidx.test.filters.LargeTest;
|
||||
|
||||
import de.danoeh.antennapod.event.playback.SleepTimerUpdatedEvent;
|
||||
import de.danoeh.antennapod.core.preferences.SleepTimerPreferences;
|
||||
import de.danoeh.antennapod.core.widget.WidgetUpdater;
|
||||
import de.danoeh.antennapod.playback.service.internal.PlaybackServiceTaskManager;
|
||||
import de.danoeh.antennapod.storage.preferences.SleepTimerPreferences;
|
||||
import de.danoeh.antennapod.storage.database.PodDBAdapter;
|
||||
import de.danoeh.antennapod.ui.widget.WidgetUpdater;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.junit.After;
|
||||
|
@ -23,7 +24,6 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackServiceTaskManager;
|
||||
import de.danoeh.antennapod.model.playback.Playable;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
@ -63,7 +63,8 @@ public class PlaybackServiceTaskManagerTest {
|
|||
|
||||
private List<FeedItem> writeTestQueue(String pref) {
|
||||
final int NUM_ITEMS = 10;
|
||||
Feed f = new Feed(0, null, "title", "link", "d", null, null, null, null, "id", null, "null", "url", false);
|
||||
Feed f = new Feed(0, null, "title", "link", "d", null, null, null, null, "id",
|
||||
null, "null", "url", System.currentTimeMillis());
|
||||
f.setItems(new ArrayList<>());
|
||||
for (int i = 0; i < NUM_ITEMS; i++) {
|
||||
f.getItems().add(new FeedItem(0, pref + i, pref + i, "link", new Date(), FeedItem.PLAYED, f));
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package de.test.antennapod.service.playback;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import de.danoeh.antennapod.storage.preferences.SleepTimerPreferences;
|
||||
|
||||
public class SleepTimerPreferencesTest {
|
||||
@Test
|
||||
public void testIsInTimeRange() {
|
||||
assertTrue(SleepTimerPreferences.isInTimeRange(0, 10, 8));
|
||||
assertTrue(SleepTimerPreferences.isInTimeRange(1, 10, 8));
|
||||
assertTrue(SleepTimerPreferences.isInTimeRange(1, 10, 1));
|
||||
assertTrue(SleepTimerPreferences.isInTimeRange(20, 10, 8));
|
||||
assertTrue(SleepTimerPreferences.isInTimeRange(20, 20, 8));
|
||||
assertFalse(SleepTimerPreferences.isInTimeRange(1, 6, 8));
|
||||
assertFalse(SleepTimerPreferences.isInTimeRange(1, 6, 6));
|
||||
assertFalse(SleepTimerPreferences.isInTimeRange(20, 6, 8));
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
package de.test.antennapod.storage;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
import de.test.antennapod.ui.UITestUtils;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.awaitility.core.ConditionTimeoutException;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class AutoDownloadTest {
|
||||
|
||||
private Context context;
|
||||
private UITestUtils stubFeedsServer;
|
||||
private StubDownloadAlgorithm stubDownloadAlgorithm;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
stubFeedsServer = new UITestUtils(context);
|
||||
stubFeedsServer.setup();
|
||||
|
||||
EspressoTestUtils.clearPreferences();
|
||||
EspressoTestUtils.clearDatabase();
|
||||
UserPreferences.setAllowMobileStreaming(true);
|
||||
|
||||
// Setup: enable automatic download
|
||||
// it is not needed, as the actual automatic download is stubbed.
|
||||
stubDownloadAlgorithm = new StubDownloadAlgorithm();
|
||||
DBTasks.setDownloadAlgorithm(stubDownloadAlgorithm);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
DBTasks.setDownloadAlgorithm(new AutomaticDownloadAlgorithm());
|
||||
EspressoTestUtils.tryKillPlaybackService();
|
||||
stubFeedsServer.tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* A cross-functional test, ensuring playback's behavior works with Auto Download in boundary condition.
|
||||
*
|
||||
* Scenario:
|
||||
* - For setting enqueue location AFTER_CURRENTLY_PLAYING
|
||||
* - when playback of an episode is complete and the app advances to the next episode (continuous playback on)
|
||||
* - when automatic download kicks in,
|
||||
* - ensure the next episode is the current playing one, needed for AFTER_CURRENTLY_PLAYING enqueue location.
|
||||
*/
|
||||
@Test
|
||||
public void downloadsEnqueuedToAfterCurrent_CurrentAdvancedToNextOnPlaybackComplete() throws Exception {
|
||||
UserPreferences.setFollowQueue(true); // continuous playback
|
||||
|
||||
// Setup: feeds and queue
|
||||
// downloads 3 of them, leave some in new state (auto-downloadable)
|
||||
stubFeedsServer.addLocalFeedData(false);
|
||||
List<FeedItem> queue = DBReader.getQueue();
|
||||
assertTrue(queue.size() > 1);
|
||||
FeedItem item0 = queue.get(0);
|
||||
FeedItem item1 = queue.get(1);
|
||||
|
||||
// Actual test
|
||||
// Play the first one in the queue
|
||||
playEpisode(item0);
|
||||
|
||||
try {
|
||||
// when playback is complete, advances to the next one, and auto download kicks in,
|
||||
// ensure that currently playing has been advanced to the next one by this point.
|
||||
Awaitility.await("advanced to the next episode")
|
||||
.atMost(6000, MILLISECONDS) // the test mp3 media is 3-second long. twice should be enough
|
||||
.until(() -> item1.getMedia().getId() == stubDownloadAlgorithm.getCurrentlyPlayingAtDownload());
|
||||
} catch (ConditionTimeoutException cte) {
|
||||
long actual = stubDownloadAlgorithm.getCurrentlyPlayingAtDownload();
|
||||
fail("when auto download is triggered, the next episode should be playing: ("
|
||||
+ item1.getId() + ", " + item1.getTitle() + ") . "
|
||||
+ "Actual playing: (" + actual + ")"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void playEpisode(@NonNull FeedItem item) {
|
||||
FeedMedia media = item.getMedia();
|
||||
new PlaybackServiceStarter(context, media)
|
||||
.callEvenIfRunning(true)
|
||||
.start();
|
||||
Awaitility.await("episode is playing")
|
||||
.atMost(2000, MILLISECONDS)
|
||||
.until(() -> item.getMedia().getId() == PlaybackPreferences.getCurrentlyPlayingFeedMediaId());
|
||||
}
|
||||
|
||||
private static class StubDownloadAlgorithm extends AutomaticDownloadAlgorithm {
|
||||
private long currentlyPlaying = -1;
|
||||
|
||||
@Override
|
||||
public Runnable autoDownloadUndownloadedItems(Context context) {
|
||||
return () -> {
|
||||
if (currentlyPlaying == -1) {
|
||||
currentlyPlaying = PlaybackPreferences.getCurrentlyPlayingFeedMediaId();
|
||||
} else {
|
||||
throw new AssertionError("Stub automatic download should be invoked once and only once");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
long getCurrentlyPlayingAtDownload() {
|
||||
return currentlyPlaying;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,8 +2,8 @@ package de.test.antennapod.ui;
|
|||
|
||||
import android.content.Intent;
|
||||
import androidx.test.espresso.intent.rule.IntentsTestRule;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
|
@ -70,10 +70,10 @@ public class FeedSettingsTest {
|
|||
clickPreference(R.string.pref_feed_skip);
|
||||
onView(withText(R.string.cancel_label)).perform(click());
|
||||
|
||||
clickPreference(R.string.auto_delete_label);
|
||||
clickPreference(R.string.pref_auto_delete_playback_title);
|
||||
onView(withText(R.string.cancel_label)).perform(click());
|
||||
|
||||
clickPreference(R.string.feed_volume_reduction);
|
||||
clickPreference(R.string.feed_volume_adapdation);
|
||||
onView(withText(R.string.cancel_label)).perform(click());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
package de.test.antennapod.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
|
||||
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.model.feed.Feed;
|
||||
import de.danoeh.antennapod.storage.database.PodDBAdapter;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
|
@ -19,27 +18,14 @@ import org.junit.runner.RunWith;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
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;
|
||||
import static androidx.test.espresso.action.ViewActions.scrollTo;
|
||||
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.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.waitForViewGlobally;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* User interface tests for MainActivity.
|
||||
|
@ -47,7 +33,6 @@ import static org.junit.Assert.assertTrue;
|
|||
@RunWith(AndroidJUnit4.class)
|
||||
public class MainActivityTest {
|
||||
|
||||
private Solo solo;
|
||||
private UITestUtils uiTestUtils;
|
||||
|
||||
@Rule
|
||||
|
@ -62,8 +47,6 @@ public class MainActivityTest {
|
|||
|
||||
uiTestUtils = new UITestUtils(InstrumentationRegistry.getInstrumentation().getTargetContext());
|
||||
uiTestUtils.setup();
|
||||
|
||||
solo = new Solo(InstrumentationRegistry.getInstrumentation(), activityRule.getActivity());
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -80,7 +63,7 @@ public class MainActivityTest {
|
|||
openNavDrawer();
|
||||
onView(withText(R.string.add_feed_label)).perform(click());
|
||||
onView(withId(R.id.addViaUrlButton)).perform(scrollTo(), click());
|
||||
onView(withId(R.id.urlEditText)).perform(replaceText(feed.getDownload_url()));
|
||||
onView(withId(R.id.urlEditText)).perform(replaceText(feed.getDownloadUrl()));
|
||||
onView(withText(R.string.confirm_label)).perform(scrollTo(), click());
|
||||
|
||||
// subscribe podcast
|
||||
|
@ -91,79 +74,4 @@ public class MainActivityTest {
|
|||
// wait for podcast feed item list
|
||||
waitForViewGlobally(withId(R.id.butShowSettings), 15000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackButtonBehaviorGoToPage() {
|
||||
openNavDrawer();
|
||||
onView(withText(R.string.settings_label)).perform(click());
|
||||
clickPreference(R.string.user_interface_label);
|
||||
clickPreference(R.string.pref_back_button_behavior_title);
|
||||
|
||||
onView(withText(R.string.back_button_go_to_page)).perform(click());
|
||||
onView(withText(R.string.subscriptions_label)).perform(click());
|
||||
onView(withText(R.string.confirm_label)).perform(click());
|
||||
|
||||
solo.goBackToActivity(MainActivity.class.getSimpleName());
|
||||
solo.goBack();
|
||||
solo.goBack();
|
||||
onView(allOf(withId(R.id.toolbar), isDisplayed())).check(
|
||||
matches(hasDescendant(withText(R.string.subscriptions_label))));
|
||||
solo.goBack();
|
||||
assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackButtonBehaviorOpenDrawer() {
|
||||
openNavDrawer();
|
||||
onView(withText(R.string.settings_label)).perform(click());
|
||||
clickPreference(R.string.user_interface_label);
|
||||
clickPreference(R.string.pref_back_button_behavior_title);
|
||||
onView(withText(R.string.back_button_open_drawer)).perform(click());
|
||||
solo.goBackToActivity(MainActivity.class.getSimpleName());
|
||||
solo.goBack();
|
||||
solo.goBack();
|
||||
assertTrue(((MainActivity) solo.getCurrentActivity()).isDrawerOpen());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackButtonBehaviorDoubleTap() {
|
||||
openNavDrawer();
|
||||
onView(withText(R.string.settings_label)).perform(click());
|
||||
clickPreference(R.string.user_interface_label);
|
||||
clickPreference(R.string.pref_back_button_behavior_title);
|
||||
onView(withText(R.string.back_button_double_tap)).perform(click());
|
||||
solo.goBackToActivity(MainActivity.class.getSimpleName());
|
||||
solo.goBack();
|
||||
solo.goBack();
|
||||
solo.goBack();
|
||||
assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackButtonBehaviorPrompt() throws Exception {
|
||||
openNavDrawer();
|
||||
onView(withText(R.string.settings_label)).perform(click());
|
||||
clickPreference(R.string.user_interface_label);
|
||||
clickPreference(R.string.pref_back_button_behavior_title);
|
||||
onView(withText(R.string.back_button_show_prompt)).perform(click());
|
||||
solo.goBackToActivity(MainActivity.class.getSimpleName());
|
||||
solo.goBack();
|
||||
solo.goBack();
|
||||
onView(withText(R.string.yes)).perform(click());
|
||||
Thread.sleep(100);
|
||||
assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackButtonBehaviorDefault() {
|
||||
openNavDrawer();
|
||||
onView(withText(R.string.settings_label)).perform(click());
|
||||
clickPreference(R.string.user_interface_label);
|
||||
clickPreference(R.string.pref_back_button_behavior_title);
|
||||
onView(withText(R.string.back_button_default)).perform(click());
|
||||
solo.goBackToActivity(MainActivity.class.getSimpleName());
|
||||
solo.goBack();
|
||||
solo.goBack();
|
||||
assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,14 +7,14 @@ 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.activity.PreferenceActivity;
|
||||
import de.danoeh.antennapod.fragment.CompletedDownloadsFragment;
|
||||
import de.danoeh.antennapod.ui.screen.preferences.PreferenceActivity;
|
||||
import de.danoeh.antennapod.ui.screen.download.CompletedDownloadsFragment;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.fragment.AllEpisodesFragment;
|
||||
import de.danoeh.antennapod.fragment.NavDrawerFragment;
|
||||
import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
|
||||
import de.danoeh.antennapod.fragment.QueueFragment;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.ui.screen.AllEpisodesFragment;
|
||||
import de.danoeh.antennapod.ui.screen.drawer.NavDrawerFragment;
|
||||
import de.danoeh.antennapod.ui.screen.PlaybackHistoryFragment;
|
||||
import de.danoeh.antennapod.ui.screen.queue.QueueFragment;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
@ -81,12 +81,24 @@ public class NavigationDrawerTest {
|
|||
UserPreferences.setHiddenDrawerItems(new ArrayList<>());
|
||||
activityRule.launchActivity(new Intent());
|
||||
|
||||
// home
|
||||
openNavDrawer();
|
||||
onDrawerItem(withText(R.string.home_label)).perform(click());
|
||||
onView(isRoot()).perform(waitForView(allOf(isDescendantOfA(withId(R.id.toolbar)),
|
||||
withText(R.string.home_label)), 1000));
|
||||
|
||||
// queue
|
||||
openNavDrawer();
|
||||
onDrawerItem(withText(R.string.queue_label)).perform(click());
|
||||
onView(isRoot()).perform(waitForView(allOf(isDescendantOfA(withId(R.id.toolbar)),
|
||||
withText(R.string.queue_label)), 1000));
|
||||
|
||||
// Inbox
|
||||
openNavDrawer();
|
||||
onDrawerItem(withText(R.string.inbox_label)).perform(click());
|
||||
onView(isRoot()).perform(waitForView(allOf(isDescendantOfA(withId(R.id.toolbar)),
|
||||
withText(R.string.inbox_label)), 1000));
|
||||
|
||||
// episodes
|
||||
openNavDrawer();
|
||||
onDrawerItem(withText(R.string.episodes_label)).perform(click());
|
||||
|
@ -143,6 +155,7 @@ public class NavigationDrawerTest {
|
|||
openNavDrawer();
|
||||
onDrawerItem(withText(R.string.queue_label)).perform(longClick());
|
||||
onView(withText(R.string.episodes_label)).perform(click());
|
||||
onView(withId(R.id.contentPanel)).perform(swipeUp());
|
||||
onView(withText(R.string.playback_history_label)).perform(click());
|
||||
onView(withText(R.string.confirm_label)).perform(click());
|
||||
|
||||
|
@ -160,8 +173,9 @@ public class NavigationDrawerTest {
|
|||
openNavDrawer();
|
||||
onView(first(withText(R.string.queue_label))).perform(longClick());
|
||||
|
||||
onView(withText(R.string.downloads_label)).perform(click());
|
||||
onView(withText(R.string.queue_label)).perform(click());
|
||||
onView(withId(R.id.contentPanel)).perform(swipeUp());
|
||||
onView(withText(R.string.downloads_label)).perform(click());
|
||||
onView(withText(R.string.confirm_label)).perform(click());
|
||||
|
||||
hidden = UserPreferences.getHiddenDrawerItems();
|
||||
|
@ -184,7 +198,7 @@ public class NavigationDrawerTest {
|
|||
onView(allOf(withText(title), isDisplayed())).perform(click());
|
||||
|
||||
if (i == 3) {
|
||||
onView(withId(R.id.select_dialog_listview)).perform(swipeUp());
|
||||
onView(withId(R.id.contentPanel)).perform(swipeUp());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,6 +220,7 @@ public class NavigationDrawerTest {
|
|||
openNavDrawer();
|
||||
|
||||
onView(first(withText(R.string.queue_label))).perform(longClick());
|
||||
onView(withId(R.id.contentPanel)).perform(swipeUp());
|
||||
onView(first(withText(R.string.downloads_label))).perform(click());
|
||||
onView(withText(R.string.confirm_label)).perform(click());
|
||||
|
||||
|
|
|
@ -6,55 +6,40 @@ import android.content.res.Resources;
|
|||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.test.espresso.matcher.RootMatchers;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
|
||||
import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithmFactory;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.ui.screen.preferences.PreferenceActivity;
|
||||
import de.danoeh.antennapod.net.download.service.episode.autodownload.APCleanupAlgorithm;
|
||||
import de.danoeh.antennapod.net.download.service.episode.autodownload.APNullCleanupAlgorithm;
|
||||
import de.danoeh.antennapod.net.download.service.episode.autodownload.APQueueCleanupAlgorithm;
|
||||
import de.danoeh.antennapod.net.download.service.episode.autodownload.EpisodeCleanupAlgorithm;
|
||||
import de.danoeh.antennapod.net.download.service.episode.autodownload.EpisodeCleanupAlgorithmFactory;
|
||||
import de.danoeh.antennapod.net.download.service.episode.autodownload.ExceptFavoriteCleanupAlgorithm;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences.EnqueueLocation;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.PreferenceActivity;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences.EnqueueLocation;
|
||||
import de.danoeh.antennapod.core.storage.APCleanupAlgorithm;
|
||||
import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm;
|
||||
import de.danoeh.antennapod.core.storage.APQueueCleanupAlgorithm;
|
||||
import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
|
||||
import de.danoeh.antennapod.core.storage.ExceptFavoriteCleanupAlgorithm;
|
||||
import de.danoeh.antennapod.fragment.AllEpisodesFragment;
|
||||
import de.danoeh.antennapod.fragment.QueueFragment;
|
||||
import de.danoeh.antennapod.fragment.SubscriptionFragment;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onData;
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
|
||||
import static androidx.test.espresso.action.ViewActions.replaceText;
|
||||
import static androidx.test.espresso.action.ViewActions.scrollTo;
|
||||
import static androidx.test.espresso.action.ViewActions.swipeDown;
|
||||
import static androidx.test.espresso.action.ViewActions.swipeUp;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isChecked;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
|
||||
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.waitForView;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.hamcrest.Matchers.anything;
|
||||
import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@LargeTest
|
||||
|
@ -80,38 +65,6 @@ public class PreferencesTest {
|
|||
UserPreferences.init(activityTestRule.getActivity());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSwitchTheme() {
|
||||
final int theme = UserPreferences.getTheme();
|
||||
int otherTheme;
|
||||
if (theme == de.danoeh.antennapod.core.R.style.Theme_AntennaPod_Light) {
|
||||
otherTheme = R.string.pref_theme_title_dark;
|
||||
} else {
|
||||
otherTheme = R.string.pref_theme_title_light;
|
||||
}
|
||||
clickPreference(R.string.user_interface_label);
|
||||
clickPreference(R.string.pref_set_theme_title);
|
||||
onView(withText(otherTheme)).perform(click());
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> UserPreferences.getTheme() != theme);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSwitchThemeBack() {
|
||||
final int theme = UserPreferences.getTheme();
|
||||
int otherTheme;
|
||||
if (theme == de.danoeh.antennapod.core.R.style.Theme_AntennaPod_Light) {
|
||||
otherTheme = R.string.pref_theme_title_dark;
|
||||
} else {
|
||||
otherTheme = R.string.pref_theme_title_light;
|
||||
}
|
||||
clickPreference(R.string.user_interface_label);
|
||||
clickPreference(R.string.pref_set_theme_title);
|
||||
onView(withText(otherTheme)).perform(click());
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> UserPreferences.getTheme() != theme);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnablePersistentPlaybackControls() {
|
||||
final boolean persistNotify = UserPreferences.isPersistNotify();
|
||||
|
@ -125,38 +78,22 @@ public class PreferencesTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testSetLockscreenButtons() {
|
||||
public void testSetNotificationButtons() {
|
||||
clickPreference(R.string.user_interface_label);
|
||||
String[] buttons = res.getStringArray(R.array.compact_notification_buttons_options);
|
||||
clickPreference(R.string.pref_compact_notification_buttons_title);
|
||||
String[] buttons = res.getStringArray(R.array.full_notification_buttons_options);
|
||||
clickPreference(R.string.pref_full_notification_buttons_title);
|
||||
// First uncheck checkboxes
|
||||
onView(withText(buttons[0])).perform(click());
|
||||
onView(withText(buttons[1])).perform(click());
|
||||
|
||||
// Now try to check all checkboxes
|
||||
onView(withText(buttons[0])).perform(click());
|
||||
onView(withText(buttons[1])).perform(click());
|
||||
onView(withText(buttons[2])).perform(click());
|
||||
|
||||
// Make sure that the third checkbox is unchecked
|
||||
onView(withText(buttons[2])).check(matches(not(isChecked())));
|
||||
|
||||
String snackBarText = String.format(res.getString(
|
||||
R.string.pref_compact_notification_buttons_dialog_error), 2);
|
||||
Awaitility.await().ignoreExceptions().atMost(4000, MILLISECONDS)
|
||||
.until(() -> {
|
||||
onView(withText(snackBarText)).check(doesNotExist());
|
||||
return true;
|
||||
});
|
||||
|
||||
onView(withText(R.string.confirm_label)).perform(click());
|
||||
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(UserPreferences::showRewindOnCompactNotification);
|
||||
.until(() -> UserPreferences.showSkipOnFullNotification());
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(UserPreferences::showFastForwardOnCompactNotification);
|
||||
.until(() -> UserPreferences.showNextChapterOnFullNotification());
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> !UserPreferences.showSkipOnCompactNotification());
|
||||
.until(() -> !UserPreferences.showPlaybackSpeedOnFullNotification());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -165,6 +102,7 @@ public class PreferencesTest {
|
|||
doTestEnqueueLocation(R.string.enqueue_location_after_current, EnqueueLocation.AFTER_CURRENTLY_PLAYING);
|
||||
doTestEnqueueLocation(R.string.enqueue_location_front, EnqueueLocation.FRONT);
|
||||
doTestEnqueueLocation(R.string.enqueue_location_back, EnqueueLocation.BACK);
|
||||
doTestEnqueueLocation(R.string.enqueue_location_random, EnqueueLocation.RANDOM);
|
||||
}
|
||||
|
||||
private void doTestEnqueueLocation(@StringRes int optionResId, EnqueueLocation expected) {
|
||||
|
@ -234,16 +172,35 @@ public class PreferencesTest {
|
|||
|
||||
@Test
|
||||
public void testAutoDelete() {
|
||||
clickPreference(R.string.storage_pref);
|
||||
final boolean autoDelete = UserPreferences.isAutoDelete();
|
||||
clickPreference(R.string.downloads_pref);
|
||||
onView(withText(R.string.pref_auto_delete_title)).perform(click());
|
||||
final boolean autoDelete = UserPreferences.isAutoDelete();
|
||||
onView(withText(R.string.pref_auto_delete_playback_title)).perform(click());
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> autoDelete != UserPreferences.isAutoDelete());
|
||||
onView(withText(R.string.pref_auto_delete_title)).perform(click());
|
||||
onView(withText(R.string.pref_auto_delete_playback_title)).perform(click());
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> autoDelete == UserPreferences.isAutoDelete());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoDeleteLocal() {
|
||||
clickPreference(R.string.downloads_pref);
|
||||
onView(withText(R.string.pref_auto_delete_title)).perform(click());
|
||||
onView(withText(R.string.pref_auto_delete_playback_title)).perform(click());
|
||||
assertTrue(UserPreferences.isAutoDelete());
|
||||
assertFalse(UserPreferences.isAutoDeleteLocal());
|
||||
|
||||
onView(withText(R.string.pref_auto_local_delete_title)).perform(click());
|
||||
onView(withText(R.string.yes)).perform(click());
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> UserPreferences.isAutoDeleteLocal());
|
||||
|
||||
onView(withText(R.string.pref_auto_local_delete_title)).perform(click());
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> !UserPreferences.isAutoDeleteLocal());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlaybackSpeeds() {
|
||||
clickPreference(R.string.playback_pref);
|
||||
|
@ -264,71 +221,13 @@ public class PreferencesTest {
|
|||
.until(() -> pauseForFocusLoss == UserPreferences.shouldPauseForFocusLoss());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisableUpdateInterval() {
|
||||
clickPreference(R.string.network_pref);
|
||||
clickPreference(R.string.feed_refresh_title);
|
||||
onView(withText(R.string.feed_refresh_never)).perform(click());
|
||||
onView(withId(R.id.disableRadioButton)).perform(click());
|
||||
onView(withText(R.string.confirm_label)).perform(click());
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> UserPreferences.getUpdateInterval() == 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetUpdateInterval() {
|
||||
clickPreference(R.string.network_pref);
|
||||
clickPreference(R.string.feed_refresh_title);
|
||||
onView(withId(R.id.intervalRadioButton)).perform(click());
|
||||
onView(withId(R.id.spinner)).perform(click());
|
||||
int position = 1; // an arbitrary position
|
||||
onData(anything()).inRoot(RootMatchers.isPlatformPopup()).atPosition(position).perform(click());
|
||||
onView(withText(R.string.confirm_label)).perform(click());
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> UserPreferences.getUpdateInterval() == TimeUnit.HOURS.toMillis(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSequentialDownload() {
|
||||
clickPreference(R.string.network_pref);
|
||||
clickPreference(R.string.pref_parallel_downloads_title);
|
||||
onView(isRoot()).perform(waitForView(withClassName(endsWith("EditText")), 1000));
|
||||
onView(withClassName(endsWith("EditText"))).perform(replaceText("1"));
|
||||
onView(withText(android.R.string.ok)).perform(click());
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> UserPreferences.getParallelDownloads() == 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetParallelDownloads() {
|
||||
clickPreference(R.string.network_pref);
|
||||
clickPreference(R.string.pref_parallel_downloads_title);
|
||||
onView(isRoot()).perform(waitForView(withClassName(endsWith("EditText")), 1000));
|
||||
onView(withClassName(endsWith("EditText"))).perform(replaceText("10"));
|
||||
onView(withClassName(endsWith("EditText"))).perform(closeSoftKeyboard());
|
||||
onView(withText(android.R.string.ok)).perform(click());
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> UserPreferences.getParallelDownloads() == 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetParallelDownloadsInvalidInput() {
|
||||
clickPreference(R.string.network_pref);
|
||||
clickPreference(R.string.pref_parallel_downloads_title);
|
||||
onView(isRoot()).perform(waitForView(withClassName(endsWith("EditText")), 1000));
|
||||
onView(withClassName(endsWith("EditText"))).perform(replaceText("0"));
|
||||
onView(withClassName(endsWith("EditText"))).check(matches(withText("")));
|
||||
onView(withClassName(endsWith("EditText"))).perform(replaceText("100"));
|
||||
onView(withClassName(endsWith("EditText"))).check(matches(withText("")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetEpisodeCache() {
|
||||
String[] entries = res.getStringArray(R.array.episode_cache_size_entries);
|
||||
String[] values = res.getStringArray(R.array.episode_cache_size_values);
|
||||
String entry = entries[entries.length / 2];
|
||||
final int value = Integer.parseInt(values[values.length / 2]);
|
||||
clickPreference(R.string.network_pref);
|
||||
clickPreference(R.string.downloads_pref);
|
||||
clickPreference(R.string.pref_automatic_download_title);
|
||||
clickPreference(R.string.pref_episode_cache_title);
|
||||
onView(isRoot()).perform(waitForView(withText(entry), 1000));
|
||||
|
@ -344,7 +243,7 @@ public class PreferencesTest {
|
|||
String minEntry = entries[0];
|
||||
final int minValue = Integer.parseInt(values[0]);
|
||||
|
||||
clickPreference(R.string.network_pref);
|
||||
clickPreference(R.string.downloads_pref);
|
||||
clickPreference(R.string.pref_automatic_download_title);
|
||||
clickPreference(R.string.pref_episode_cache_title);
|
||||
onView(withId(R.id.select_dialog_listview)).perform(swipeDown());
|
||||
|
@ -359,7 +258,7 @@ public class PreferencesTest {
|
|||
String[] values = res.getStringArray(R.array.episode_cache_size_values);
|
||||
String maxEntry = entries[entries.length - 1];
|
||||
final int maxValue = Integer.parseInt(values[values.length - 1]);
|
||||
onView(withText(R.string.network_pref)).perform(click());
|
||||
onView(withText(R.string.downloads_pref)).perform(click());
|
||||
onView(withText(R.string.pref_automatic_download_title)).perform(click());
|
||||
onView(withText(R.string.pref_episode_cache_title)).perform(click());
|
||||
onView(withId(R.id.select_dialog_listview)).perform(swipeUp());
|
||||
|
@ -371,7 +270,7 @@ public class PreferencesTest {
|
|||
@Test
|
||||
public void testAutomaticDownload() {
|
||||
final boolean automaticDownload = UserPreferences.isEnableAutodownload();
|
||||
clickPreference(R.string.network_pref);
|
||||
clickPreference(R.string.downloads_pref);
|
||||
clickPreference(R.string.pref_automatic_download_title);
|
||||
clickPreference(R.string.pref_automatic_download_title);
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
|
@ -392,8 +291,8 @@ public class PreferencesTest {
|
|||
|
||||
@Test
|
||||
public void testEpisodeCleanupFavoriteOnly() {
|
||||
clickPreference(R.string.network_pref);
|
||||
onView(withText(R.string.pref_automatic_download_title)).perform(click());
|
||||
clickPreference(R.string.downloads_pref);
|
||||
onView(withText(R.string.pref_auto_delete_title)).perform(click());
|
||||
onView(withText(R.string.pref_episode_cleanup_title)).perform(click());
|
||||
onView(withId(R.id.select_dialog_listview)).perform(swipeDown());
|
||||
onView(withText(R.string.episode_cleanup_except_favorite_removal)).perform(click());
|
||||
|
@ -403,8 +302,8 @@ public class PreferencesTest {
|
|||
|
||||
@Test
|
||||
public void testEpisodeCleanupQueueOnly() {
|
||||
clickPreference(R.string.network_pref);
|
||||
onView(withText(R.string.pref_automatic_download_title)).perform(click());
|
||||
clickPreference(R.string.downloads_pref);
|
||||
onView(withText(R.string.pref_auto_delete_title)).perform(click());
|
||||
onView(withText(R.string.pref_episode_cleanup_title)).perform(click());
|
||||
onView(withId(R.id.select_dialog_listview)).perform(swipeDown());
|
||||
onView(withText(R.string.episode_cleanup_queue_removal)).perform(click());
|
||||
|
@ -414,8 +313,8 @@ public class PreferencesTest {
|
|||
|
||||
@Test
|
||||
public void testEpisodeCleanupNeverAlg() {
|
||||
clickPreference(R.string.network_pref);
|
||||
onView(withText(R.string.pref_automatic_download_title)).perform(click());
|
||||
clickPreference(R.string.downloads_pref);
|
||||
onView(withText(R.string.pref_auto_delete_title)).perform(click());
|
||||
onView(withText(R.string.pref_episode_cleanup_title)).perform(click());
|
||||
onView(withId(R.id.select_dialog_listview)).perform(swipeUp());
|
||||
onView(withText(R.string.episode_cleanup_never)).perform(click());
|
||||
|
@ -425,10 +324,9 @@ public class PreferencesTest {
|
|||
|
||||
@Test
|
||||
public void testEpisodeCleanupClassic() {
|
||||
clickPreference(R.string.network_pref);
|
||||
onView(withText(R.string.pref_automatic_download_title)).perform(click());
|
||||
clickPreference(R.string.downloads_pref);
|
||||
onView(withText(R.string.pref_auto_delete_title)).perform(click());
|
||||
onView(withText(R.string.pref_episode_cleanup_title)).perform(click());
|
||||
onView(withId(R.id.select_dialog_listview)).perform(swipeUp());
|
||||
onView(withText(R.string.episode_cleanup_after_listening)).perform(click());
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> {
|
||||
|
@ -443,8 +341,8 @@ public class PreferencesTest {
|
|||
|
||||
@Test
|
||||
public void testEpisodeCleanupNumDays() {
|
||||
clickPreference(R.string.network_pref);
|
||||
clickPreference(R.string.pref_automatic_download_title);
|
||||
clickPreference(R.string.downloads_pref);
|
||||
onView(withText(R.string.pref_auto_delete_title)).perform(click());
|
||||
clickPreference(R.string.pref_episode_cleanup_title);
|
||||
String search = res.getQuantityString(R.plurals.episode_cleanup_days_after_listening, 3, 3);
|
||||
onView(withText(search)).perform(scrollTo());
|
||||
|
@ -474,7 +372,6 @@ public class PreferencesTest {
|
|||
// Find next value (wrapping around to next)
|
||||
int newIndex = (currentIndex + 1) % deltas.length;
|
||||
onView(withText(deltas[newIndex] + " seconds")).perform(click());
|
||||
onView(withText("Confirm")).perform(click());
|
||||
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> UserPreferences.getRewindSecs() == deltas[newIndex]);
|
||||
|
@ -496,45 +393,15 @@ public class PreferencesTest {
|
|||
int newIndex = (currentIndex + 1) % deltas.length;
|
||||
|
||||
onView(withText(deltas[newIndex] + " seconds")).perform(click());
|
||||
onView(withText("Confirm")).perform(click());
|
||||
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> UserPreferences.getFastForwardSecs() == deltas[newIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackButtonBehaviorGoToPageSelector() {
|
||||
clickPreference(R.string.user_interface_label);
|
||||
clickPreference(R.string.pref_back_button_behavior_title);
|
||||
onView(withText(R.string.back_button_go_to_page)).perform(click());
|
||||
onView(withText(R.string.queue_label)).perform(click());
|
||||
onView(withText(R.string.confirm_label)).perform(click());
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> UserPreferences.getBackButtonBehavior() == UserPreferences.BackButtonBehavior.GO_TO_PAGE);
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> UserPreferences.getBackButtonGoToPage().equals(QueueFragment.TAG));
|
||||
clickPreference(R.string.pref_back_button_behavior_title);
|
||||
onView(withText(R.string.back_button_go_to_page)).perform(click());
|
||||
onView(withText(R.string.episodes_label)).perform(click());
|
||||
onView(withText(R.string.confirm_label)).perform(click());
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> UserPreferences.getBackButtonBehavior() == UserPreferences.BackButtonBehavior.GO_TO_PAGE);
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> UserPreferences.getBackButtonGoToPage().equals(AllEpisodesFragment.TAG));
|
||||
clickPreference(R.string.pref_back_button_behavior_title);
|
||||
onView(withText(R.string.back_button_go_to_page)).perform(click());
|
||||
onView(withText(R.string.subscriptions_label)).perform(click());
|
||||
onView(withText(R.string.confirm_label)).perform(click());
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> UserPreferences.getBackButtonBehavior() == UserPreferences.BackButtonBehavior.GO_TO_PAGE);
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
.until(() -> UserPreferences.getBackButtonGoToPage().equals(SubscriptionFragment.TAG));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteRemovesFromQueue() {
|
||||
clickPreference(R.string.storage_pref);
|
||||
clickPreference(R.string.downloads_pref);
|
||||
if (!UserPreferences.shouldDeleteRemoveFromQueue()) {
|
||||
clickPreference(R.string.pref_delete_removes_from_queue_title);
|
||||
Awaitility.await().atMost(1000, MILLISECONDS)
|
||||
|
|
|
@ -5,7 +5,7 @@ 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.danoeh.antennapod.ui.screen.queue.QueueFragment;
|
||||
import de.test.antennapod.EspressoTestUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
|
@ -33,7 +33,7 @@ public class QueueFragmentTest {
|
|||
public void setUp() {
|
||||
EspressoTestUtils.clearPreferences();
|
||||
EspressoTestUtils.clearDatabase();
|
||||
EspressoTestUtils.setLastNavFragment(QueueFragment.TAG);
|
||||
EspressoTestUtils.setLaunchScreen(QueueFragment.TAG);
|
||||
activityRule.launchActivity(new Intent());
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ import static de.test.antennapod.EspressoTestUtils.onDrawerItem;
|
|||
import static de.test.antennapod.EspressoTestUtils.openNavDrawer;
|
||||
import static de.test.antennapod.EspressoTestUtils.waitForView;
|
||||
import static org.hamcrest.CoreMatchers.allOf;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
|
||||
/**
|
||||
* Test UI for feeds that do not have media files
|
||||
|
@ -66,8 +65,8 @@ public class TextOnlyFeedsTest {
|
|||
onDrawerItem(withText(feed.getTitle())).perform(click());
|
||||
onView(withText(feed.getItemAtIndex(0).getTitle())).perform(click());
|
||||
onView(isRoot()).perform(waitForView(withText(R.string.mark_read_no_media_label), 3000));
|
||||
onView(withText(R.string.mark_read_no_media_label)).perform(click());
|
||||
onView(isRoot()).perform(waitForView(allOf(withText(R.string.mark_read_no_media_label), not(isDisplayed())), 3000));
|
||||
onView(allOf(withText(R.string.mark_read_no_media_label), isDisplayed())).perform(click());
|
||||
EspressoTestUtils.waitForViewToDisappear(withText(R.string.mark_read_no_media_label), 3000);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ public class UITestUtils {
|
|||
}
|
||||
}
|
||||
|
||||
private String hostFeed(Feed feed) throws IOException {
|
||||
public String hostFeed(Feed feed) throws IOException {
|
||||
File feedFile = new File(hostedFeedDir, feed.getTitle());
|
||||
FileOutputStream out = new FileOutputStream(feedFile);
|
||||
Rss2Generator generator = new Rss2Generator();
|
||||
|
@ -123,22 +123,23 @@ public class UITestUtils {
|
|||
for (int i = 0; i < NUM_FEEDS; i++) {
|
||||
Feed feed = new Feed(0, null, "Title " + i, "http://example.com/" + i, "Description of feed " + i,
|
||||
"http://example.com/pay/feed" + i, "author " + i, "en", Feed.TYPE_RSS2, "feed" + i, null, null,
|
||||
"http://example.com/feed/src/" + i, false);
|
||||
"http://example.com/feed/src/" + i, System.currentTimeMillis());
|
||||
|
||||
// create items
|
||||
List<FeedItem> items = new ArrayList<>();
|
||||
for (int j = 0; j < NUM_ITEMS_PER_FEED; j++) {
|
||||
FeedItem item = new FeedItem(j, "Feed " + (i+1) + ": Item " + (j+1), "item" + j,
|
||||
FeedItem item = new FeedItem(0, "Feed " + (i+1) + ": Item " + (j+1), "item" + j,
|
||||
"http://example.com/feed" + i + "/item/" + j, new Date(), FeedItem.UNPLAYED, feed);
|
||||
items.add(item);
|
||||
|
||||
if (!hostTextOnlyFeeds) {
|
||||
File mediaFile = newMediaFile("feed-" + i + "-episode-" + j + ".mp3");
|
||||
item.setMedia(new FeedMedia(j, item, 0, 0, mediaFile.length(), "audio/mp3", null, hostFile(mediaFile), false, null, 0, 0));
|
||||
item.setMedia(new FeedMedia(j, item, 0, 0, mediaFile.length(), "audio/mp3",
|
||||
null, hostFile(mediaFile), 0, null, 0, 0));
|
||||
}
|
||||
}
|
||||
feed.setItems(items);
|
||||
feed.setDownload_url(hostFeed(feed));
|
||||
feed.setDownloadUrl(hostFeed(feed));
|
||||
hostedFeeds.add(feed);
|
||||
}
|
||||
feedDataHosted = true;
|
||||
|
@ -169,14 +170,13 @@ public class UITestUtils {
|
|||
|
||||
List<FeedItem> queue = new ArrayList<>();
|
||||
for (Feed feed : hostedFeeds) {
|
||||
feed.setDownloaded(true);
|
||||
if (downloadEpisodes) {
|
||||
for (FeedItem item : feed.getItems()) {
|
||||
if (item.hasMedia()) {
|
||||
FeedMedia media = item.getMedia();
|
||||
int fileId = Integer.parseInt(StringUtils.substringAfter(media.getDownload_url(), "files/"));
|
||||
media.setFile_url(server.accessFile(fileId).getAbsolutePath());
|
||||
media.setDownloaded(true);
|
||||
int fileId = Integer.parseInt(StringUtils.substringAfter(media.getDownloadUrl(), "files/"));
|
||||
media.setLocalFileUrl(server.accessFile(fileId).getAbsolutePath());
|
||||
media.setDownloaded(true, System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,10 +45,10 @@ public class UITestUtilsTest {
|
|||
assertFalse(feeds.isEmpty());
|
||||
|
||||
for (Feed feed : feeds) {
|
||||
testUrlReachable(feed.getDownload_url());
|
||||
testUrlReachable(feed.getDownloadUrl());
|
||||
for (FeedItem item : feed.getItems()) {
|
||||
if (item.hasMedia()) {
|
||||
testUrlReachable(item.getMedia().getDownload_url());
|
||||
testUrlReachable(item.getMedia().getDownloadUrl());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -77,8 +77,8 @@ public class UITestUtilsTest {
|
|||
assertTrue(item.getMedia().getId() != 0);
|
||||
if (downloadEpisodes) {
|
||||
assertTrue(item.getMedia().isDownloaded());
|
||||
assertNotNull(item.getMedia().getFile_url());
|
||||
File file = new File(item.getMedia().getFile_url());
|
||||
assertNotNull(item.getMedia().getLocalFileUrl());
|
||||
File file = new File(item.getMedia().getLocalFileUrl());
|
||||
assertTrue(file.exists());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
package de.test.antennapod.util.event;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import de.danoeh.antennapod.core.event.DownloadEvent;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Test helper to listen to {@link DownloadEvent} and handle them accordingly.
|
||||
*/
|
||||
public class DownloadEventListener {
|
||||
private final List<DownloadEvent> events = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Provides an listener subscribing to {@link DownloadEvent} that the callers can use.
|
||||
* Note: it uses RxJava's version of {@link Consumer} because it allows exceptions to be thrown.
|
||||
*/
|
||||
public static void withDownloadEventListener(@NonNull Consumer<DownloadEventListener> consumer) throws Exception {
|
||||
DownloadEventListener feedItemEventListener = new DownloadEventListener();
|
||||
try {
|
||||
EventBus.getDefault().register(feedItemEventListener);
|
||||
consumer.accept(feedItemEventListener);
|
||||
} finally {
|
||||
EventBus.getDefault().unregister(feedItemEventListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onEvent(DownloadEvent event) {
|
||||
events.add(event);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public DownloadEvent getLatestEvent() {
|
||||
if (events.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
return events.get(events.size() - 1);
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package de.test.antennapod.util.event;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.event.FeedItemEvent;
|
||||
import io.reactivex.functions.Consumer;
|
||||
|
||||
/**
|
||||
* Test helpers to listen {@link FeedItemEvent} and handle them accordingly
|
||||
*
|
||||
*/
|
||||
public class FeedItemEventListener {
|
||||
private final List<FeedItemEvent> events = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Provides an listener subscribing to {@link FeedItemEvent} that the callers can use
|
||||
*
|
||||
* Note: it uses RxJava's version of {@link Consumer} because it allows exceptions to be thrown.
|
||||
*/
|
||||
public static void withFeedItemEventListener(@NonNull Consumer<FeedItemEventListener> consumer)
|
||||
throws Exception {
|
||||
FeedItemEventListener feedItemEventListener = new FeedItemEventListener();
|
||||
try {
|
||||
EventBus.getDefault().register(feedItemEventListener);
|
||||
consumer.accept(feedItemEventListener);
|
||||
} finally {
|
||||
EventBus.getDefault().unregister(feedItemEventListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onEvent(FeedItemEvent event) {
|
||||
events.add(event);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<? extends FeedItemEvent> getEvents() {
|
||||
return events;
|
||||
}
|
||||
}
|
|
@ -7,9 +7,7 @@ import java.io.IOException;
|
|||
/**
|
||||
* Utility methods for FeedGenerator
|
||||
*/
|
||||
class GeneratorUtil {
|
||||
private GeneratorUtil(){}
|
||||
|
||||
abstract class GeneratorUtil {
|
||||
public static void addPaymentLink(XmlSerializer xml, String paymentLink, boolean withNamespace) throws IOException {
|
||||
String ns = (withNamespace) ? "http://www.w3.org/2005/Atom" : null;
|
||||
xml.startTag(ns, "link");
|
||||
|
|
|
@ -6,13 +6,15 @@ import org.xmlpull.v1.XmlSerializer;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.model.feed.FeedFunding;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.parser.feed.namespace.PodcastIndex;
|
||||
import de.danoeh.antennapod.core.util.DateFormatter;
|
||||
|
||||
/**
|
||||
* Creates RSS 2.0 feeds. See FeedGenerator for more information.
|
||||
|
@ -98,7 +100,7 @@ public class Rss2Generator implements FeedGenerator {
|
|||
}
|
||||
if (item.getPubDate() != null) {
|
||||
xml.startTag(null, "pubDate");
|
||||
xml.text(DateFormatter.formatRfc822Date(item.getPubDate()));
|
||||
xml.text(formatRfc822Date(item.getPubDate()));
|
||||
xml.endTag(null, "pubDate");
|
||||
}
|
||||
if ((flags & FEATURE_WRITE_GUID) != 0) {
|
||||
|
@ -108,9 +110,9 @@ public class Rss2Generator implements FeedGenerator {
|
|||
}
|
||||
if (item.getMedia() != null) {
|
||||
xml.startTag(null, "enclosure");
|
||||
xml.attribute(null, "url", item.getMedia().getDownload_url());
|
||||
xml.attribute(null, "url", item.getMedia().getDownloadUrl());
|
||||
xml.attribute(null, "length", String.valueOf(item.getMedia().getSize()));
|
||||
xml.attribute(null, "type", item.getMedia().getMime_type());
|
||||
xml.attribute(null, "type", item.getMedia().getMimeType());
|
||||
xml.endTag(null, "enclosure");
|
||||
}
|
||||
if (fundingList != null) {
|
||||
|
@ -132,4 +134,9 @@ public class Rss2Generator implements FeedGenerator {
|
|||
|
||||
xml.endDocument();
|
||||
}
|
||||
|
||||
private static String formatRfc822Date(Date date) {
|
||||
SimpleDateFormat format = new SimpleDateFormat("dd MMM yy HH:mm:ss Z", Locale.US);
|
||||
return format.format(date);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
package de.danoeh.antennapod.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
|
||||
public class RatingDialog {
|
||||
|
||||
private RatingDialog(){}
|
||||
|
||||
private static final String TAG = RatingDialog.class.getSimpleName();
|
||||
private static final int AFTER_DAYS = 7;
|
||||
|
||||
private static WeakReference<Context> mContext;
|
||||
private static SharedPreferences mPreferences;
|
||||
private static Dialog mDialog;
|
||||
|
||||
private static final String PREFS_NAME = "RatingPrefs";
|
||||
private static final String KEY_RATED = "KEY_WAS_RATED";
|
||||
private static final String KEY_FIRST_START_DATE = "KEY_FIRST_HIT_DATE";
|
||||
|
||||
public static void init(Context context) {
|
||||
mContext = new WeakReference<>(context);
|
||||
mPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
|
||||
long firstDate = mPreferences.getLong(KEY_FIRST_START_DATE, 0);
|
||||
if (firstDate == 0) {
|
||||
resetStartDate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void check() {
|
||||
if (mDialog != null && mDialog.isShowing()) {
|
||||
return;
|
||||
}
|
||||
if (shouldShow()) {
|
||||
try {
|
||||
mDialog = createDialog();
|
||||
if (mDialog != null) {
|
||||
mDialog.show();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void rateNow() {
|
||||
Context context = mContext.get();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
IntentUtils.openInBrowser(context, "https://play.google.com/store/apps/details?id=de.danoeh.antennapod");
|
||||
saveRated();
|
||||
}
|
||||
|
||||
private static boolean rated() {
|
||||
return mPreferences.getBoolean(KEY_RATED, false);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void saveRated() {
|
||||
mPreferences
|
||||
.edit()
|
||||
.putBoolean(KEY_RATED, true)
|
||||
.apply();
|
||||
}
|
||||
|
||||
private static void resetStartDate() {
|
||||
mPreferences
|
||||
.edit()
|
||||
.putLong(KEY_FIRST_START_DATE, System.currentTimeMillis())
|
||||
.apply();
|
||||
}
|
||||
|
||||
private static boolean shouldShow() {
|
||||
if (rated()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long firstDate = mPreferences.getLong(KEY_FIRST_START_DATE, now);
|
||||
long diff = now - firstDate;
|
||||
long diffDays = TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
|
||||
return diffDays >= AFTER_DAYS;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static AlertDialog createDialog() {
|
||||
Context context = mContext.get();
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
return new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.rating_title)
|
||||
.setMessage(R.string.rating_message)
|
||||
.setPositiveButton(R.string.rating_now_label, (dialog, which) -> rateNow())
|
||||
.setNegativeButton(R.string.rating_never_label, (dialog, which) -> saveRated())
|
||||
.setNeutralButton(R.string.rating_later_label, (dialog, which) -> resetStartDate())
|
||||
.setOnCancelListener(dialog1 -> resetStartDate())
|
||||
.create();
|
||||
}
|
||||
}
|
|
@ -1,15 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="de.danoeh.antennapod"
|
||||
android:installLocation="auto">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
|
@ -25,15 +28,12 @@
|
|||
android:name="android.hardware.touchscreen"
|
||||
android:required="false"/>
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
|
||||
<application
|
||||
android:name="de.danoeh.antennapod.PodcastApp"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:backupAgent=".core.backup.OpmlBackupAgent"
|
||||
android:backupAgent=".storage.importexport.OpmlBackupAgent"
|
||||
android:restoreAnyVersion="true"
|
||||
android:theme="@style/Theme.AntennaPod.Splash"
|
||||
android:usesCleartextTraffic="true"
|
||||
|
@ -44,7 +44,7 @@
|
|||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
<activity
|
||||
android:name=".activity.PlaybackSpeedDialogActivity"
|
||||
android:name=".ui.screen.playback.PlaybackSpeedDialogActivity"
|
||||
android:noHistory="true"
|
||||
android:exported="false"
|
||||
android:excludeFromRecents="true"
|
||||
|
@ -76,7 +76,6 @@
|
|||
|
||||
<activity
|
||||
android:name=".activity.SplashActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
|
@ -99,7 +98,6 @@
|
|||
android:configChanges="keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout|density|uiMode|keyboard|navigation"
|
||||
android:windowSoftInputMode="stateAlwaysHidden"
|
||||
android:launchMode="singleTask"
|
||||
android:label="@string/app_name"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
@ -111,53 +109,38 @@
|
|||
android:host="antennapod.org"
|
||||
android:pathPrefix="/deeplink/main"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="antennapod.org"
|
||||
android:pathPrefix="/deeplink/search"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="de.danoeh.antennapod.intents.MAIN_ACTIVITY" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<action android:name="de.danoeh.antennapod.intents.MAIN_ACTIVITY" />
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activity.DownloadAuthenticationActivity"
|
||||
android:theme="@style/Theme.AntennaPod.Dark.Translucent"
|
||||
android:launchMode="singleInstance"/>
|
||||
|
||||
<activity
|
||||
android:name=".activity.PreferenceActivity"
|
||||
android:name=".ui.screen.preferences.PreferenceActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:exported="false"
|
||||
android:label="@string/settings_label">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="de.danoeh.antennapod.activity.MainActivity"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activity.WidgetConfigActivity"
|
||||
android:label="@string/widget_settings"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGUR"/>
|
||||
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver
|
||||
android:name=".core.receiver.PlayerWidget"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
<action android:name="de.danoeh.antennapod.FORCE_WIDGET_UPDATE"/>
|
||||
<action android:name="de.danoeh.antennapod.STOP_WIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/player_widget_info"/>
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:name=".activity.OpmlImportActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
|
@ -180,20 +163,19 @@
|
|||
<data android:scheme="https"/>
|
||||
|
||||
<data android:host="*"/>
|
||||
<data android:pathPattern=".*.xml" />
|
||||
<data android:pathPattern=".*.opml" />
|
||||
<data android:pathPattern="/.*.opml" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.BugReportActivity"
|
||||
android:name=".ui.screen.preferences.BugReportActivity"
|
||||
android:label="@string/bug_report_title">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="de.danoeh.antennapod.activity.PreferenceActivity"/>
|
||||
android:value="de.danoeh.antennapod.ui.screen.preferences.PreferenceActivity"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activity.VideoplayerActivity"
|
||||
android:name=".ui.screen.playback.video.VideoplayerActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
|
@ -208,7 +190,7 @@
|
|||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activity.OnlineFeedViewActivity"
|
||||
android:name=".ui.screen.onlinefeedview.OnlineFeedViewActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:theme="@style/Theme.AntennaPod.Dark.Translucent"
|
||||
android:label="@string/add_feed_label"
|
||||
|
@ -227,9 +209,9 @@
|
|||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:host="*"/>
|
||||
<data android:pathPattern=".*\\.xml"/>
|
||||
<data android:pathPattern=".*\\.rss"/>
|
||||
<data android:pathPattern=".*\\.atom"/>
|
||||
<data android:pathPattern="/.*\\.xml"/>
|
||||
<data android:pathPattern="/.*\\.rss"/>
|
||||
<data android:pathPattern="/.*\\.atom"/>
|
||||
</intent-filter>
|
||||
|
||||
<!-- Feedburner URLs -->
|
||||
|
@ -282,7 +264,7 @@
|
|||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:pathPattern=".*\\..*/.*" />
|
||||
<data android:pathPattern="/.*\\..*/.*" />
|
||||
<data android:host="subscribeonandroid.com" />
|
||||
<data android:host="www.subscribeonandroid.com" />
|
||||
<data android:host="*subscribeonandroid.com" />
|
||||
|
@ -323,11 +305,15 @@
|
|||
<data android:mimeType="text/plain"/>
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<action android:name="de.danoeh.antennapod.intents.ONLINE_FEEDVIEW" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".activity.SelectSubscriptionActivity"
|
||||
android:label="@string/shortcut_subscription_label"
|
||||
android:icon="@drawable/ic_folder_shortcut"
|
||||
android:icon="@drawable/ic_shortcut_subscriptions"
|
||||
android:theme="@style/Theme.AntennaPod.Dark.Translucent"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
|
@ -336,24 +322,7 @@
|
|||
</activity>
|
||||
|
||||
<receiver
|
||||
android:name=".receiver.ConnectivityActionReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".receiver.PowerConnectionReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
|
||||
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".receiver.SPAReceiver"
|
||||
android:name=".spa.SPAReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="de.danoeh.antennapdsp.intent.SP_APPS_QUERY_FEEDS_RESPONSE"/>
|
||||
|
@ -371,6 +340,7 @@
|
|||
</provider>
|
||||
|
||||
<meta-data
|
||||
tools:ignore="Deprecated"
|
||||
android:name="com.google.android.actions"
|
||||
android:resource="@xml/actions" />
|
||||
</application>
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
# this file is generated automatically
|
||||
about.html
|
||||
LICENSE.txt
|
||||
CONTRIBUTORS.txt
|
|
@ -1,18 +0,0 @@
|
|||
Copyright 2015 Joan Zapata
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
It uses FontAwesome font, licensed under OFL 1.1, which is compatible
|
||||
with this library's license.
|
||||
|
||||
http://scripts.sil.org/cms/scripts/render_download.php?format=file&media_id=OFL_plaintext&filename=OFL.txt
|
|
@ -1,13 +0,0 @@
|
|||
Copyright (C) 2016 Shota Saito
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -1,208 +0,0 @@
|
|||
ByteHamster;5811634;Maintainer
|
||||
danieloeh;968613;Original creator of AntennaPod (retired)
|
||||
mfietz;6860662;Maintainer (retired)
|
||||
TomHennen;5216560;Maintainer (retired)
|
||||
orionlee;250644;Contributor
|
||||
domingos86;9538859;Contributor
|
||||
TacoTheDank;32376686;Contributor
|
||||
tonytamsf;149837;Contributor
|
||||
damoasda;46045854;Contributor
|
||||
andersonvom;69922;Contributor
|
||||
shortspider;5712543;Contributor
|
||||
spacecowboy;223655;Contributor
|
||||
ebraminio;833473;Contributor
|
||||
asdoi;36813904;Contributor
|
||||
patheticpat;16046;Contributor
|
||||
brad;1614;Contributor
|
||||
Cj-Malone;10121513;Contributor
|
||||
maxbechtold;9162198;Contributor
|
||||
keunes;11229646;Maintainer
|
||||
gaul;848247;Contributor
|
||||
qkolj;6667105;Contributor
|
||||
pachecosf;46357909;Contributor
|
||||
gerardolgvr;20119298;Contributor
|
||||
johnjohndoe;144518;Contributor
|
||||
hannesa2;3314607;Contributor
|
||||
bws9000;262625;Contributor
|
||||
ahangarha;11241315;Contributor
|
||||
rharriso;570910;Contributor
|
||||
xgouchet;818706;Contributor
|
||||
peakvalleytech;65185819;Contributor
|
||||
sevenmaster;12869538;Contributor
|
||||
TheRealFalcon;153674;Contributor
|
||||
Slinger;75751;Contributor
|
||||
vbh;56578479;Contributor
|
||||
jas14;569991;Contributor
|
||||
udif;809640;Contributor
|
||||
malockin;12814657;Contributor
|
||||
jonasburian;15125616;Contributor
|
||||
dirkmueller;1029152;Contributor
|
||||
jatinkumarg;20503830;Contributor
|
||||
peschmae0;4450993;Contributor
|
||||
orelogo;15976578;Contributor
|
||||
txtd;7108931;Contributor
|
||||
ydinath;4193331;Contributor
|
||||
CedricCabessa;365097;Contributor
|
||||
mchelen;30691;Contributor
|
||||
dethstar;1239177;Contributor
|
||||
drabux;10663142;Contributor
|
||||
saqura;1935380;Contributor
|
||||
ueen;5067479;Contributor
|
||||
binarytoto;75904760;Contributor
|
||||
bibz;5141956;Contributor
|
||||
hzulla;1705654;Contributor
|
||||
deandreamatias;21011641;Contributor
|
||||
MeirAtIMDDE;4421079;Contributor
|
||||
cketti;218061;Contributor
|
||||
egsavage;126165;Contributor
|
||||
ligi;111600;Contributor
|
||||
Xeitor;8825715;Contributor
|
||||
jhenninger;197274;Contributor
|
||||
dreiss;4121;Contributor
|
||||
liesen;26872;Contributor
|
||||
nereocystis;2257107;Contributor
|
||||
rezanejati;16049370;Contributor
|
||||
thrillfall;15801468;Contributor
|
||||
twiceyuan;2619800;Contributor
|
||||
JessieVela;33134794;Contributor
|
||||
HaBaLeS;730902;Contributor
|
||||
volhol;11587858;Contributor
|
||||
michaelmwhite;28901334;Contributor
|
||||
CameronBanga;611354;Contributor
|
||||
HrBDev;25826502;Contributor
|
||||
HolgerJeromin;2410353;Contributor
|
||||
xisberto;1914956;Contributor
|
||||
jmue;898577;Contributor
|
||||
katrinleinweber;9948149;Contributor
|
||||
LatinSuD;451487;Contributor
|
||||
24hours;650407;Contributor
|
||||
SosoTughushi;19908097;Contributor
|
||||
Thom-Merrilin;76849828;Contributor
|
||||
fabolhak;20029691;Contributor
|
||||
archibishop;36948493;Contributor
|
||||
alifeflow;24603829;Contributor
|
||||
avirajrsingh;69088913;Contributor
|
||||
toggles;14695;Contributor
|
||||
connectety;26038710;Contributor
|
||||
matdb;48329535;Contributor
|
||||
damlayildiz;56313500;Contributor
|
||||
kingargyle;177042;Contributor
|
||||
dsmith47;14109426;Contributor
|
||||
FarzanKh;14272565;Contributor
|
||||
hannesaa2;18496079;Contributor
|
||||
myslok;2098329;Contributor
|
||||
jhunnius;9149031;Contributor
|
||||
a1291762;327162;Contributor
|
||||
ShadowIce;59123;Contributor
|
||||
Niffler;8172446;Contributor
|
||||
raghulj;57007;Contributor
|
||||
raghulrm;5362986;Contributor
|
||||
mamehacker;16738348;Contributor
|
||||
skitt;2128935;Contributor
|
||||
wseemann;2296196;Contributor
|
||||
datavizard;44409076;Contributor
|
||||
markamaze;17114678;Contributor
|
||||
mohitshah3111999;42018918;Contributor
|
||||
moralesg;14352147;Contributor
|
||||
mr-intj;6268767;Contributor
|
||||
tamizh143;50977879;Contributor
|
||||
tuxayo;2678215;Contributor
|
||||
alimemonzx;44647595;Contributor
|
||||
dev-darrell;52300159;Contributor
|
||||
jmdouglas;10855634;Contributor
|
||||
olivoto;15932680;Contributor
|
||||
PtilopsisLeucotis;54054883;Contributor
|
||||
abhinavg1997;60095795;Contributor
|
||||
adrns;13379985;Contributor
|
||||
alanorth;191754;Contributor
|
||||
alexte;7724992;Contributor
|
||||
andrey-krutov;1488973;Contributor
|
||||
arantius;84729;Contributor
|
||||
BoJacobs;25435640;Contributor
|
||||
chetan882777;36985543;Contributor
|
||||
chrissicool;232590;Contributor
|
||||
britiger;2057760;Contributor
|
||||
cszucko;1810383;Contributor
|
||||
CWftw;1498303;Contributor
|
||||
danielm5;66779;Contributor
|
||||
ariedov;958646;Contributor
|
||||
brettle;118192;Contributor
|
||||
cdhiraj40;75211982;Contributor
|
||||
dhruvpatidar359;103873587;Contributor
|
||||
edwinhere;19705425;Contributor
|
||||
eirikv;4076243;Contributor
|
||||
eerden;277513;Contributor
|
||||
Geist5000;37940313;Contributor
|
||||
IordanisKokk;72551397;Contributor
|
||||
jklippel;8657220;Contributor
|
||||
jannic;232606;Contributor
|
||||
Foso;5015532;Contributor
|
||||
JonOfUs;11487762;Contributor
|
||||
CreamyCookie;3063858;Contributor
|
||||
Kaligule;3586246;Contributor
|
||||
kvithayathil;1056073;Contributor
|
||||
luiscruz;1080714;Contributor
|
||||
MStrecke;5202211;Contributor
|
||||
mlasson;5814258;Contributor
|
||||
schwedenmut;9077622;Contributor
|
||||
M-arcel;56698158;Contributor
|
||||
mgborowiec;29843126;Contributor
|
||||
mo;7117;Contributor
|
||||
mdeveloper20;2319126;Contributor
|
||||
Mchoi8;45410115;Contributor
|
||||
Gaffen;718125;Contributor
|
||||
mschuetz;108637;Contributor
|
||||
max-wittig;6639323;Contributor
|
||||
Mengshi24;58278376;Contributor
|
||||
MolarAmbiguity;10541979;Contributor
|
||||
mounirlamouri;573590;Contributor
|
||||
nicoolasj;63880378;Contributor
|
||||
nikhil097;35090769;Contributor
|
||||
nproth;48482306;Contributor
|
||||
oliver;2344;Contributor
|
||||
panoreak;25068506;Contributor
|
||||
patrickjkennedy;8617261;Contributor
|
||||
pganssle;1377457;Contributor
|
||||
ortylp;470439;Contributor
|
||||
RafaelBod;77226971;Contributor
|
||||
ramzan;55637406;Contributor
|
||||
iamrichR;44210678;Contributor
|
||||
SamWhited;512573;Contributor
|
||||
SebiderSushi;23618858;Contributor
|
||||
selivan;1208989;Contributor
|
||||
sonnayasomnambula;7716779;Contributor
|
||||
sethoscope;534043;Contributor
|
||||
shantanahardy;26757164;Contributor
|
||||
shombando;42972338;Contributor
|
||||
Silverwarriorin;46795935;Contributor
|
||||
danners;116551;Contributor
|
||||
corecode;177979;Contributor
|
||||
vimsick;20211590;Contributor
|
||||
lyallemma;25173082;Contributor
|
||||
edent;837136;Contributor
|
||||
atrus6;357881;Contributor
|
||||
Toover;8531603;Contributor
|
||||
heyyviv;56256802;Contributor
|
||||
waylife;3348620;Contributor
|
||||
yarons;406826;Contributor
|
||||
agibault;15703733;Contributor
|
||||
amhokies;3124968;Contributor
|
||||
andrewc1;19559401;Contributor
|
||||
axq;5077221;Contributor
|
||||
chrk2205;44704035;Contributor
|
||||
e-t-l;40775958;Contributor
|
||||
fossterer;4236021;Contributor
|
||||
sak96;26397224;Contributor
|
||||
gregoryjtom;32783177;Contributor
|
||||
lightonflux;1377943;Contributor
|
||||
loucasal;25279797;Contributor
|
||||
minusf;3632883;Contributor
|
||||
NWuensche;15856197;Contributor
|
||||
rubenh-be;22374542;Contributor
|
||||
s3lph;5564491;Contributor
|
||||
silansuslu;72400543;Contributor
|
||||
struggggle;19150666;Contributor
|
||||
tamizh138;26201258;Contributor
|
||||
thomasdomingos;16108830;Contributor
|
||||
trevortabaka;1552990;Contributor
|
||||
zawad2221;32180355;Contributor
|
|
|
@ -1,153 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<libraries>
|
||||
<library
|
||||
name="AntennaPod"
|
||||
author="The AntennaPod team"
|
||||
website="https://github.com/AntennaPod/AntennaPod"
|
||||
license="GPL-3.0"
|
||||
licenseText="LICENSE.txt" />
|
||||
<library
|
||||
name="AntennaPod-AudioPlayer"
|
||||
author="The AntennaPod team"
|
||||
website="https://github.com/AntennaPod/AntennaPod-AudioPlayer"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="Android Jetpack"
|
||||
author="Google"
|
||||
website="https://developer.android.com/jetpack"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="Apache Commons"
|
||||
author="The Apache Software Foundation"
|
||||
website="https://commons.apache.org/"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="Balloon"
|
||||
author="Jaewoong Eum"
|
||||
website="https://github.com/skydoves/Balloon"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="Conscrypt"
|
||||
author="Google"
|
||||
website="https://github.com/google/conscrypt"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="EventBus"
|
||||
author="greenrobot"
|
||||
website="https://github.com/greenrobot/EventBus"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="ExoPlayer"
|
||||
author="Google"
|
||||
website="https://github.com/google/ExoPlayer"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="Floating Action Button Speed Dial"
|
||||
author="Roberto Leinardi"
|
||||
website="https://github.com/leinardi/FloatingActionButtonSpeedDial"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="fyydlin"
|
||||
author="Martin Fietz"
|
||||
website="https://github.com/mfietz/fyydlin"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="Glide"
|
||||
author="bumptech"
|
||||
website="https://github.com/bumptech/glide"
|
||||
license="Simplified BSD"
|
||||
licenseText="LICENSE_GLIDE.txt" />
|
||||
<library
|
||||
name="Iconify"
|
||||
author="Joan Zapata"
|
||||
website="https://github.com/JoanZapata/android-iconify"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_ANDROID_ICONIFY.txt" />
|
||||
<library
|
||||
name="jsoup"
|
||||
author="Jonathan Hedley"
|
||||
website="https://jsoup.org/"
|
||||
license="MIT"
|
||||
licenseText="LICENSE_JSOUP.txt" />
|
||||
<library
|
||||
name="Lightweight-Stream-API"
|
||||
author="Victor Melnik"
|
||||
website="https://github.com/aNNiMON/Lightweight-Stream-API"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="Material Components for Android"
|
||||
author="Google"
|
||||
website="https://github.com/material-components/material-components-android"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="Material Design Icons"
|
||||
author="Google"
|
||||
website="https://github.com/google/material-design-icons"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="Material Design Icons"
|
||||
author="Templarian"
|
||||
website="https://github.com/Templarian/MaterialDesign"
|
||||
license="Pictogrammers Free License"
|
||||
licenseText="LICENSE_PICTOGRAMMERS.txt" />
|
||||
<library
|
||||
name="OkHttp"
|
||||
author="Square"
|
||||
website="https://github.com/square/okhttp"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_OKHTTP.txt" />
|
||||
<library
|
||||
name="Okio"
|
||||
author="Square"
|
||||
website="https://github.com/square/okio"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="RecyclerViewSwipeDecorator"
|
||||
author="Paolo Montalto"
|
||||
website="https://github.com/xabaras/RecyclerViewSwipeDecorator"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="RxAndroid"
|
||||
author="ReactiveX"
|
||||
website="https://github.com/ReactiveX/RxAndroid"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="RxJava"
|
||||
author="ReactiveX"
|
||||
website="https://github.com/ReactiveX/RxJava"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="SearchPreference"
|
||||
author="ByteHamster"
|
||||
website="https://github.com/ByteHamster/SearchPreference"
|
||||
license="MIT"
|
||||
licenseText="LICENSE_SEARCHPREFERENCE.txt" />
|
||||
<library
|
||||
name="StackBlur"
|
||||
author="Enrique López Mañas"
|
||||
website="https://github.com/kikoso/android-stackblur"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_APACHE-2.0.txt" />
|
||||
<library
|
||||
name="Triangle Label View"
|
||||
author="Shota Saito"
|
||||
website="https://github.com/shts/TriangleLabelView"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_TRIANGLE_LABEL_VIEW.txt" />
|
||||
</libraries>
|
|
@ -1,4 +0,0 @@
|
|||
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,49 +0,0 @@
|
|||
Arabic;abuzar3.khalid, AhmedHll, badarotti, HeshamTB, keunes, Mehyar, mh.abdelhay, mhamade, moftasa, mohmans, MustafaAlgurabi, nabilMaghura, rex07, shubbar
|
||||
Asturian (ast_ES);enolp, keunes
|
||||
Azerbaijani;5NOER227O, xxmn77
|
||||
Basque;bipoza, gaztainalde, IngrownMink4, keunes, Osoitz, pospolos
|
||||
Bengali;laggybird
|
||||
Breton;Belvar, Eorn, Iriep, keunes, technozuzici
|
||||
Bulgarian;keunes, ma4ko, ppk89, solusitor, x7ype
|
||||
Catalan;arseru, badlop, bluegeekgh, carles.llacer, dvd1985, exort12, IvanAmarante, javiercoll, keunes, Kintu, lambdani, marcmetallextrem, xc70
|
||||
Chinese (zh_CN);135e2, Biacke, brnme, claybiockiller, clong289734997, cyril3, Felix2yu, gaohongyuan, Guaidaodl, Huck0, iconteral, jhxie, jxj2zzz79pfp9bpo, JY3, 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, befeleme, elich, Hanzmeister, jjh, McLenin666, md.share, ShimonH, svetlemodry, Thomaash, viotalJiplk
|
||||
Danish;deusdenton, ERYpTION, JFreak, jhertel, keunes, mikini, petterbejo, SebastianKiwiDk
|
||||
Dutch;e2jk, keunes, mijnheer, oldblue, rwv, Vistaus
|
||||
Estonian;beez276, Eraser, keunes, mahfiaz
|
||||
Finnish;Ban3, keunes, ktstmu, Kuutar, noppa, Sahtor, scop, teemue
|
||||
French;5NOER227O, ayiniho, ChaoticMind, clombion, Cornegidouille, Daremo, e2jk, keunes, klintom, Kuscoo, lacouture, LouFex, Matth78, petterbejo, PierreLaville, Poussinou, RomainTT, sterylmreep, teamon
|
||||
Galician;antiparvos, pikamoku, Raichely
|
||||
German;5NOER227O, _Er, axre, ByteHamster, Ceekay, ceving, dadosch, datesastick, DerSilly, elkangaroo, enz, Erc187, f_grubm, finsterwalder, forght, hbilke, HolgerJeromin, JMAN, JoeMcFly, jokap, JoniArida, JonOfUs, kalei, keunes, Kostas_F, Macusercom, max.wittig, mfietz, Michael_Strecke, mkida, petterbejo, pudeeh, Quiss42, repat, sadfgdf, Sargon_Isa, teamon, thetrash23, timo.rohwedder, toaskoas, Tobiasff3200, tomte, Tonne11, tweimer, VfBFan, Willhelm, ypid
|
||||
Hebrew (he_IL);amir.dafnyman, E1i9, mongoose4004, pinkasey, rellieberman, Yaron
|
||||
Hindi (hi_IN);keunes, purple.coder, rajs1942, siddhusengar, singhrishi245021, thelazyoxymoron
|
||||
Hu;hurrikan, keunes, lna91, lomapur, marthynw, meskobalazs, naren93
|
||||
Icelandic;keunes, marthjod
|
||||
Indonesian;dbrw, justch, keunes, levirs565, liimee
|
||||
Italian (it_IT);aalex70, allin, alvami, atilluF, Bonnee, datesastick, dontknowcris, giuseppep, Guybrush88, ilmanzo, juanjom, keunes, lu.por, m.chinni, marco_pag, mat650, mircocau, neonsoftware, niccord, salorock, theloca95
|
||||
Japanese;ayiniho, keunes, KotaKato, Naofumi, sh3llc4t, tko_cactus, TranslatorG
|
||||
Kannada (kn_IN);chiraag.nataraj, deepu2, keunes, thejeshgn
|
||||
Ko;changwoo, eshc123, keunes, libliboom
|
||||
Latin;nivaca
|
||||
Lithuanian;keunes, naglis, Sharper
|
||||
Macedonian;krisfremen
|
||||
Malayalam;joice, keunes, KiranS, rashivkp
|
||||
Modern Greek (1453-);AnimaRain, antonist, keunes, Kostas_F, pavlosv, pcguy23
|
||||
Norwegian Bokmål (nb_NO);abstrakct, ahysing, bablecopherye, corkie, forteller, heraldo, jakobkg, Jamiera, keunes, kongk, sevenmaster, tc5, timbast, TrymSan, ttick
|
||||
Persian;ahangarha, danialbehzadi, ebadi, ebraminio, F7D, hamidrezabayat76, K2latmanesh, keunes, sinamoghaddas
|
||||
Polish (pl_PL);befeleme, ewm, Gadzinisko, hiro2020, Iwangelion, kamila.miodek1991, keunes, lomapur, mandlus, maniexx, Mephistofeles, millup, Rakowy_Manaska, shark103, tyle
|
||||
Portuguese;emansije, jmelo461, keunes, lecalam, smarquespt, WalkerPt
|
||||
Portuguese (pt_BR);alexupits, alysonborges, amalvarenga, andersonvom, aracnus, arua, bandreghetti, caioau, carlo_valente, castrors, jmelo461, keunes, lipefire, mbaltar, olivoto, philosp, rogervezaro, RubeensVinicius, SamWilliam, tepadilha, tschertel, ziul123
|
||||
Romanian (ro_RO);AdrianMirica, fuzzmz, keunes, mozartro, ralienpp
|
||||
Russian (ru_RU);ashed, btimofeev, Duke_Raven, flexagoon, gammja, homocomputeris, IgorPolyakov, keunes, mercutiy, nachoman, null, overmind88, Platun0v, PtilopsisLeucotis, s.chebotar, tepxd, un_logic, Vladryyu, whereisthetea, yako
|
||||
Slovak;ati3, jose1711, keunes, marulinko, McLenin666, real_name, tiborepcek
|
||||
Slovenian (sl_SI);anzepintar, asovic, keunes, panter23, TheFireFighter, trus2
|
||||
Spanish;5NOER227O, AleksSyntek, andersonvom, andrespelaezp, arseru, Atreyu94, badlop, CaeM0R, carlos.levy, cartojo, deandreamatias, delthia, devarops, dvd1985, elojodepajaro, Fitoschido, frandavid100, hard_ware, javiercoll, keunes, kiekie, LatinSuD, leogrignafini, meanderingDot, nivaca, rafael.osuna, technozuzici, tres.14159, vfmatzkin, victorzequeida96, wakutiteo, ziul123
|
||||
Swahili (macrolanguage);1silvester, keunes, kmtra
|
||||
Swedish (sv_SE);aiix, bittin, bpnilsson, keunes, LinAGKar, nilso, TwoD, victorhggqvst
|
||||
Tatar;seber
|
||||
Telugu;keunes, veeven
|
||||
Turkish;AhmedDuran, alianilkocak, AliGaygisiz, androtuna, archixe, brsata, Erdy, keunes, overbite, Slsdem
|
||||
Ukrainian (uk_UA);hishak, keunes, older, paul_sm, sergiyr, voinovich_vyacheslav, zhenya97
|
||||
Vietnamese;abnvolk, bruhwut, keunes, ppanhh
|
|
|
@ -1,5 +0,0 @@
|
|||
en
|
||||
fr
|
||||
nl
|
||||
it
|
||||
da
|
|
@ -5,7 +5,6 @@ import android.util.AttributeSet;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
|
@ -29,7 +28,7 @@ public class ViewPagerBottomSheetBehavior<V extends View> extends BottomSheetBeh
|
|||
|
||||
@Override
|
||||
View findScrollingChild(View view) {
|
||||
if (ViewCompat.isNestedScrollingEnabled(view)) {
|
||||
if (view.isNestedScrollingEnabled()) {
|
||||
return view;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package de.danoeh.antennapod;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import de.danoeh.antennapod.net.download.service.episode.autodownload.AutoDownloadManagerImpl;
|
||||
import de.danoeh.antennapod.net.download.service.feed.FeedUpdateManagerImpl;
|
||||
import de.danoeh.antennapod.net.download.serviceinterface.AutoDownloadManager;
|
||||
import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager;
|
||||
import de.danoeh.antennapod.net.sync.service.SyncService;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink;
|
||||
import de.danoeh.antennapod.storage.preferences.SynchronizationSettings;
|
||||
import de.danoeh.antennapod.storage.preferences.SynchronizationCredentials;
|
||||
import de.danoeh.antennapod.storage.preferences.PlaybackPreferences;
|
||||
import de.danoeh.antennapod.storage.preferences.SleepTimerPreferences;
|
||||
import de.danoeh.antennapod.storage.preferences.UsageStatistics;
|
||||
import de.danoeh.antennapod.net.common.UserAgentInterceptor;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.net.common.AntennapodHttpClient;
|
||||
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
|
||||
import de.danoeh.antennapod.net.download.service.feed.DownloadServiceInterfaceImpl;
|
||||
import de.danoeh.antennapod.net.common.NetworkUtils;
|
||||
import de.danoeh.antennapod.net.ssl.SslProviderInstaller;
|
||||
import de.danoeh.antennapod.storage.database.PodDBAdapter;
|
||||
|
||||
import de.danoeh.antennapod.ui.notifications.NotificationUtils;
|
||||
import java.io.File;
|
||||
|
||||
public class ClientConfigurator {
|
||||
private static boolean initialized = false;
|
||||
|
||||
public static synchronized void initialize(Context context) {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||||
UserAgentInterceptor.USER_AGENT = "AntennaPod/" + packageInfo.versionName;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
PodDBAdapter.init(context);
|
||||
UserPreferences.init(context);
|
||||
SynchronizationCredentials.init(context);
|
||||
SynchronizationSettings.init(context);
|
||||
UsageStatistics.init(context);
|
||||
PlaybackPreferences.init(context);
|
||||
SslProviderInstaller.install(context);
|
||||
NetworkUtils.init(context);
|
||||
DownloadServiceInterface.setImpl(new DownloadServiceInterfaceImpl());
|
||||
FeedUpdateManager.setInstance(new FeedUpdateManagerImpl());
|
||||
AutoDownloadManager.setInstance(new AutoDownloadManagerImpl());
|
||||
SynchronizationQueueSink.setServiceStarterImpl(() -> SyncService.sync(context));
|
||||
AntennapodHttpClient.setCacheDirectory(new File(context.getCacheDir(), "okhttp"));
|
||||
AntennapodHttpClient.setProxyConfig(UserPreferences.getProxyConfig());
|
||||
SleepTimerPreferences.init(context);
|
||||
NotificationUtils.createChannels(context);
|
||||
initialized = true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package de.danoeh.antennapod;
|
||||
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import de.danoeh.antennapod.BuildConfig;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
|
||||
public class CrashReportWriter implements Thread.UncaughtExceptionHandler {
|
||||
|
||||
private static final String TAG = "CrashReportWriter";
|
||||
|
||||
private final Thread.UncaughtExceptionHandler defaultHandler;
|
||||
|
||||
public CrashReportWriter() {
|
||||
defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
}
|
||||
|
||||
public static File getFile() {
|
||||
return new File(UserPreferences.getDataFolder(null), "crash-report.log");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread thread, Throwable ex) {
|
||||
write(ex);
|
||||
defaultHandler.uncaughtException(thread, ex);
|
||||
}
|
||||
|
||||
public static void write(Throwable exception) {
|
||||
File path = getFile();
|
||||
PrintWriter out = null;
|
||||
try {
|
||||
out = new PrintWriter(path, "UTF-8");
|
||||
out.println("## Crash info");
|
||||
out.println("Time: " + new SimpleDateFormat("dd-MM-yyyy HH:mm:ss", Locale.getDefault()).format(new Date()));
|
||||
out.println("AntennaPod version: " + BuildConfig.VERSION_NAME);
|
||||
out.println();
|
||||
out.println("## StackTrace");
|
||||
out.println("```");
|
||||
exception.printStackTrace(out);
|
||||
out.println("```");
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, Log.getStackTraceString(e));
|
||||
} finally {
|
||||
IOUtils.closeQuietly(out);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getSystemInfo() {
|
||||
return "## Environment"
|
||||
+ "\nAndroid version: " + Build.VERSION.RELEASE
|
||||
+ "\nOS version: " + System.getProperty("os.version")
|
||||
+ "\nAntennaPod version: " + BuildConfig.VERSION_NAME
|
||||
+ "\nModel: " + Build.MODEL
|
||||
+ "\nDevice: " + Build.DEVICE
|
||||
+ "\nProduct: " + Build.PRODUCT;
|
||||
}
|
||||
}
|
|
@ -1,44 +1,22 @@
|
|||
package de.danoeh.antennapod;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.app.Application;
|
||||
import android.os.StrictMode;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.multidex.MultiDexApplication;
|
||||
import com.joanzapata.iconify.Iconify;
|
||||
import com.joanzapata.iconify.fonts.FontAwesomeModule;
|
||||
import com.joanzapata.iconify.fonts.MaterialModule;
|
||||
import com.google.android.material.color.DynamicColors;
|
||||
|
||||
import de.danoeh.antennapod.activity.SplashActivity;
|
||||
import de.danoeh.antennapod.core.ApCoreEventBusIndex;
|
||||
import de.danoeh.antennapod.core.ClientConfig;
|
||||
import de.danoeh.antennapod.error.CrashReportWriter;
|
||||
import de.danoeh.antennapod.error.RxJavaErrorHandlerSetup;
|
||||
import de.danoeh.antennapod.spa.SPAUtil;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.EventBusException;
|
||||
|
||||
/** Main application class. */
|
||||
public class PodcastApp extends MultiDexApplication {
|
||||
|
||||
// make sure that ClientConfigurator executes its static code
|
||||
static {
|
||||
try {
|
||||
Class.forName("de.danoeh.antennapod.config.ClientConfigurator");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("ClientConfigurator not found", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static PodcastApp singleton;
|
||||
|
||||
public static PodcastApp getInstance() {
|
||||
return singleton;
|
||||
}
|
||||
public class PodcastApp extends Application {
|
||||
private static final String TAG = "PodcastApp";
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler(new CrashReportWriter());
|
||||
RxJavaErrorHandlerSetup.setupRxJavaErrorHandler();
|
||||
|
||||
|
@ -53,28 +31,20 @@ public class PodcastApp extends MultiDexApplication {
|
|||
StrictMode.setVmPolicy(builder.build());
|
||||
}
|
||||
|
||||
singleton = this;
|
||||
|
||||
ClientConfig.initialize(this);
|
||||
|
||||
Iconify.with(new FontAwesomeModule());
|
||||
Iconify.with(new MaterialModule());
|
||||
try {
|
||||
// Robolectric calls onCreate for every test, which causes problems with static members
|
||||
EventBus.builder()
|
||||
.addIndex(new ApEventBusIndex())
|
||||
.logNoSubscriberMessages(false)
|
||||
.sendNoSubscriberEvent(false)
|
||||
.installDefaultEventBus();
|
||||
} catch (EventBusException e) {
|
||||
Log.d(TAG, e.getMessage());
|
||||
}
|
||||
|
||||
DynamicColors.applyToActivitiesIfAvailable(this);
|
||||
ClientConfigurator.initialize(this);
|
||||
PreferenceUpgrader.checkUpgrades(this);
|
||||
SPAUtil.sendSPAppsQueryFeedsIntent(this);
|
||||
EventBus.builder()
|
||||
.addIndex(new ApEventBusIndex())
|
||||
.addIndex(new ApCoreEventBusIndex())
|
||||
.logNoSubscriberMessages(false)
|
||||
.sendNoSubscriberEvent(false)
|
||||
.installDefaultEventBus();
|
||||
}
|
||||
|
||||
public static void forceRestart() {
|
||||
Intent intent = new Intent(getInstance(), SplashActivity.class);
|
||||
ComponentName cn = intent.getComponent();
|
||||
Intent mainIntent = Intent.makeRestartActivityTask(cn);
|
||||
getInstance().startActivity(mainIntent);
|
||||
Runtime.getRuntime().exit(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
package de.danoeh.antennapod;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.view.KeyEvent;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import de.danoeh.antennapod.BuildConfig;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.storage.preferences.SleepTimerPreferences;
|
||||
import de.danoeh.antennapod.CrashReportWriter;
|
||||
import de.danoeh.antennapod.ui.screen.AllEpisodesFragment;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences.EnqueueLocation;
|
||||
import de.danoeh.antennapod.ui.screen.queue.QueueFragment;
|
||||
import de.danoeh.antennapod.ui.swipeactions.SwipeAction;
|
||||
import de.danoeh.antennapod.ui.swipeactions.SwipeActions;
|
||||
|
||||
public class PreferenceUpgrader {
|
||||
private static final String PREF_CONFIGURED_VERSION = "version_code";
|
||||
private static final String PREF_NAME = "app_version";
|
||||
|
||||
private static SharedPreferences prefs;
|
||||
|
||||
public static void checkUpgrades(Context context) {
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences upgraderPrefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
|
||||
int oldVersion = upgraderPrefs.getInt(PREF_CONFIGURED_VERSION, -1);
|
||||
int newVersion = BuildConfig.VERSION_CODE;
|
||||
|
||||
if (oldVersion != newVersion) {
|
||||
CrashReportWriter.getFile().delete();
|
||||
|
||||
upgrade(oldVersion, context);
|
||||
upgraderPrefs.edit().putInt(PREF_CONFIGURED_VERSION, newVersion).apply();
|
||||
}
|
||||
}
|
||||
|
||||
private static void upgrade(int oldVersion, Context context) {
|
||||
if (oldVersion == -1) {
|
||||
//New installation
|
||||
return;
|
||||
}
|
||||
if (oldVersion < 1070196) {
|
||||
// migrate episode cleanup value (unit changed from days to hours)
|
||||
int oldValueInDays = UserPreferences.getEpisodeCleanupValue();
|
||||
if (oldValueInDays > 0) {
|
||||
UserPreferences.setEpisodeCleanupValue(oldValueInDays * 24);
|
||||
} // else 0 or special negative values, no change needed
|
||||
}
|
||||
if (oldVersion < 1070197) {
|
||||
if (prefs.getBoolean("prefMobileUpdate", false)) {
|
||||
prefs.edit().putString("prefMobileUpdateAllowed", "everything").apply();
|
||||
}
|
||||
}
|
||||
if (oldVersion < 1070300) {
|
||||
if (prefs.getBoolean("prefEnableAutoDownloadOnMobile", false)) {
|
||||
UserPreferences.setAllowMobileAutoDownload(true);
|
||||
}
|
||||
switch (prefs.getString("prefMobileUpdateAllowed", "images")) {
|
||||
case "everything":
|
||||
UserPreferences.setAllowMobileFeedRefresh(true);
|
||||
UserPreferences.setAllowMobileEpisodeDownload(true);
|
||||
UserPreferences.setAllowMobileImages(true);
|
||||
break;
|
||||
case "images":
|
||||
UserPreferences.setAllowMobileImages(true);
|
||||
break;
|
||||
case "nothing":
|
||||
UserPreferences.setAllowMobileImages(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (oldVersion < 1070400) {
|
||||
UserPreferences.ThemePreference theme = UserPreferences.getTheme();
|
||||
if (theme == UserPreferences.ThemePreference.LIGHT) {
|
||||
prefs.edit().putString(UserPreferences.PREF_THEME, "system").apply();
|
||||
}
|
||||
|
||||
UserPreferences.setQueueLocked(false);
|
||||
UserPreferences.setStreamOverDownload(false);
|
||||
|
||||
if (!prefs.contains(UserPreferences.PREF_ENQUEUE_LOCATION)) {
|
||||
final String keyOldPrefEnqueueFront = "prefQueueAddToFront";
|
||||
boolean enqueueAtFront = prefs.getBoolean(keyOldPrefEnqueueFront, false);
|
||||
EnqueueLocation enqueueLocation = enqueueAtFront ? EnqueueLocation.FRONT : EnqueueLocation.BACK;
|
||||
UserPreferences.setEnqueueLocation(enqueueLocation);
|
||||
}
|
||||
}
|
||||
if (oldVersion < 2010300) {
|
||||
// Migrate hardware button preferences
|
||||
if (prefs.getBoolean("prefHardwareForwardButtonSkips", false)) {
|
||||
prefs.edit().putString(UserPreferences.PREF_HARDWARE_FORWARD_BUTTON,
|
||||
String.valueOf(KeyEvent.KEYCODE_MEDIA_NEXT)).apply();
|
||||
}
|
||||
if (prefs.getBoolean("prefHardwarePreviousButtonRestarts", false)) {
|
||||
prefs.edit().putString(UserPreferences.PREF_HARDWARE_PREVIOUS_BUTTON,
|
||||
String.valueOf(KeyEvent.KEYCODE_MEDIA_PREVIOUS)).apply();
|
||||
}
|
||||
}
|
||||
if (oldVersion < 2040000) {
|
||||
SharedPreferences swipePrefs = context.getSharedPreferences(SwipeActions.PREF_NAME, Context.MODE_PRIVATE);
|
||||
swipePrefs.edit().putString(SwipeActions.KEY_PREFIX_SWIPEACTIONS + QueueFragment.TAG,
|
||||
SwipeAction.REMOVE_FROM_QUEUE + "," + SwipeAction.REMOVE_FROM_QUEUE).apply();
|
||||
}
|
||||
if (oldVersion < 2050000) {
|
||||
prefs.edit().putBoolean(UserPreferences.PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, true).apply();
|
||||
}
|
||||
if (oldVersion < 2080000) {
|
||||
// Migrate drawer feed counter setting to reflect removal of
|
||||
// "unplayed and in inbox" (0), by changing it to "unplayed" (2)
|
||||
String feedCounterSetting = prefs.getString(UserPreferences.PREF_DRAWER_FEED_COUNTER, "1");
|
||||
if (feedCounterSetting.equals("0")) {
|
||||
prefs.edit().putString(UserPreferences.PREF_DRAWER_FEED_COUNTER, "2").apply();
|
||||
}
|
||||
|
||||
SharedPreferences sleepTimerPreferences =
|
||||
context.getSharedPreferences(SleepTimerPreferences.PREF_NAME, Context.MODE_PRIVATE);
|
||||
TimeUnit[] timeUnits = { TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS };
|
||||
long value = Long.parseLong(SleepTimerPreferences.lastTimerValue());
|
||||
TimeUnit unit = timeUnits[sleepTimerPreferences.getInt("LastTimeUnit", 1)];
|
||||
SleepTimerPreferences.setLastTimer(String.valueOf(unit.toMinutes(value)));
|
||||
|
||||
if (prefs.getString(UserPreferences.PREF_EPISODE_CACHE_SIZE, "20")
|
||||
.equals(context.getString(R.string.pref_episode_cache_unlimited))) {
|
||||
prefs.edit().putString(UserPreferences.PREF_EPISODE_CACHE_SIZE,
|
||||
"" + UserPreferences.EPISODE_CACHE_SIZE_UNLIMITED).apply();
|
||||
}
|
||||
}
|
||||
if (oldVersion < 3000007) {
|
||||
if (prefs.getString("prefBackButtonBehavior", "").equals("drawer")) {
|
||||
prefs.edit().putBoolean(UserPreferences.PREF_BACK_OPENS_DRAWER, true).apply();
|
||||
}
|
||||
}
|
||||
if (oldVersion < 3010000) {
|
||||
if (prefs.getString(UserPreferences.PREF_THEME, "system").equals("2")) {
|
||||
prefs.edit()
|
||||
.putString(UserPreferences.PREF_THEME, "1")
|
||||
.putBoolean(UserPreferences.PREF_THEME_BLACK, true)
|
||||
.apply();
|
||||
}
|
||||
UserPreferences.setAllowMobileSync(true);
|
||||
if (prefs.getString(UserPreferences.PREF_UPDATE_INTERVAL, ":").contains(":")) { // Unset or "time of day"
|
||||
prefs.edit().putString(UserPreferences.PREF_UPDATE_INTERVAL, "12").apply();
|
||||
}
|
||||
}
|
||||
if (oldVersion < 3020000) {
|
||||
NotificationManagerCompat.from(context).deleteNotificationChannel("auto_download");
|
||||
}
|
||||
|
||||
if (oldVersion < 3030000) {
|
||||
SharedPreferences allEpisodesPreferences =
|
||||
context.getSharedPreferences(AllEpisodesFragment.PREF_NAME, Context.MODE_PRIVATE);
|
||||
String oldEpisodeSort = allEpisodesPreferences.getString(UserPreferences.PREF_SORT_ALL_EPISODES, "");
|
||||
if (!StringUtils.isAllEmpty(oldEpisodeSort)) {
|
||||
prefs.edit().putString(UserPreferences.PREF_SORT_ALL_EPISODES, oldEpisodeSort).apply();
|
||||
}
|
||||
|
||||
String oldEpisodeFilter = allEpisodesPreferences.getString("filter", "");
|
||||
if (!StringUtils.isAllEmpty(oldEpisodeFilter)) {
|
||||
prefs.edit().putString(UserPreferences.PREF_FILTER_ALL_EPISODES, oldEpisodeFilter).apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package de.danoeh.antennapod;
|
||||
|
||||
import android.util.Log;
|
||||
import io.reactivex.exceptions.UndeliverableException;
|
||||
import io.reactivex.plugins.RxJavaPlugins;
|
||||
|
||||
public class RxJavaErrorHandlerSetup {
|
||||
private static final String TAG = "RxJavaErrorHandler";
|
||||
|
||||
private RxJavaErrorHandlerSetup() {
|
||||
|
||||
}
|
||||
|
||||
public static void setupRxJavaErrorHandler() {
|
||||
RxJavaPlugins.setErrorHandler(exception -> {
|
||||
if (exception instanceof UndeliverableException) {
|
||||
// Probably just disposed because the fragment was left
|
||||
Log.d(TAG, "Ignored exception: " + Log.getStackTraceString(exception));
|
||||
return;
|
||||
}
|
||||
|
||||
// Usually, undeliverable exceptions are wrapped in an UndeliverableException.
|
||||
// If an undeliverable exception is a NPE (or some others), wrapping does not happen.
|
||||
// AntennaPod threads might throw NPEs after disposing because we set controllers to null.
|
||||
// Just swallow all exceptions here.
|
||||
Log.e(TAG, Log.getStackTraceString(exception));
|
||||
CrashReportWriter.write(exception);
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Thread.currentThread().getUncaughtExceptionHandler()
|
||||
.uncaughtException(Thread.currentThread(), exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package de.danoeh.antennapod.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.storage.database.DBWriter;
|
||||
|
||||
public class CancelDownloadActionButton extends ItemActionButton {
|
||||
|
||||
public CancelDownloadActionButton(FeedItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
public int getLabel() {
|
||||
return R.string.cancel_download_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DrawableRes
|
||||
public int getDrawable() {
|
||||
return R.drawable.ic_cancel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Context context) {
|
||||
FeedMedia media = item.getMedia();
|
||||
DownloadServiceInterface.get().cancel(context, media);
|
||||
if (UserPreferences.isEnableAutodownload()) {
|
||||
item.disableAutoDownload();
|
||||
DBWriter.setFeedItem(item);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package de.danoeh.antennapod.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.storage.database.DBWriter;
|
||||
import de.danoeh.antennapod.ui.view.LocalDeleteModal;
|
||||
|
||||
public class DeleteActionButton extends ItemActionButton {
|
||||
|
||||
public DeleteActionButton(FeedItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
public int getLabel() {
|
||||
return R.string.delete_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DrawableRes
|
||||
public int getDrawable() {
|
||||
return R.drawable.ic_delete;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Context context) {
|
||||
final FeedMedia media = item.getMedia();
|
||||
if (media == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
LocalDeleteModal.showLocalFeedDeleteWarningIfNecessary(context, Collections.singletonList(item),
|
||||
() -> DBWriter.deleteFeedMediaOfItem(context, media));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVisibility() {
|
||||
if (item.getMedia() != null && (item.getMedia().isDownloaded() || item.getFeed().isLocalFeed())) {
|
||||
return View.VISIBLE;
|
||||
}
|
||||
|
||||
return View.INVISIBLE;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package de.danoeh.antennapod.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.storage.preferences.UsageStatistics;
|
||||
import de.danoeh.antennapod.net.common.NetworkUtils;
|
||||
|
||||
public class DownloadActionButton extends ItemActionButton {
|
||||
|
||||
public DownloadActionButton(FeedItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
public int getLabel() {
|
||||
return R.string.download_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DrawableRes
|
||||
public int getDrawable() {
|
||||
return R.drawable.ic_download;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVisibility() {
|
||||
return item.getFeed().isLocalFeed() ? View.INVISIBLE : View.VISIBLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Context context) {
|
||||
final FeedMedia media = item.getMedia();
|
||||
if (media == null || shouldNotDownload(media)) {
|
||||
return;
|
||||
}
|
||||
|
||||
UsageStatistics.logAction(UsageStatistics.ACTION_DOWNLOAD);
|
||||
|
||||
if (NetworkUtils.isEpisodeDownloadAllowed()) {
|
||||
DownloadServiceInterface.get().downloadNow(context, item, false);
|
||||
} else {
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.confirm_mobile_download_dialog_title)
|
||||
.setPositiveButton(R.string.confirm_mobile_download_dialog_download_later,
|
||||
(d, w) -> DownloadServiceInterface.get().downloadNow(context, item, false))
|
||||
.setNeutralButton(R.string.confirm_mobile_download_dialog_allow_this_time,
|
||||
(d, w) -> DownloadServiceInterface.get().downloadNow(context, item, true))
|
||||
.setNegativeButton(R.string.cancel_label, null);
|
||||
if (NetworkUtils.isNetworkRestricted() && NetworkUtils.isVpnOverWifi()) {
|
||||
builder.setMessage(R.string.confirm_mobile_download_dialog_message_vpn);
|
||||
} else {
|
||||
builder.setMessage(R.string.confirm_mobile_download_dialog_message);
|
||||
}
|
||||
|
||||
builder.show();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldNotDownload(@NonNull FeedMedia media) {
|
||||
boolean isDownloading = DownloadServiceInterface.get().isDownloadingEpisode(media.getDownloadUrl());
|
||||
return isDownloading || media.isDownloaded();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package de.danoeh.antennapod.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.ImageView;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import android.view.View;
|
||||
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.playback.service.PlaybackStatus;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
|
||||
public abstract class ItemActionButton {
|
||||
FeedItem item;
|
||||
|
||||
ItemActionButton(FeedItem item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
@StringRes
|
||||
public abstract int getLabel();
|
||||
|
||||
@DrawableRes
|
||||
public abstract int getDrawable();
|
||||
|
||||
public abstract void onClick(Context context);
|
||||
|
||||
public int getVisibility() {
|
||||
return View.VISIBLE;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ItemActionButton forItem(@NonNull FeedItem item) {
|
||||
final FeedMedia media = item.getMedia();
|
||||
if (media == null) {
|
||||
return new MarkAsPlayedActionButton(item);
|
||||
}
|
||||
|
||||
final boolean isDownloadingMedia = DownloadServiceInterface.get().isDownloadingEpisode(media.getDownloadUrl());
|
||||
if (PlaybackStatus.isCurrentlyPlaying(media)) {
|
||||
return new PauseActionButton(item);
|
||||
} else if (item.getFeed().isLocalFeed()) {
|
||||
return new PlayLocalActionButton(item);
|
||||
} else if (media.isDownloaded()) {
|
||||
return new PlayActionButton(item);
|
||||
} else if (isDownloadingMedia) {
|
||||
return new CancelDownloadActionButton(item);
|
||||
} else if (item.getFeed().getState() != Feed.STATE_SUBSCRIBED) {
|
||||
return new StreamActionButton(item);
|
||||
} else if (UserPreferences.isStreamOverDownload()) {
|
||||
return new StreamActionButton(item);
|
||||
} else {
|
||||
return new DownloadActionButton(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void configure(@NonNull View button, @NonNull ImageView icon, Context context) {
|
||||
button.setVisibility(getVisibility());
|
||||
button.setContentDescription(context.getString(getLabel()));
|
||||
button.setOnClickListener((view) -> onClick(context));
|
||||
icon.setImageResource(getDrawable());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package de.danoeh.antennapod.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
import android.view.View;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.storage.database.DBWriter;
|
||||
|
||||
public class MarkAsPlayedActionButton extends ItemActionButton {
|
||||
|
||||
public MarkAsPlayedActionButton(FeedItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
public int getLabel() {
|
||||
return (item.hasMedia() ? R.string.mark_read_label : R.string.mark_read_no_media_label);
|
||||
}
|
||||
|
||||
@Override
|
||||
@DrawableRes
|
||||
public int getDrawable() {
|
||||
return R.drawable.ic_check;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Context context) {
|
||||
if (!item.isPlayed()) {
|
||||
DBWriter.markItemPlayed(item, FeedItem.PLAYED, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVisibility() {
|
||||
return (item.isPlayed()) ? View.INVISIBLE : View.VISIBLE;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package de.danoeh.antennapod.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.KeyEvent;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.playback.service.PlaybackStatus;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.ui.appstartintent.MediaButtonStarter;
|
||||
|
||||
public class PauseActionButton extends ItemActionButton {
|
||||
|
||||
public PauseActionButton(FeedItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
public int getLabel() {
|
||||
return R.string.pause_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DrawableRes
|
||||
public int getDrawable() {
|
||||
return R.drawable.ic_pause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Context context) {
|
||||
FeedMedia media = item.getMedia();
|
||||
if (media == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (PlaybackStatus.isCurrentlyPlaying(media)) {
|
||||
context.sendBroadcast(MediaButtonStarter.createIntent(context, KeyEvent.KEYCODE_MEDIA_PAUSE));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package de.danoeh.antennapod.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.playback.service.PlaybackService;
|
||||
import de.danoeh.antennapod.playback.service.PlaybackServiceStarter;
|
||||
import de.danoeh.antennapod.storage.database.DBWriter;
|
||||
import de.danoeh.antennapod.event.FeedItemEvent;
|
||||
import de.danoeh.antennapod.event.MessageEvent;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.model.playback.MediaType;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
public class PlayActionButton extends ItemActionButton {
|
||||
private static final String TAG = "PlayActionButton";
|
||||
|
||||
public PlayActionButton(FeedItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
public int getLabel() {
|
||||
return R.string.play_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DrawableRes
|
||||
public int getDrawable() {
|
||||
return R.drawable.ic_play_24dp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Context context) {
|
||||
FeedMedia media = item.getMedia();
|
||||
if (media == null) {
|
||||
return;
|
||||
}
|
||||
if (!media.fileExists()) {
|
||||
Log.i(TAG, "Missing episode. Will update the database now.");
|
||||
media.setDownloaded(false, 0);
|
||||
media.setLocalFileUrl(null);
|
||||
DBWriter.setFeedMedia(media);
|
||||
EventBus.getDefault().post(FeedItemEvent.updated(media.getItem()));
|
||||
EventBus.getDefault().post(new MessageEvent(context.getString(R.string.error_file_not_found)));
|
||||
return;
|
||||
}
|
||||
new PlaybackServiceStarter(context, media)
|
||||
.callEvenIfRunning(true)
|
||||
.start();
|
||||
|
||||
if (media.getMediaType() == MediaType.VIDEO) {
|
||||
context.startActivity(PlaybackService.getPlayerActivityIntent(context, media));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package de.danoeh.antennapod.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.model.playback.MediaType;
|
||||
import de.danoeh.antennapod.playback.service.PlaybackService;
|
||||
import de.danoeh.antennapod.playback.service.PlaybackServiceStarter;
|
||||
|
||||
public class PlayLocalActionButton extends ItemActionButton {
|
||||
|
||||
public PlayLocalActionButton(FeedItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
public int getLabel() {
|
||||
return R.string.play_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DrawableRes
|
||||
public int getDrawable() {
|
||||
return R.drawable.ic_play_24dp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Context context) {
|
||||
final FeedMedia media = item.getMedia();
|
||||
if (media == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
new PlaybackServiceStarter(context, media)
|
||||
.callEvenIfRunning(true)
|
||||
.start();
|
||||
|
||||
if (media.getMediaType() == MediaType.VIDEO) {
|
||||
context.startActivity(PlaybackService.getPlayerActivityIntent(context, media));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package de.danoeh.antennapod.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.model.playback.MediaType;
|
||||
import de.danoeh.antennapod.playback.service.PlaybackService;
|
||||
import de.danoeh.antennapod.playback.service.PlaybackServiceStarter;
|
||||
import de.danoeh.antennapod.storage.preferences.UsageStatistics;
|
||||
import de.danoeh.antennapod.net.common.NetworkUtils;
|
||||
import de.danoeh.antennapod.ui.StreamingConfirmationDialog;
|
||||
|
||||
public class StreamActionButton extends ItemActionButton {
|
||||
|
||||
public StreamActionButton(FeedItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
public int getLabel() {
|
||||
return R.string.stream_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DrawableRes
|
||||
public int getDrawable() {
|
||||
return R.drawable.ic_stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Context context) {
|
||||
final FeedMedia media = item.getMedia();
|
||||
if (media == null) {
|
||||
return;
|
||||
}
|
||||
UsageStatistics.logAction(UsageStatistics.ACTION_STREAM);
|
||||
|
||||
if (!NetworkUtils.isStreamingAllowed()) {
|
||||
new StreamingConfirmationDialog(context, media).show();
|
||||
return;
|
||||
}
|
||||
new PlaybackServiceStarter(context, media)
|
||||
.callEvenIfRunning(true)
|
||||
.start();
|
||||
|
||||
if (media.getMediaType() == MediaType.VIDEO) {
|
||||
context.startActivity(PlaybackService.getPlayerActivityIntent(context, media));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package de.danoeh.antennapod.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.ui.common.IntentUtils;
|
||||
|
||||
public class VisitWebsiteActionButton extends ItemActionButton {
|
||||
|
||||
public VisitWebsiteActionButton(FeedItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
public int getLabel() {
|
||||
return R.string.visit_website_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DrawableRes
|
||||
public int getDrawable() {
|
||||
return R.drawable.ic_web;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Context context) {
|
||||
IntentUtils.openInBrowser(context, item.getLink());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVisibility() {
|
||||
return (item.getLink() == null) ? View.INVISIBLE : View.VISIBLE;
|
||||
}
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
package de.danoeh.antennapod.activity;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
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.app.ShareCompat;
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.TextView;
|
||||
|
||||
|
||||
import de.danoeh.antennapod.error.CrashReportWriter;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Displays the 'crash report' screen
|
||||
*/
|
||||
public class BugReportActivity extends AppCompatActivity {
|
||||
private static final String TAG = "BugReportActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(UserPreferences.getTheme());
|
||||
super.onCreate(savedInstanceState);
|
||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
setContentView(R.layout.bug_report);
|
||||
|
||||
String stacktrace = "No crash report recorded";
|
||||
try {
|
||||
File crashFile = CrashReportWriter.getFile();
|
||||
if (crashFile.exists()) {
|
||||
stacktrace = IOUtils.toString(new FileInputStream(crashFile), Charset.forName("UTF-8"));
|
||||
} else {
|
||||
Log.d(TAG, stacktrace);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
TextView crashDetailsTextView = findViewById(R.id.crash_report_logs);
|
||||
crashDetailsTextView.setText(CrashReportWriter.getSystemInfo() + "\n\n" + stacktrace);
|
||||
|
||||
findViewById(R.id.btn_open_bug_tracker).setOnClickListener(v -> IntentUtils.openInBrowser(
|
||||
BugReportActivity.this, "https://github.com/AntennaPod/AntennaPod/issues"));
|
||||
|
||||
findViewById(R.id.btn_copy_log).setOnClickListener(v -> {
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText(getString(R.string.bug_report_title), crashDetailsTextView.getText());
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Snackbar.make(findViewById(android.R.id.content), R.string.copied_to_clipboard, Snackbar.LENGTH_SHORT).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");
|
||||
String cmd = "logcat -d -f " + filename.getAbsolutePath();
|
||||
Runtime.getRuntime().exec(cmd);
|
||||
//share file
|
||||
try {
|
||||
String authority = getString(R.string.provider_authority);
|
||||
Uri fileUri = FileProvider.getUriForFile(this, authority, filename);
|
||||
|
||||
new ShareCompat.IntentBuilder(this)
|
||||
.setType("text/*")
|
||||
.addStream(fileUri)
|
||||
.setChooserTitle(R.string.share_file_label)
|
||||
.startChooser();
|
||||
} 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,76 +0,0 @@
|
|||
package de.danoeh.antennapod.activity;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadService;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.model.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.dialog.AuthenticationDialog;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
|
||||
/**
|
||||
* Shows a username and a password text field.
|
||||
* The activity MUST be started with the ARG_DOWNlOAD_REQUEST argument set to a non-null value.
|
||||
*/
|
||||
public class DownloadAuthenticationActivity extends AppCompatActivity {
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(UserPreferences.getTranslucentTheme());
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Validate.isTrue(getIntent().hasExtra(ARG_DOWNLOAD_REQUEST), "Download request missing");
|
||||
DownloadRequest request = getIntent().getParcelableExtra(ARG_DOWNLOAD_REQUEST);
|
||||
|
||||
new AuthenticationDialog(this, R.string.authentication_label, true, "", "") {
|
||||
@Override
|
||||
protected void onConfirmed(String username, String password) {
|
||||
Completable.fromAction(
|
||||
() -> {
|
||||
request.setUsername(username);
|
||||
request.setPassword(password);
|
||||
|
||||
if (request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
|
||||
long mediaId = request.getFeedfileId();
|
||||
FeedMedia media = DBReader.getFeedMedia(mediaId);
|
||||
if (media != null) {
|
||||
FeedPreferences preferences = media.getItem().getFeed().getPreferences();
|
||||
if (TextUtils.isEmpty(preferences.getPassword())
|
||||
|| TextUtils.isEmpty(preferences.getUsername())) {
|
||||
preferences.setUsername(username);
|
||||
preferences.setPassword(password);
|
||||
DBWriter.setFeedPreferences(preferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(() -> {
|
||||
DownloadService.download(DownloadAuthenticationActivity.this, false, request);
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
finish();
|
||||
}
|
||||
}.show();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package de.danoeh.antennapod.activity;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
|
@ -9,8 +8,6 @@ import android.media.AudioManager;
|
|||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
|
@ -18,55 +15,66 @@ import android.view.MenuItem;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentContainerView;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import androidx.work.WorkInfo;
|
||||
import androidx.work.WorkManager;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.google.android.material.appbar.MaterialToolbar;
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import de.danoeh.antennapod.fragment.AllEpisodesFragment;
|
||||
import de.danoeh.antennapod.fragment.CompletedDownloadsFragment;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.net.download.service.feed.FeedUpdateManagerImpl;
|
||||
import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink;
|
||||
import de.danoeh.antennapod.ui.appstartintent.MediaButtonStarter;
|
||||
import de.danoeh.antennapod.ui.common.ThemeSwitcher;
|
||||
import de.danoeh.antennapod.ui.screen.rating.RatingDialogManager;
|
||||
import de.danoeh.antennapod.event.EpisodeDownloadEvent;
|
||||
import de.danoeh.antennapod.event.FeedUpdateRunningEvent;
|
||||
import de.danoeh.antennapod.event.MessageEvent;
|
||||
import de.danoeh.antennapod.ui.screen.AddFeedFragment;
|
||||
import de.danoeh.antennapod.ui.screen.AllEpisodesFragment;
|
||||
import de.danoeh.antennapod.ui.screen.playback.audio.AudioPlayerFragment;
|
||||
import de.danoeh.antennapod.ui.screen.download.CompletedDownloadsFragment;
|
||||
import de.danoeh.antennapod.ui.screen.download.DownloadLogFragment;
|
||||
import de.danoeh.antennapod.ui.screen.feed.FeedItemlistFragment;
|
||||
import de.danoeh.antennapod.ui.screen.InboxFragment;
|
||||
import de.danoeh.antennapod.ui.screen.drawer.NavDrawerFragment;
|
||||
import de.danoeh.antennapod.ui.screen.PlaybackHistoryFragment;
|
||||
import de.danoeh.antennapod.ui.screen.queue.QueueFragment;
|
||||
import de.danoeh.antennapod.ui.screen.SearchFragment;
|
||||
import de.danoeh.antennapod.ui.screen.subscriptions.SubscriptionFragment;
|
||||
import de.danoeh.antennapod.ui.TransitionEffect;
|
||||
import de.danoeh.antennapod.model.download.DownloadStatus;
|
||||
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
|
||||
import de.danoeh.antennapod.playback.cast.CastEnabledActivity;
|
||||
import de.danoeh.antennapod.storage.importexport.AutomaticDatabaseExportWorker;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
import de.danoeh.antennapod.ui.discovery.DiscoveryFragment;
|
||||
import de.danoeh.antennapod.ui.screen.home.HomeFragment;
|
||||
import de.danoeh.antennapod.ui.view.LockableBottomSheetBehavior;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.event.MessageEvent;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
|
||||
import de.danoeh.antennapod.dialog.RatingDialog;
|
||||
import de.danoeh.antennapod.fragment.AddFeedFragment;
|
||||
import de.danoeh.antennapod.fragment.AudioPlayerFragment;
|
||||
import de.danoeh.antennapod.fragment.InboxFragment;
|
||||
import de.danoeh.antennapod.fragment.FeedItemlistFragment;
|
||||
import de.danoeh.antennapod.fragment.NavDrawerFragment;
|
||||
import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
|
||||
import de.danoeh.antennapod.fragment.QueueFragment;
|
||||
import de.danoeh.antennapod.fragment.SearchFragment;
|
||||
import de.danoeh.antennapod.fragment.SubscriptionFragment;
|
||||
import de.danoeh.antennapod.fragment.TransitionEffect;
|
||||
import de.danoeh.antennapod.preferences.PreferenceUpgrader;
|
||||
import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
import de.danoeh.antennapod.view.LockableBottomSheetBehavior;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The activity that is shown when the user launches the app.
|
||||
|
@ -79,11 +87,8 @@ public class MainActivity extends CastEnabledActivity {
|
|||
public static final String PREF_NAME = "MainActivityPrefs";
|
||||
public static final String PREF_IS_FIRST_LAUNCH = "prefMainActivityIsFirstLaunch";
|
||||
|
||||
public static final String EXTRA_FRAGMENT_TAG = "fragment_tag";
|
||||
public static final String EXTRA_FRAGMENT_ARGS = "fragment_args";
|
||||
public static final String EXTRA_FEED_ID = "fragment_feed_id";
|
||||
public static final String EXTRA_REFRESH_ON_START = "refresh_on_start";
|
||||
public static final String EXTRA_STARTED_FROM_SEARCH = "started_from_search";
|
||||
public static final String EXTRA_ADD_TO_BACK_STACK = "add_to_back_stack";
|
||||
public static final String KEY_GENERATED_VIEW_ID = "generated_view_id";
|
||||
|
||||
|
@ -91,9 +96,9 @@ public class MainActivity extends CastEnabledActivity {
|
|||
private @Nullable ActionBarDrawerToggle drawerToggle;
|
||||
private View navDrawer;
|
||||
private LockableBottomSheetBehavior sheetBehavior;
|
||||
private long lastBackButtonPressTime = 0;
|
||||
private RecyclerView.RecycledViewPool recycledViewPool = new RecyclerView.RecycledViewPool();
|
||||
private int lastTheme = 0;
|
||||
private Insets navigationBarInsets = Insets.NONE;
|
||||
|
||||
@NonNull
|
||||
public static Intent getIntentToOpenFeed(@NonNull Context context, long feedId) {
|
||||
|
@ -105,11 +110,12 @@ public class MainActivity extends CastEnabledActivity {
|
|||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
lastTheme = UserPreferences.getNoTitleTheme();
|
||||
lastTheme = ThemeSwitcher.getNoTitleTheme(this);
|
||||
setTheme(lastTheme);
|
||||
if (savedInstanceState != null) {
|
||||
ensureGeneratedViewIdGreaterThan(savedInstanceState.getInt(KEY_GENERATED_VIEW_ID, 0));
|
||||
}
|
||||
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main);
|
||||
recycledViewPool.setMaxRecycledViews(R.id.view_type_episode_item, 25);
|
||||
|
@ -118,19 +124,32 @@ public class MainActivity extends CastEnabledActivity {
|
|||
navDrawer = findViewById(R.id.navDrawerFragment);
|
||||
setNavDrawerSize();
|
||||
|
||||
// Consume navigation bar insets - we apply them in setPlayerVisible()
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main_view), (v, insets) -> {
|
||||
navigationBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars());
|
||||
updateInsets();
|
||||
return new WindowInsetsCompat.Builder(insets)
|
||||
.setInsets(WindowInsetsCompat.Type.navigationBars(), Insets.NONE)
|
||||
.build();
|
||||
});
|
||||
|
||||
final FragmentManager fm = getSupportFragmentManager();
|
||||
if (fm.findFragmentByTag(MAIN_FRAGMENT_TAG) == null) {
|
||||
String lastFragment = NavDrawerFragment.getLastNavFragment(this);
|
||||
if (ArrayUtils.contains(NavDrawerFragment.NAV_DRAWER_TAGS, lastFragment)) {
|
||||
loadFragment(lastFragment, null);
|
||||
if (!UserPreferences.DEFAULT_PAGE_REMEMBER.equals(UserPreferences.getDefaultPage())) {
|
||||
loadFragment(UserPreferences.getDefaultPage(), null);
|
||||
} else {
|
||||
try {
|
||||
loadFeedFragmentById(Integer.parseInt(lastFragment), null);
|
||||
} catch (NumberFormatException e) {
|
||||
// it's not a number, this happens if we removed
|
||||
// a label from the NAV_DRAWER_TAGS
|
||||
// give them a nice default...
|
||||
loadFragment(QueueFragment.TAG, null);
|
||||
String lastFragment = NavDrawerFragment.getLastNavFragment(this);
|
||||
if (ArrayUtils.contains(NavDrawerFragment.NAV_DRAWER_TAGS, lastFragment)) {
|
||||
loadFragment(lastFragment, null);
|
||||
} else {
|
||||
try {
|
||||
loadFeedFragmentById(Integer.parseInt(lastFragment), null);
|
||||
} catch (NumberFormatException e) {
|
||||
// it's not a number, this happens if we removed
|
||||
// a label from the NAV_DRAWER_TAGS
|
||||
// give them a nice default...
|
||||
loadFragment(HomeFragment.TAG, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -143,12 +162,67 @@ public class MainActivity extends CastEnabledActivity {
|
|||
transaction.commit();
|
||||
|
||||
checkFirstLaunch();
|
||||
PreferenceUpgrader.checkUpgrades(this);
|
||||
View bottomSheet = findViewById(R.id.audioplayerFragment);
|
||||
sheetBehavior = (LockableBottomSheetBehavior) BottomSheetBehavior.from(bottomSheet);
|
||||
sheetBehavior.setPeekHeight((int) getResources().getDimension(R.dimen.external_player_height));
|
||||
sheetBehavior.setHideable(false);
|
||||
sheetBehavior.setBottomSheetCallback(bottomSheetCallback);
|
||||
|
||||
FeedUpdateManager.getInstance().restartUpdateAlarm(this, false);
|
||||
SynchronizationQueueSink.syncNowIfNotSyncedRecently();
|
||||
AutomaticDatabaseExportWorker.enqueueIfNeeded(this, false);
|
||||
|
||||
WorkManager.getInstance(this)
|
||||
.getWorkInfosByTagLiveData(FeedUpdateManagerImpl.WORK_TAG_FEED_UPDATE)
|
||||
.observe(this, workInfos -> {
|
||||
boolean isRefreshingFeeds = false;
|
||||
for (WorkInfo workInfo : workInfos) {
|
||||
if (workInfo.getState() == WorkInfo.State.RUNNING) {
|
||||
isRefreshingFeeds = true;
|
||||
} else if (workInfo.getState() == WorkInfo.State.ENQUEUED) {
|
||||
isRefreshingFeeds = true;
|
||||
}
|
||||
}
|
||||
EventBus.getDefault().postSticky(new FeedUpdateRunningEvent(isRefreshingFeeds));
|
||||
});
|
||||
WorkManager.getInstance(this)
|
||||
.getWorkInfosByTagLiveData(DownloadServiceInterface.WORK_TAG)
|
||||
.observe(this, workInfos -> {
|
||||
Map<String, DownloadStatus> updatedEpisodes = new HashMap<>();
|
||||
for (WorkInfo workInfo : workInfos) {
|
||||
String downloadUrl = null;
|
||||
for (String tag : workInfo.getTags()) {
|
||||
if (tag.startsWith(DownloadServiceInterface.WORK_TAG_EPISODE_URL)) {
|
||||
downloadUrl = tag.substring(DownloadServiceInterface.WORK_TAG_EPISODE_URL.length());
|
||||
}
|
||||
}
|
||||
if (downloadUrl == null) {
|
||||
continue;
|
||||
}
|
||||
int status;
|
||||
if (workInfo.getState() == WorkInfo.State.RUNNING) {
|
||||
status = DownloadStatus.STATE_RUNNING;
|
||||
} else if (workInfo.getState() == WorkInfo.State.ENQUEUED
|
||||
|| workInfo.getState() == WorkInfo.State.BLOCKED) {
|
||||
status = DownloadStatus.STATE_QUEUED;
|
||||
} else {
|
||||
status = DownloadStatus.STATE_COMPLETED;
|
||||
}
|
||||
int progress = workInfo.getProgress().getInt(DownloadServiceInterface.WORK_DATA_PROGRESS, -1);
|
||||
if (progress == -1 && status != DownloadStatus.STATE_COMPLETED) {
|
||||
status = DownloadStatus.STATE_QUEUED;
|
||||
progress = 0;
|
||||
}
|
||||
updatedEpisodes.put(downloadUrl, new DownloadStatus(status, progress));
|
||||
}
|
||||
DownloadServiceInterface.get().setCurrentDownloads(updatedEpisodes);
|
||||
EventBus.getDefault().postSticky(new EpisodeDownloadEvent(updatedEpisodes));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
updateInsets();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -170,8 +244,9 @@ public class MainActivity extends CastEnabledActivity {
|
|||
outState.putInt(KEY_GENERATED_VIEW_ID, View.generateViewId());
|
||||
}
|
||||
|
||||
private final BottomSheetBehavior.BottomSheetCallback bottomSheetCallback =
|
||||
new BottomSheetBehavior.BottomSheetCallback() {
|
||||
private final BottomSheetBehavior.BottomSheetCallback bottomSheetCallback = new AntennaPodBottomSheetCallback();
|
||||
|
||||
private class AntennaPodBottomSheetCallback extends BottomSheetBehavior.BottomSheetCallback {
|
||||
@Override
|
||||
public void onStateChanged(@NonNull View view, int state) {
|
||||
if (state == BottomSheetBehavior.STATE_COLLAPSED) {
|
||||
|
@ -193,14 +268,11 @@ public class MainActivity extends CastEnabledActivity {
|
|||
audioPlayer.scrollToPage(AudioPlayerFragment.POS_COVER);
|
||||
}
|
||||
|
||||
float condensedSlideOffset = Math.max(0.0f, Math.min(0.2f, slideOffset - 0.2f)) / 0.2f;
|
||||
audioPlayer.getExternalPlayerHolder().setAlpha(1 - condensedSlideOffset);
|
||||
audioPlayer.getExternalPlayerHolder().setVisibility(
|
||||
condensedSlideOffset > 0.99f ? View.GONE : View.VISIBLE);
|
||||
audioPlayer.fadePlayerToToolbar(slideOffset);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void setupToolbarToggle(@NonNull Toolbar toolbar, boolean displayUpArrow) {
|
||||
public void setupToolbarToggle(@NonNull MaterialToolbar toolbar, boolean displayUpArrow) {
|
||||
if (drawerLayout != null) { // Tablet layout does not have a drawer
|
||||
if (drawerToggle != null) {
|
||||
drawerLayout.removeDrawerListener(drawerToggle);
|
||||
|
@ -219,19 +291,18 @@ public class MainActivity extends CastEnabledActivity {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (drawerLayout != null && drawerToggle != null) {
|
||||
drawerLayout.removeDrawerListener(drawerToggle);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkFirstLaunch() {
|
||||
SharedPreferences prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE);
|
||||
if (prefs.getBoolean(PREF_IS_FIRST_LAUNCH, true)) {
|
||||
loadFragment(AddFeedFragment.TAG, null);
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||
if (drawerLayout != null) { // Tablet layout does not have a drawer
|
||||
drawerLayout.openDrawer(navDrawer);
|
||||
}
|
||||
}, 1500);
|
||||
|
||||
// for backward compatibility, we only change defaults for fresh installs
|
||||
UserPreferences.setUpdateInterval(12);
|
||||
AutoUpdateManager.restartUpdateAlarm(this);
|
||||
FeedUpdateManager.getInstance().restartUpdateAlarm(this, true);
|
||||
|
||||
SharedPreferences.Editor edit = prefs.edit();
|
||||
edit.putBoolean(PREF_IS_FIRST_LAUNCH, false);
|
||||
|
@ -247,12 +318,29 @@ public class MainActivity extends CastEnabledActivity {
|
|||
return sheetBehavior;
|
||||
}
|
||||
|
||||
private void updateInsets() {
|
||||
setPlayerVisible(findViewById(R.id.audioplayerFragment).getVisibility() == View.VISIBLE);
|
||||
int playerHeight = (int) getResources().getDimension(R.dimen.external_player_height);
|
||||
sheetBehavior.setPeekHeight(playerHeight + navigationBarInsets.bottom);
|
||||
}
|
||||
|
||||
public void setPlayerVisible(boolean visible) {
|
||||
getBottomSheet().setLocked(!visible);
|
||||
if (visible) {
|
||||
bottomSheetCallback.onStateChanged(null, getBottomSheet().getState()); // Update toolbar visibility
|
||||
} else {
|
||||
getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
}
|
||||
FragmentContainerView mainView = findViewById(R.id.main_view);
|
||||
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mainView.getLayoutParams();
|
||||
params.setMargins(0, 0, 0, visible ? (int) getResources().getDimension(R.dimen.external_player_height) : 0);
|
||||
int externalPlayerHeight = (int) getResources().getDimension(R.dimen.external_player_height);
|
||||
params.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right,
|
||||
navigationBarInsets.bottom + (visible ? externalPlayerHeight : 0));
|
||||
mainView.setLayoutParams(params);
|
||||
FragmentContainerView playerView = findViewById(R.id.playerFragment);
|
||||
ViewGroup.MarginLayoutParams playerParams = (ViewGroup.MarginLayoutParams) playerView.getLayoutParams();
|
||||
playerParams.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right, 0);
|
||||
playerView.setLayoutParams(playerParams);
|
||||
findViewById(R.id.audioplayerFragment).setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
|
@ -260,10 +348,13 @@ public class MainActivity extends CastEnabledActivity {
|
|||
return recycledViewPool;
|
||||
}
|
||||
|
||||
public void loadFragment(String tag, Bundle args) {
|
||||
public Fragment createFragmentInstance(String tag, Bundle args) {
|
||||
Log.d(TAG, "loadFragment(tag: " + tag + ", args: " + args + ")");
|
||||
Fragment fragment;
|
||||
switch (tag) {
|
||||
case HomeFragment.TAG:
|
||||
fragment = new HomeFragment();
|
||||
break;
|
||||
case QueueFragment.TAG:
|
||||
fragment = new QueueFragment();
|
||||
break;
|
||||
|
@ -285,19 +376,24 @@ public class MainActivity extends CastEnabledActivity {
|
|||
case SubscriptionFragment.TAG:
|
||||
fragment = new SubscriptionFragment();
|
||||
break;
|
||||
case DiscoveryFragment.TAG:
|
||||
fragment = new DiscoveryFragment();
|
||||
break;
|
||||
default:
|
||||
// default to the queue
|
||||
fragment = new QueueFragment();
|
||||
tag = QueueFragment.TAG;
|
||||
// default to home screen
|
||||
fragment = new HomeFragment();
|
||||
args = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (args != null) {
|
||||
fragment.setArguments(args);
|
||||
}
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public void loadFragment(String tag, Bundle args) {
|
||||
NavDrawerFragment.saveLastNavFragment(this, tag);
|
||||
loadFragment(fragment);
|
||||
loadFragment(createFragmentInstance(tag, args));
|
||||
}
|
||||
|
||||
public void loadFeedFragmentById(long feedId, Bundle args) {
|
||||
|
@ -309,7 +405,7 @@ public class MainActivity extends CastEnabledActivity {
|
|||
loadFragment(fragment);
|
||||
}
|
||||
|
||||
private void loadFragment(Fragment fragment) {
|
||||
public void loadFragment(Fragment fragment) {
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
// clear back stack
|
||||
for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) {
|
||||
|
@ -406,25 +502,27 @@ public class MainActivity extends CastEnabledActivity {
|
|||
public void onStart() {
|
||||
super.onStart();
|
||||
EventBus.getDefault().register(this);
|
||||
RatingDialog.init(this);
|
||||
new RatingDialogManager(this).showIfNeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
handleNavIntent();
|
||||
RatingDialog.check();
|
||||
|
||||
if (lastTheme != UserPreferences.getNoTitleTheme()) {
|
||||
if (lastTheme != ThemeSwitcher.getNoTitleTheme(this)) {
|
||||
finish();
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
}
|
||||
if (UserPreferences.getHiddenDrawerItems().contains(NavDrawerFragment.getLastNavFragment(this))) {
|
||||
loadFragment(UserPreferences.getDefaultPage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
lastTheme = UserPreferences.getNoTitleTheme(); // Don't recreate activity when a result is pending
|
||||
lastTheme = ThemeSwitcher.getNoTitleTheme(this); // Don't recreate activity when a result is pending
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -433,7 +531,6 @@ public class MainActivity extends CastEnabledActivity {
|
|||
EventBus.getDefault().unregister(this);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
super.onTrimMemory(level);
|
||||
|
@ -462,43 +559,23 @@ public class MainActivity extends CastEnabledActivity {
|
|||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (isDrawerOpen()) {
|
||||
if (isDrawerOpen() && drawerLayout != null) {
|
||||
drawerLayout.closeDrawer(navDrawer);
|
||||
} else if (sheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
} else if (getSupportFragmentManager().getBackStackEntryCount() != 0) {
|
||||
super.onBackPressed();
|
||||
} else {
|
||||
switch (UserPreferences.getBackButtonBehavior()) {
|
||||
case OPEN_DRAWER:
|
||||
if (drawerLayout != null) { // Tablet layout does not have drawer
|
||||
drawerLayout.openDrawer(navDrawer);
|
||||
}
|
||||
break;
|
||||
case SHOW_PROMPT:
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.close_prompt)
|
||||
.setPositiveButton(R.string.yes, (dialogInterface, i) -> MainActivity.super.onBackPressed())
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
break;
|
||||
case DOUBLE_TAP:
|
||||
if (lastBackButtonPressTime < System.currentTimeMillis() - 2000) {
|
||||
Toast.makeText(this, R.string.double_tap_toast, Toast.LENGTH_SHORT).show();
|
||||
lastBackButtonPressTime = System.currentTimeMillis();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
break;
|
||||
case GO_TO_PAGE:
|
||||
if (NavDrawerFragment.getLastNavFragment(this).equals(UserPreferences.getBackButtonGoToPage())) {
|
||||
super.onBackPressed();
|
||||
} else {
|
||||
loadFragment(UserPreferences.getBackButtonGoToPage(), null);
|
||||
}
|
||||
break;
|
||||
default: super.onBackPressed();
|
||||
String toPage = UserPreferences.getDefaultPage();
|
||||
if (NavDrawerFragment.getLastNavFragment(this).equals(toPage)
|
||||
|| UserPreferences.DEFAULT_PAGE_REMEMBER.equals(toPage)) {
|
||||
if (UserPreferences.backButtonOpensDrawer() && drawerLayout != null) {
|
||||
drawerLayout.openDrawer(navDrawer);
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
} else {
|
||||
loadFragment(toPage, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -507,42 +584,55 @@ public class MainActivity extends CastEnabledActivity {
|
|||
public void onEventMainThread(MessageEvent event) {
|
||||
Log.d(TAG, "onEvent(" + event + ")");
|
||||
|
||||
Snackbar snackbar = showSnackbarAbovePlayer(event.message, Snackbar.LENGTH_SHORT);
|
||||
Snackbar snackbar = showSnackbarAbovePlayer(event.message, Snackbar.LENGTH_LONG);
|
||||
if (event.action != null) {
|
||||
snackbar.setAction(getString(R.string.undo), v -> event.action.run());
|
||||
snackbar.setAction(event.actionText, v -> event.action.accept(this));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleNavIntent() {
|
||||
Log.d(TAG, "handleNavIntent()");
|
||||
Intent intent = getIntent();
|
||||
if (intent.hasExtra(EXTRA_FEED_ID) || intent.hasExtra(EXTRA_FRAGMENT_TAG) || intent.hasExtra(EXTRA_REFRESH_ON_START)) {
|
||||
Log.d(TAG, "handleNavIntent()");
|
||||
String tag = intent.getStringExtra(EXTRA_FRAGMENT_TAG);
|
||||
Bundle args = intent.getBundleExtra(EXTRA_FRAGMENT_ARGS);
|
||||
boolean refreshOnStart = intent.getBooleanExtra(EXTRA_REFRESH_ON_START, false);
|
||||
if (refreshOnStart) {
|
||||
AutoUpdateManager.runImmediate(this);
|
||||
}
|
||||
|
||||
if (intent.hasExtra(EXTRA_FEED_ID)) {
|
||||
long feedId = intent.getLongExtra(EXTRA_FEED_ID, 0);
|
||||
if (tag != null) {
|
||||
loadFragment(tag, args);
|
||||
} else if (feedId > 0) {
|
||||
boolean startedFromSearch = intent.getBooleanExtra(EXTRA_STARTED_FROM_SEARCH, false);
|
||||
Bundle args = intent.getBundleExtra(MainActivityStarter.EXTRA_FRAGMENT_ARGS);
|
||||
if (feedId > 0) {
|
||||
boolean addToBackStack = intent.getBooleanExtra(EXTRA_ADD_TO_BACK_STACK, false);
|
||||
if (startedFromSearch || addToBackStack) {
|
||||
if (addToBackStack) {
|
||||
loadChildFragment(FeedItemlistFragment.newInstance(feedId));
|
||||
} else {
|
||||
loadFeedFragmentById(feedId, args);
|
||||
}
|
||||
}
|
||||
sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
} else if (intent.hasExtra(MainActivityStarter.EXTRA_FRAGMENT_TAG)) {
|
||||
String tag = intent.getStringExtra(MainActivityStarter.EXTRA_FRAGMENT_TAG);
|
||||
Bundle args = intent.getBundleExtra(MainActivityStarter.EXTRA_FRAGMENT_ARGS);
|
||||
if (tag != null) {
|
||||
Fragment fragment = createFragmentInstance(tag, args);
|
||||
if (intent.getBooleanExtra(MainActivityStarter.EXTRA_ADD_TO_BACK_STACK, false)) {
|
||||
loadChildFragment(fragment);
|
||||
} else {
|
||||
loadFragment(fragment);
|
||||
}
|
||||
}
|
||||
sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
} else if (intent.getBooleanExtra(MainActivityStarter.EXTRA_OPEN_PLAYER, false)) {
|
||||
sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
|
||||
bottomSheetCallback.onSlide(null, 1.0f);
|
||||
} else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
|
||||
} else {
|
||||
handleDeeplink(intent.getData());
|
||||
}
|
||||
|
||||
if (intent.getBooleanExtra(MainActivityStarter.EXTRA_OPEN_DRAWER, false) && drawerLayout != null) {
|
||||
drawerLayout.open();
|
||||
}
|
||||
if (intent.getBooleanExtra(MainActivityStarter.EXTRA_OPEN_DOWNLOAD_LOGS, false)) {
|
||||
new DownloadLogFragment().show(getSupportFragmentManager(), null);
|
||||
}
|
||||
if (intent.getBooleanExtra(EXTRA_REFRESH_ON_START, false)) {
|
||||
FeedUpdateManager.getInstance().runOnceOrAsk(this);
|
||||
}
|
||||
// to avoid handling the intent twice when the configuration changes
|
||||
setIntent(new Intent(MainActivity.this, MainActivity.class));
|
||||
}
|
||||
|
@ -671,9 +761,7 @@ public class MainActivity extends CastEnabledActivity {
|
|||
}
|
||||
|
||||
if (customKeyCode != null) {
|
||||
Intent intent = new Intent(this, PlaybackService.class);
|
||||
intent.putExtra(MediaButtonReceiver.EXTRA_KEYCODE, customKeyCode);
|
||||
ContextCompat.startForegroundService(this, intent);
|
||||
sendBroadcast(MediaButtonStarter.createIntent(this, customKeyCode));
|
||||
return true;
|
||||
}
|
||||
return super.onKeyUp(keyCode, event);
|
||||
|
|
|
@ -1,703 +0,0 @@
|
|||
package de.danoeh.antennapod.activity;
|
||||
|
||||
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.Bundle;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.NavUtils;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.adapter.FeedItemlistDescriptionAdapter;
|
||||
import de.danoeh.antennapod.core.event.DownloadEvent;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadService;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadRequestCreator;
|
||||
import de.danoeh.antennapod.core.feed.FeedUrlNotFoundException;
|
||||
import de.danoeh.antennapod.core.util.DownloadErrorLabel;
|
||||
import de.danoeh.antennapod.event.FeedListUpdateEvent;
|
||||
import de.danoeh.antennapod.event.PlayerStatusEvent;
|
||||
import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
||||
import de.danoeh.antennapod.core.glide.FastBlurTransformation;
|
||||
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadRequest;
|
||||
import de.danoeh.antennapod.model.download.DownloadStatus;
|
||||
import de.danoeh.antennapod.core.service.download.Downloader;
|
||||
import de.danoeh.antennapod.core.service.download.HttpDownloader;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.net.discovery.CombinedSearcher;
|
||||
import de.danoeh.antennapod.net.discovery.PodcastSearchResult;
|
||||
import de.danoeh.antennapod.net.discovery.PodcastSearcherRegistry;
|
||||
import de.danoeh.antennapod.parser.feed.FeedHandler;
|
||||
import de.danoeh.antennapod.parser.feed.FeedHandlerResult;
|
||||
import de.danoeh.antennapod.model.download.DownloadError;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import de.danoeh.antennapod.core.util.URLChecker;
|
||||
import de.danoeh.antennapod.core.util.syndication.FeedDiscoverer;
|
||||
import de.danoeh.antennapod.core.util.syndication.HtmlToPlainText;
|
||||
import de.danoeh.antennapod.databinding.OnlinefeedviewActivityBinding;
|
||||
import de.danoeh.antennapod.dialog.AuthenticationDialog;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.model.feed.FeedPreferences;
|
||||
import de.danoeh.antennapod.model.playback.RemoteMedia;
|
||||
import de.danoeh.antennapod.parser.feed.UnsupportedFeedtypeException;
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.observers.DisposableMaybeObserver;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Downloads a feed from a feed URL and parses it. Subclasses can display the
|
||||
* feed object that was parsed. This activity MUST be started with a given URL
|
||||
* or an Exception will be thrown.
|
||||
* <p/>
|
||||
* If the feed cannot be downloaded or parsed, an error dialog will be displayed
|
||||
* and the activity will finish as soon as the error dialog is closed.
|
||||
*/
|
||||
public class OnlineFeedViewActivity extends AppCompatActivity {
|
||||
|
||||
public static final String ARG_FEEDURL = "arg.feedurl";
|
||||
// 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 String selectedDownloadUrl;
|
||||
private Downloader downloader;
|
||||
private String username = null;
|
||||
private String password = null;
|
||||
|
||||
private boolean isPaused;
|
||||
private boolean didPressSubscribe = false;
|
||||
private boolean isFeedFoundBySearch = false;
|
||||
|
||||
private Dialog dialog;
|
||||
|
||||
private Disposable download;
|
||||
private Disposable parser;
|
||||
private Disposable updater;
|
||||
|
||||
private OnlinefeedviewActivityBinding viewBinding;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(UserPreferences.getTranslucentTheme());
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
viewBinding = OnlinefeedviewActivityBinding.inflate(getLayoutInflater());
|
||||
setContentView(viewBinding.getRoot());
|
||||
|
||||
viewBinding.transparentBackground.setOnClickListener(v -> finish());
|
||||
viewBinding.card.setOnClickListener(null);
|
||||
|
||||
String feedUrl = null;
|
||||
if (getIntent().hasExtra(ARG_FEEDURL)) {
|
||||
feedUrl = getIntent().getStringExtra(ARG_FEEDURL);
|
||||
} else if (TextUtils.equals(getIntent().getAction(), Intent.ACTION_SEND)) {
|
||||
feedUrl = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
||||
} else if (TextUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) {
|
||||
feedUrl = getIntent().getDataString();
|
||||
}
|
||||
|
||||
if (feedUrl == null) {
|
||||
Log.e(TAG, "feedUrl is null.");
|
||||
showNoPodcastFoundError();
|
||||
} else {
|
||||
Log.d(TAG, "Activity was started with url " + feedUrl);
|
||||
setLoadingLayout();
|
||||
// Remove subscribeonandroid.com from feed URL in order to subscribe to the actual feed URL
|
||||
if (feedUrl.contains("subscribeonandroid.com")) {
|
||||
feedUrl = feedUrl.replaceFirst("((www.)?(subscribeonandroid.com/))", "");
|
||||
}
|
||||
if (savedInstanceState != null) {
|
||||
username = savedInstanceState.getString("username");
|
||||
password = savedInstanceState.getString("password");
|
||||
}
|
||||
lookupUrlAndDownload(feedUrl);
|
||||
}
|
||||
}
|
||||
|
||||
private void showNoPodcastFoundError() {
|
||||
runOnUiThread(() -> new AlertDialog.Builder(OnlineFeedViewActivity.this)
|
||||
.setNeutralButton(android.R.string.ok, (dialog, which) -> finish())
|
||||
.setTitle(R.string.error_label)
|
||||
.setMessage(R.string.null_value_podcast_error)
|
||||
.setOnDismissListener(dialog1 -> {
|
||||
setResult(RESULT_ERROR);
|
||||
finish();
|
||||
})
|
||||
.show());
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a progress indicator.
|
||||
*/
|
||||
private void setLoadingLayout() {
|
||||
viewBinding.progressBar.setVisibility(View.VISIBLE);
|
||||
viewBinding.feedDisplayContainer.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
isPaused = false;
|
||||
EventBus.getDefault().register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
isPaused = true;
|
||||
EventBus.getDefault().unregister(this);
|
||||
if (downloader != null && !downloader.isFinished()) {
|
||||
downloader.cancel();
|
||||
}
|
||||
if(dialog != null && dialog.isShowing()) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if(updater != null) {
|
||||
updater.dispose();
|
||||
}
|
||||
if(download != null) {
|
||||
download.dispose();
|
||||
}
|
||||
if(parser != null) {
|
||||
parser.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString("username", username);
|
||||
outState.putString("password", password);
|
||||
}
|
||||
|
||||
private void resetIntent(String url) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(ARG_FEEDURL, url);
|
||||
setIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
Intent destIntent = new Intent(this, MainActivity.class);
|
||||
if (NavUtils.shouldUpRecreateTask(this, destIntent)) {
|
||||
startActivity(destIntent);
|
||||
} else {
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void lookupUrlAndDownload(String url) {
|
||||
download = PodcastSearcherRegistry.lookupUrl(url)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(this::startFeedDownload,
|
||||
error -> {
|
||||
if (error instanceof FeedUrlNotFoundException) {
|
||||
tryToRetrieveFeedUrlBySearch((FeedUrlNotFoundException) error);
|
||||
} else {
|
||||
showNoPodcastFoundError();
|
||||
Log.e(TAG, Log.getStackTraceString(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void tryToRetrieveFeedUrlBySearch(FeedUrlNotFoundException error) {
|
||||
Log.d(TAG, "Unable to retrieve feed url, trying to retrieve feed url from search");
|
||||
String url = searchFeedUrlByTrackName(error.getTrackName(), error.getArtistName());
|
||||
if (url != null) {
|
||||
Log.d(TAG, "Successfully retrieve feed url");
|
||||
isFeedFoundBySearch = true;
|
||||
startFeedDownload(url);
|
||||
} else {
|
||||
showNoPodcastFoundError();
|
||||
Log.d(TAG, "Failed to retrieve feed url");
|
||||
}
|
||||
}
|
||||
|
||||
private String searchFeedUrlByTrackName(String trackName, String artistName) {
|
||||
CombinedSearcher searcher = new CombinedSearcher();
|
||||
String query = trackName + " " + artistName;
|
||||
List<PodcastSearchResult> results = searcher.search(query).blockingGet();
|
||||
for (PodcastSearchResult result : results) {
|
||||
if (result.feedUrl != null && result.author != null
|
||||
&& result.author.equalsIgnoreCase(artistName) && result.title.equalsIgnoreCase(trackName)) {
|
||||
return result.feedUrl;
|
||||
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void startFeedDownload(String url) {
|
||||
Log.d(TAG, "Starting feed download");
|
||||
selectedDownloadUrl = URLChecker.prepareURL(url);
|
||||
DownloadRequest request = DownloadRequestCreator.create(new Feed(selectedDownloadUrl, null))
|
||||
.withAuthentication(username, password)
|
||||
.withInitiatedByUser(true)
|
||||
.build();
|
||||
|
||||
download = Observable.fromCallable(() -> {
|
||||
feeds = DBReader.getFeedList();
|
||||
downloader = new HttpDownloader(request);
|
||||
downloader.call();
|
||||
return downloader.getResult();
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(status -> checkDownloadResult(status, request.getDestination()),
|
||||
error -> Log.e(TAG, Log.getStackTraceString(error)));
|
||||
}
|
||||
|
||||
private void checkDownloadResult(@NonNull DownloadStatus status, String destination) {
|
||||
if (status.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
if (status.isSuccessful()) {
|
||||
parseFeed(destination);
|
||||
} else if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) {
|
||||
if (!isFinishing() && !isPaused) {
|
||||
if (username != null && password != null) {
|
||||
Toast.makeText(this, R.string.download_error_unauthorized, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
dialog = new FeedViewAuthenticationDialog(OnlineFeedViewActivity.this,
|
||||
R.string.authentication_notification_title,
|
||||
downloader.getDownloadRequest().getSource()).create();
|
||||
dialog.show();
|
||||
}
|
||||
} else {
|
||||
showErrorDialog(getString(DownloadErrorLabel.from(status.getReason())), status.getReasonDetailed());
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onFeedListChanged(FeedListUpdateEvent event) {
|
||||
updater = Observable.fromCallable(DBReader::getFeedList)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
feeds -> {
|
||||
OnlineFeedViewActivity.this.feeds = feeds;
|
||||
handleUpdatedFeedStatus();
|
||||
}, error -> Log.e(TAG, Log.getStackTraceString(error))
|
||||
);
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(DownloadEvent event) {
|
||||
Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]");
|
||||
handleUpdatedFeedStatus();
|
||||
}
|
||||
|
||||
private void parseFeed(String destination) {
|
||||
Log.d(TAG, "Parsing feed");
|
||||
parser = Maybe.fromCallable(() -> doParseFeed(destination))
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeWith(new DisposableMaybeObserver<FeedHandlerResult>() {
|
||||
@Override
|
||||
public void onSuccess(@NonNull FeedHandlerResult result) {
|
||||
showFeedInformation(result.feed, result.alternateFeedUrls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
// Ignore null result: We showed the discovery dialog.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable error) {
|
||||
showErrorDialog(error.getMessage(), "");
|
||||
Log.d(TAG, "Feed parser exception: " + Log.getStackTraceString(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to parse the feed.
|
||||
* @return The FeedHandlerResult if successful.
|
||||
* Null if unsuccessful but we started another attempt.
|
||||
* @throws Exception If unsuccessful but we do not know a resolution.
|
||||
*/
|
||||
@Nullable
|
||||
private FeedHandlerResult doParseFeed(String destination) throws Exception {
|
||||
FeedHandler handler = new FeedHandler();
|
||||
Feed feed = new Feed(selectedDownloadUrl, null);
|
||||
feed.setFile_url(destination);
|
||||
File destinationFile = new File(destination);
|
||||
try {
|
||||
return handler.parseFeed(feed);
|
||||
} catch (UnsupportedFeedtypeException e) {
|
||||
Log.d(TAG, "Unsupported feed type detected");
|
||||
if ("html".equalsIgnoreCase(e.getRootElement())) {
|
||||
boolean dialogShown = showFeedDiscoveryDialog(destinationFile, selectedDownloadUrl);
|
||||
if (dialogShown) {
|
||||
return null; // Should not display an error message
|
||||
} else {
|
||||
throw new UnsupportedFeedtypeException(getString(R.string.download_error_unsupported_type_html));
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, Log.getStackTraceString(e));
|
||||
throw e;
|
||||
} finally {
|
||||
boolean rc = destinationFile.delete();
|
||||
Log.d(TAG, "Deleted feed source file. Result: " + rc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when feed parsed successfully.
|
||||
* This method is executed on the GUI thread.
|
||||
*/
|
||||
private void showFeedInformation(final Feed feed, Map<String, String> alternateFeedUrls) {
|
||||
viewBinding.progressBar.setVisibility(View.GONE);
|
||||
viewBinding.feedDisplayContainer.setVisibility(View.VISIBLE);
|
||||
if (isFeedFoundBySearch) {
|
||||
int resId = R.string.no_feed_url_podcast_found_by_search;
|
||||
Snackbar.make(findViewById(android.R.id.content), resId, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
viewBinding.backgroundImage.setColorFilter(new LightingColorFilter(0xff828282, 0x000000));
|
||||
|
||||
View header = View.inflate(this, R.layout.onlinefeedview_header, null);
|
||||
|
||||
viewBinding.listView.addHeaderView(header);
|
||||
viewBinding.listView.setSelector(android.R.color.transparent);
|
||||
viewBinding.listView.setAdapter(new FeedItemlistDescriptionAdapter(this, 0, feed.getItems()));
|
||||
|
||||
TextView description = header.findViewById(R.id.txtvDescription);
|
||||
|
||||
if (StringUtils.isNotBlank(feed.getImageUrl())) {
|
||||
Glide.with(this)
|
||||
.load(feed.getImageUrl())
|
||||
.apply(new RequestOptions()
|
||||
.placeholder(R.color.light_gray)
|
||||
.error(R.color.light_gray)
|
||||
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
|
||||
.fitCenter()
|
||||
.dontAnimate())
|
||||
.into(viewBinding.coverImage);
|
||||
Glide.with(this)
|
||||
.load(feed.getImageUrl())
|
||||
.apply(new RequestOptions()
|
||||
.placeholder(R.color.image_readability_tint)
|
||||
.error(R.color.image_readability_tint)
|
||||
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
|
||||
.transform(new FastBlurTransformation())
|
||||
.dontAnimate())
|
||||
.into(viewBinding.backgroundImage);
|
||||
}
|
||||
|
||||
viewBinding.titleLabel.setText(feed.getTitle());
|
||||
viewBinding.authorLabel.setText(feed.getAuthor());
|
||||
description.setText(HtmlToPlainText.getPlainText(feed.getDescription()));
|
||||
|
||||
viewBinding.subscribeButton.setOnClickListener(v -> {
|
||||
if (feedInFeedlist()) {
|
||||
openFeed();
|
||||
} else {
|
||||
Feed f = new Feed(selectedDownloadUrl, null, feed.getTitle());
|
||||
DownloadService.download(this, false, DownloadRequestCreator.create(f)
|
||||
.withAuthentication(username, password)
|
||||
.build());
|
||||
didPressSubscribe = true;
|
||||
handleUpdatedFeedStatus();
|
||||
}
|
||||
});
|
||||
|
||||
viewBinding.stopPreviewButton.setOnClickListener(v -> {
|
||||
PlaybackPreferences.writeNoMediaPlaying();
|
||||
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 -> {
|
||||
if (description.getMaxLines() > MAX_LINES_COLLAPSED) {
|
||||
description.setMaxLines(MAX_LINES_COLLAPSED);
|
||||
} else {
|
||||
description.setMaxLines(2000);
|
||||
}
|
||||
});
|
||||
|
||||
if (alternateFeedUrls.isEmpty()) {
|
||||
viewBinding.alternateUrlsSpinner.setVisibility(View.GONE);
|
||||
} else {
|
||||
viewBinding.alternateUrlsSpinner.setVisibility(View.VISIBLE);
|
||||
|
||||
final List<String> alternateUrlsList = new ArrayList<>();
|
||||
final List<String> alternateUrlsTitleList = new ArrayList<>();
|
||||
|
||||
alternateUrlsList.add(feed.getDownload_url());
|
||||
alternateUrlsTitleList.add(feed.getTitle());
|
||||
|
||||
|
||||
alternateUrlsList.addAll(alternateFeedUrls.keySet());
|
||||
for (String url : alternateFeedUrls.keySet()) {
|
||||
alternateUrlsTitleList.add(alternateFeedUrls.get(url));
|
||||
}
|
||||
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
|
||||
R.layout.alternate_urls_item, alternateUrlsTitleList) {
|
||||
@Override
|
||||
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
// reusing the old view causes a visual bug on Android <= 10
|
||||
return super.getDropDownView(position, null, parent);
|
||||
}
|
||||
};
|
||||
|
||||
adapter.setDropDownViewResource(R.layout.alternate_urls_dropdown_item);
|
||||
viewBinding.alternateUrlsSpinner.setAdapter(adapter);
|
||||
viewBinding.alternateUrlsSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
selectedDownloadUrl = alternateUrlsList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
handleUpdatedFeedStatus();
|
||||
}
|
||||
|
||||
private void openFeed() {
|
||||
// feed.getId() is always 0, we have to retrieve the id from the feed list from
|
||||
// the database
|
||||
Intent intent = MainActivity.getIntentToOpenFeed(this, getFeedId());
|
||||
intent.putExtra(MainActivity.EXTRA_STARTED_FROM_SEARCH,
|
||||
getIntent().getBooleanExtra(MainActivity.EXTRA_STARTED_FROM_SEARCH, false));
|
||||
finish();
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void handleUpdatedFeedStatus() {
|
||||
if (DownloadService.isDownloadingFile(selectedDownloadUrl)) {
|
||||
viewBinding.subscribeButton.setEnabled(false);
|
||||
viewBinding.subscribeButton.setText(R.string.subscribing_label);
|
||||
} else if (feedInFeedlist()) {
|
||||
viewBinding.subscribeButton.setEnabled(true);
|
||||
viewBinding.subscribeButton.setText(R.string.open_podcast);
|
||||
if (didPressSubscribe) {
|
||||
didPressSubscribe = false;
|
||||
if (UserPreferences.isEnableAutodownload()) {
|
||||
boolean autoDownload = viewBinding.autoDownloadCheckBox.isChecked();
|
||||
|
||||
Feed feed1 = DBReader.getFeed(getFeedId());
|
||||
FeedPreferences feedPreferences = feed1.getPreferences();
|
||||
feedPreferences.setAutoDownload(autoDownload);
|
||||
DBWriter.setFeedPreferences(feedPreferences);
|
||||
|
||||
SharedPreferences preferences = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putBoolean(PREF_LAST_AUTO_DOWNLOAD, autoDownload);
|
||||
editor.apply();
|
||||
}
|
||||
openFeed();
|
||||
}
|
||||
} else {
|
||||
viewBinding.subscribeButton.setEnabled(true);
|
||||
viewBinding.subscribeButton.setText(R.string.subscribe_label);
|
||||
if (UserPreferences.isEnableAutodownload()) {
|
||||
viewBinding.autoDownloadCheckBox.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean feedInFeedlist() {
|
||||
return getFeedId() != 0;
|
||||
}
|
||||
|
||||
private long getFeedId() {
|
||||
if (feeds == null) {
|
||||
return 0;
|
||||
}
|
||||
for (Feed f : feeds) {
|
||||
if (f.getDownload_url().equals(selectedDownloadUrl)) {
|
||||
return f.getId();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void showErrorDialog(String errorMsg, String details) {
|
||||
if (!isFinishing() && !isPaused) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.error_label);
|
||||
if (errorMsg != null) {
|
||||
String total = errorMsg + "\n\n" + details;
|
||||
SpannableString errorMessage = new SpannableString(total);
|
||||
errorMessage.setSpan(new ForegroundColorSpan(0x88888888),
|
||||
errorMsg.length(), total.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
builder.setMessage(errorMessage);
|
||||
} else {
|
||||
builder.setMessage(R.string.download_error_error_unknown);
|
||||
}
|
||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.cancel());
|
||||
builder.setOnDismissListener(dialog -> {
|
||||
setResult(RESULT_ERROR);
|
||||
finish();
|
||||
});
|
||||
if (dialog != null && dialog.isShowing()) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
dialog = builder.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void playbackStateChanged(PlayerStatusEvent event) {
|
||||
boolean isPlayingPreview =
|
||||
PlaybackPreferences.getCurrentlyPlayingMediaType() == RemoteMedia.PLAYABLE_TYPE_REMOTE_MEDIA;
|
||||
viewBinding.stopPreviewButton.setVisibility(isPlayingPreview ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return true if a FeedDiscoveryDialog is shown, false otherwise (e.g., due to no feed found).
|
||||
*/
|
||||
private boolean showFeedDiscoveryDialog(File feedFile, String baseUrl) {
|
||||
FeedDiscoverer fd = new FeedDiscoverer();
|
||||
final Map<String, String> urlsMap;
|
||||
try {
|
||||
urlsMap = fd.findLinks(feedFile, baseUrl);
|
||||
if (urlsMap == null || urlsMap.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isPaused || isFinishing()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final List<String> titles = new ArrayList<>();
|
||||
|
||||
final List<String> urls = new ArrayList<>(urlsMap.keySet());
|
||||
for (String url : urls) {
|
||||
titles.add(urlsMap.get(url));
|
||||
}
|
||||
|
||||
if (urls.size() == 1) {
|
||||
// Skip dialog and display the item directly
|
||||
resetIntent(urls.get(0));
|
||||
startFeedDownload(urls.get(0));
|
||||
return true;
|
||||
}
|
||||
|
||||
final ArrayAdapter<String> adapter = new ArrayAdapter<>(OnlineFeedViewActivity.this,
|
||||
R.layout.ellipsize_start_listitem, R.id.txtvTitle, titles);
|
||||
DialogInterface.OnClickListener onClickListener = (dialog, which) -> {
|
||||
String selectedUrl = urls.get(which);
|
||||
dialog.dismiss();
|
||||
resetIntent(selectedUrl);
|
||||
startFeedDownload(selectedUrl);
|
||||
};
|
||||
|
||||
AlertDialog.Builder ab = new AlertDialog.Builder(OnlineFeedViewActivity.this)
|
||||
.setTitle(R.string.feeds_label)
|
||||
.setCancelable(true)
|
||||
.setOnCancelListener(dialog -> finish())
|
||||
.setAdapter(adapter, onClickListener);
|
||||
|
||||
runOnUiThread(() -> {
|
||||
if(dialog != null && dialog.isShowing()) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
dialog = ab.show();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private class FeedViewAuthenticationDialog extends AuthenticationDialog {
|
||||
|
||||
private final String feedUrl;
|
||||
|
||||
FeedViewAuthenticationDialog(Context context, int titleRes, String feedUrl) {
|
||||
super(context, titleRes, true, username, password);
|
||||
this.feedUrl = feedUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
super.onCancelled();
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfirmed(String username, String password) {
|
||||
OnlineFeedViewActivity.this.username = username;
|
||||
OnlineFeedViewActivity.this.password = password;
|
||||
startFeedDownload(feedUrl);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -6,7 +6,9 @@ import android.content.pm.PackageManager;
|
|||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.util.Log;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.Menu;
|
||||
|
@ -20,18 +22,18 @@ import android.widget.Toast;
|
|||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.export.opml.OpmlElement;
|
||||
import de.danoeh.antennapod.core.export.opml.OpmlReader;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager;
|
||||
import de.danoeh.antennapod.ui.common.ThemeSwitcher;
|
||||
|
||||
import de.danoeh.antennapod.core.service.download.DownloadService;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadRequestCreator;
|
||||
import de.danoeh.antennapod.storage.database.FeedDatabaseWriter;
|
||||
import de.danoeh.antennapod.databinding.OpmlSelectionBinding;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.storage.importexport.OpmlElement;
|
||||
import de.danoeh.antennapod.storage.importexport.OpmlReader;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
|
@ -43,7 +45,9 @@ import java.io.InputStream;
|
|||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Activity for Opml Import.
|
||||
|
@ -51,7 +55,7 @@ import java.util.List;
|
|||
public class OpmlImportActivity extends AppCompatActivity {
|
||||
private static final String TAG = "OpmlImportBaseActivity";
|
||||
@Nullable private Uri uri;
|
||||
OpmlSelectionBinding viewBinding;
|
||||
private OpmlSelectionBinding viewBinding;
|
||||
private ArrayAdapter<String> listAdapter;
|
||||
private MenuItem selectAll;
|
||||
private MenuItem deselectAll;
|
||||
|
@ -59,7 +63,7 @@ public class OpmlImportActivity extends AppCompatActivity {
|
|||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
setTheme(UserPreferences.getTheme());
|
||||
setTheme(ThemeSwitcher.getTheme(this));
|
||||
super.onCreate(savedInstanceState);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
viewBinding = OpmlSelectionBinding.inflate(getLayoutInflater());
|
||||
|
@ -95,9 +99,12 @@ public class OpmlImportActivity extends AppCompatActivity {
|
|||
continue;
|
||||
}
|
||||
OpmlElement element = readElements.get(checked.keyAt(i));
|
||||
Feed feed = new Feed(element.getXmlUrl(), null, element.getText());
|
||||
DownloadService.download(this, false, DownloadRequestCreator.create(feed).build());
|
||||
Feed feed = new Feed(element.getXmlUrl(), null,
|
||||
element.getText() != null ? element.getText() : "Unknown podcast");
|
||||
feed.setItems(Collections.emptyList());
|
||||
FeedDatabaseWriter.updateFeed(this, feed, false);
|
||||
}
|
||||
FeedUpdateManager.getInstance().runOnce(this);
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
@ -109,6 +116,7 @@ public class OpmlImportActivity extends AppCompatActivity {
|
|||
startActivity(intent);
|
||||
finish();
|
||||
}, e -> {
|
||||
e.printStackTrace();
|
||||
viewBinding.progressBar.setVisibility(View.GONE);
|
||||
Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
|
||||
});
|
||||
|
@ -128,22 +136,13 @@ public class OpmlImportActivity extends AppCompatActivity {
|
|||
|
||||
void importUri(@Nullable Uri uri) {
|
||||
if (uri == null) {
|
||||
new AlertDialog.Builder(this)
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setMessage(R.string.opml_import_error_no_file)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
this.uri = uri;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& uri.toString().contains(Environment.getExternalStorageDirectory().toString())) {
|
||||
int permission = ActivityCompat.checkSelfPermission(this,
|
||||
android.Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||
if (permission != PackageManager.PERMISSION_GRANTED) {
|
||||
requestPermission();
|
||||
return;
|
||||
}
|
||||
}
|
||||
startImport();
|
||||
}
|
||||
|
||||
|
@ -202,7 +201,7 @@ public class OpmlImportActivity extends AppCompatActivity {
|
|||
if (isGranted) {
|
||||
startImport();
|
||||
} else {
|
||||
new AlertDialog.Builder(this)
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setMessage(R.string.opml_import_ask_read_permission)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) ->
|
||||
requestPermission())
|
||||
|
@ -239,12 +238,29 @@ public class OpmlImportActivity extends AppCompatActivity {
|
|||
getTitleList());
|
||||
viewBinding.feedlist.setAdapter(listAdapter);
|
||||
}, e -> {
|
||||
Log.d(TAG, Log.getStackTraceString(e));
|
||||
String message = e.getMessage() == null ? "" : e.getMessage();
|
||||
if (message.toLowerCase(Locale.ROOT).contains("permission")
|
||||
&& Build.VERSION.SDK_INT >= 23) {
|
||||
int permission = ActivityCompat.checkSelfPermission(this,
|
||||
android.Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||
if (permission != PackageManager.PERMISSION_GRANTED) {
|
||||
requestPermission();
|
||||
return;
|
||||
}
|
||||
}
|
||||
viewBinding.progressBar.setVisibility(View.GONE);
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
MaterialAlertDialogBuilder alert = new MaterialAlertDialogBuilder(this);
|
||||
alert.setTitle(R.string.error_label);
|
||||
alert.setMessage(getString(R.string.opml_reader_error) + e.getMessage());
|
||||
alert.setNeutralButton(android.R.string.ok, (dialog, which) -> dialog.dismiss());
|
||||
alert.create().show();
|
||||
String userReadable = getString(R.string.opml_reader_error);
|
||||
String details = e.getMessage();
|
||||
String total = userReadable + "\n\n" + details;
|
||||
SpannableString errorMessage = new SpannableString(total);
|
||||
errorMessage.setSpan(new ForegroundColorSpan(0x88888888),
|
||||
userReadable.length(), total.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
alert.setMessage(errorMessage);
|
||||
alert.setPositiveButton(android.R.string.ok, (dialog, which) -> finish());
|
||||
alert.show();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
package de.danoeh.antennapod.activity;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
|
||||
|
||||
public class PlaybackSpeedDialogActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(UserPreferences.getTranslucentTheme());
|
||||
super.onCreate(savedInstanceState);
|
||||
VariableSpeedDialog speedDialog = new InnerVariableSpeedDialog();
|
||||
speedDialog.show(getSupportFragmentManager(), null);
|
||||
}
|
||||
|
||||
public static class InnerVariableSpeedDialog extends VariableSpeedDialog {
|
||||
@Override
|
||||
public void onDismiss(@NonNull DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
package de.danoeh.antennapod.activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
import com.bytehamster.lib.preferencesearch.SearchPreferenceResult;
|
||||
import com.bytehamster.lib.preferencesearch.SearchPreferenceResultListener;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.databinding.SettingsActivityBinding;
|
||||
import de.danoeh.antennapod.fragment.preferences.AutoDownloadPreferencesFragment;
|
||||
import de.danoeh.antennapod.fragment.preferences.ImportExportPreferencesFragment;
|
||||
import de.danoeh.antennapod.fragment.preferences.MainPreferencesFragment;
|
||||
import de.danoeh.antennapod.fragment.preferences.NetworkPreferencesFragment;
|
||||
import de.danoeh.antennapod.fragment.preferences.NotificationPreferencesFragment;
|
||||
import de.danoeh.antennapod.fragment.preferences.PlaybackPreferencesFragment;
|
||||
import de.danoeh.antennapod.fragment.preferences.StoragePreferencesFragment;
|
||||
import de.danoeh.antennapod.fragment.preferences.synchronization.SynchronizationPreferencesFragment;
|
||||
import de.danoeh.antennapod.fragment.preferences.SwipePreferencesFragment;
|
||||
import de.danoeh.antennapod.fragment.preferences.UserInterfacePreferencesFragment;
|
||||
|
||||
/**
|
||||
* PreferenceActivity for API 11+. In order to change the behavior of the preference UI, see
|
||||
* PreferenceController.
|
||||
*/
|
||||
public class PreferenceActivity extends AppCompatActivity implements SearchPreferenceResultListener {
|
||||
private static final String FRAGMENT_TAG = "tag_preferences";
|
||||
public static final String OPEN_AUTO_DOWNLOAD_SETTINGS = "OpenAutoDownloadSettings";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(UserPreferences.getTheme());
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
final SettingsActivityBinding binding = SettingsActivityBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
|
||||
if (getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG) == null) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.settingsContainer, new MainPreferencesFragment(), FRAGMENT_TAG)
|
||||
.commit();
|
||||
}
|
||||
Intent intent = getIntent();
|
||||
if (intent.getBooleanExtra(OPEN_AUTO_DOWNLOAD_SETTINGS, false)) {
|
||||
openScreen(R.xml.preferences_autodownload);
|
||||
}
|
||||
}
|
||||
|
||||
private PreferenceFragmentCompat getPreferenceScreen(int screen) {
|
||||
PreferenceFragmentCompat prefFragment = null;
|
||||
|
||||
if (screen == R.xml.preferences_user_interface) {
|
||||
prefFragment = new UserInterfacePreferencesFragment();
|
||||
} else if (screen == R.xml.preferences_network) {
|
||||
prefFragment = new NetworkPreferencesFragment();
|
||||
} else if (screen == R.xml.preferences_storage) {
|
||||
prefFragment = new StoragePreferencesFragment();
|
||||
} else if (screen == R.xml.preferences_import_export) {
|
||||
prefFragment = new ImportExportPreferencesFragment();
|
||||
} else if (screen == R.xml.preferences_autodownload) {
|
||||
prefFragment = new AutoDownloadPreferencesFragment();
|
||||
} else if (screen == R.xml.preferences_synchronization) {
|
||||
prefFragment = new SynchronizationPreferencesFragment();
|
||||
} else if (screen == R.xml.preferences_playback) {
|
||||
prefFragment = new PlaybackPreferencesFragment();
|
||||
} else if (screen == R.xml.preferences_notifications) {
|
||||
prefFragment = new NotificationPreferencesFragment();
|
||||
} else if (screen == R.xml.preferences_swipe) {
|
||||
prefFragment = new SwipePreferencesFragment();
|
||||
}
|
||||
return prefFragment;
|
||||
}
|
||||
|
||||
public static int getTitleOfPage(int preferences) {
|
||||
if (preferences == R.xml.preferences_network) {
|
||||
return R.string.network_pref;
|
||||
} else if (preferences == R.xml.preferences_autodownload) {
|
||||
return R.string.pref_automatic_download_title;
|
||||
} else if (preferences == R.xml.preferences_playback) {
|
||||
return R.string.playback_pref;
|
||||
} else if (preferences == R.xml.preferences_storage) {
|
||||
return R.string.storage_pref;
|
||||
} else if (preferences == R.xml.preferences_import_export) {
|
||||
return R.string.import_export_pref;
|
||||
} else if (preferences == R.xml.preferences_user_interface) {
|
||||
return R.string.user_interface_label;
|
||||
} else if (preferences == R.xml.preferences_synchronization) {
|
||||
return R.string.synchronization_pref;
|
||||
} else if (preferences == R.xml.preferences_notifications) {
|
||||
return R.string.notification_pref_fragment;
|
||||
} else if (preferences == R.xml.feed_settings) {
|
||||
return R.string.feed_settings_label;
|
||||
} else if (preferences == R.xml.preferences_swipe) {
|
||||
return R.string.swipeactions_label;
|
||||
}
|
||||
return R.string.settings_label;
|
||||
}
|
||||
|
||||
public PreferenceFragmentCompat openScreen(int screen) {
|
||||
PreferenceFragmentCompat fragment = getPreferenceScreen(screen);
|
||||
if (screen == R.xml.preferences_notifications && Build.VERSION.SDK_INT >= 26) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
|
||||
startActivity(intent);
|
||||
} else {
|
||||
getSupportFragmentManager().beginTransaction().replace(R.id.settingsContainer, fragment)
|
||||
.addToBackStack(getString(getTitleOfPage(screen))).commit();
|
||||
}
|
||||
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
||||
finish();
|
||||
} else {
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
||||
View view = getCurrentFocus();
|
||||
//If no view currently has focus, create a new one, just so we can grab a window token from it
|
||||
if (view == null) {
|
||||
view = new View(this);
|
||||
}
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
getSupportFragmentManager().popBackStack();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSearchResultClicked(SearchPreferenceResult result) {
|
||||
int screen = result.getResourceFile();
|
||||
if (screen == R.xml.feed_settings) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.feed_settings_label);
|
||||
builder.setMessage(R.string.pref_feed_settings_dialog_msg);
|
||||
builder.setPositiveButton(android.R.string.ok, null);
|
||||
builder.show();
|
||||
} else if (screen == R.xml.preferences_notifications) {
|
||||
openScreen(screen);
|
||||
} else {
|
||||
PreferenceFragmentCompat fragment = openScreen(result.getResourceFile());
|
||||
result.highlight(fragment);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,11 +26,12 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.NavDrawerData;
|
||||
import de.danoeh.antennapod.ui.common.ThemeSwitcher;
|
||||
import de.danoeh.antennapod.storage.database.DBReader;
|
||||
import de.danoeh.antennapod.storage.database.NavDrawerData;
|
||||
import de.danoeh.antennapod.databinding.SubscriptionSelectionActivityBinding;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
@ -47,7 +48,7 @@ public class SelectSubscriptionActivity extends AppCompatActivity {
|
|||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
setTheme(UserPreferences.getTranslucentTheme());
|
||||
setTheme(ThemeSwitcher.getTranslucentTheme(this));
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
viewBinding = SubscriptionSelectionActivityBinding.inflate(getLayoutInflater());
|
||||
|
@ -77,7 +78,7 @@ public class SelectSubscriptionActivity extends AppCompatActivity {
|
|||
public List<Feed> getFeedItems(List<NavDrawerData.DrawerItem> items, List<Feed> result) {
|
||||
for (NavDrawerData.DrawerItem item : items) {
|
||||
if (item.type == NavDrawerData.DrawerItem.Type.TAG) {
|
||||
getFeedItems(((NavDrawerData.TagDrawerItem) item).children, result);
|
||||
getFeedItems(((NavDrawerData.TagDrawerItem) item).getChildren(), result);
|
||||
} else {
|
||||
Feed feed = ((NavDrawerData.FeedDrawerItem) item).feed;
|
||||
if (!result.contains(feed)) {
|
||||
|
@ -99,7 +100,7 @@ public class SelectSubscriptionActivity extends AppCompatActivity {
|
|||
if (bitmap != null) {
|
||||
icon = IconCompat.createWithAdaptiveBitmap(bitmap);
|
||||
} else {
|
||||
icon = IconCompat.createWithResource(this, R.drawable.ic_folder_shortcut);
|
||||
icon = IconCompat.createWithResource(this, R.drawable.ic_shortcut_subscriptions);
|
||||
}
|
||||
|
||||
ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(this, id)
|
||||
|
@ -142,7 +143,8 @@ public class SelectSubscriptionActivity extends AppCompatActivity {
|
|||
}
|
||||
disposable = Observable.fromCallable(
|
||||
() -> {
|
||||
NavDrawerData data = DBReader.getNavDrawerData();
|
||||
NavDrawerData data = DBReader.getNavDrawerData(UserPreferences.getSubscriptionsFilter(),
|
||||
UserPreferences.getFeedOrder(), UserPreferences.getFeedCounterSetting());
|
||||
return getFeedItems(data.items, new ArrayList<>());
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
|
|
|
@ -1,19 +1,13 @@
|
|||
package de.danoeh.antennapod.activity;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.error.CrashReportWriter;
|
||||
import de.danoeh.antennapod.CrashReportWriter;
|
||||
import de.danoeh.antennapod.storage.database.PodDBAdapter;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
|
@ -22,21 +16,13 @@ import io.reactivex.schedulers.Schedulers;
|
|||
/**
|
||||
* Shows the AntennaPod logo while waiting for the main activity to start.
|
||||
*/
|
||||
public class SplashActivity extends AppCompatActivity {
|
||||
@SuppressLint("CustomSplashScreen")
|
||||
public class SplashActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.splash);
|
||||
|
||||
ProgressBar progressBar = findViewById(R.id.progressBar);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
Drawable wrapDrawable = DrawableCompat.wrap(progressBar.getIndeterminateDrawable());
|
||||
DrawableCompat.setTint(wrapDrawable, 0xffffffff);
|
||||
progressBar.setIndeterminateDrawable(DrawableCompat.unwrap(wrapDrawable));
|
||||
} else {
|
||||
progressBar.getIndeterminateDrawable().setColorFilter(
|
||||
new PorterDuffColorFilter(0xffffffff, PorterDuff.Mode.SRC_IN));
|
||||
}
|
||||
final View content = findViewById(android.R.id.content);
|
||||
content.getViewTreeObserver().addOnPreDrawListener(() -> false); // Keep splash screen active
|
||||
|
||||
Completable.create(subscriber -> {
|
||||
// Trigger schema updates
|
||||
|
|
|
@ -1,832 +0,0 @@
|
|||
package de.danoeh.antennapod.activity;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationSet;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.ScaleAnimation;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.SeekBar;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||
import com.bumptech.glide.Glide;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
|
||||
import de.danoeh.antennapod.event.playback.BufferUpdateEvent;
|
||||
import de.danoeh.antennapod.event.playback.PlaybackPositionEvent;
|
||||
import de.danoeh.antennapod.event.PlayerErrorEvent;
|
||||
import de.danoeh.antennapod.event.playback.PlaybackServiceEvent;
|
||||
import de.danoeh.antennapod.event.playback.SleepTimerUpdatedEvent;
|
||||
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.Converter;
|
||||
import de.danoeh.antennapod.core.util.FeedItemUtil;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import de.danoeh.antennapod.core.util.ShareUtils;
|
||||
import de.danoeh.antennapod.core.util.TimeSpeedConverter;
|
||||
import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
import de.danoeh.antennapod.databinding.VideoplayerActivityBinding;
|
||||
import de.danoeh.antennapod.dialog.PlaybackControlsDialog;
|
||||
import de.danoeh.antennapod.dialog.ShareDialog;
|
||||
import de.danoeh.antennapod.dialog.SkipPreferenceDialog;
|
||||
import de.danoeh.antennapod.dialog.SleepTimerDialog;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.model.playback.Playable;
|
||||
import de.danoeh.antennapod.playback.base.PlayerStatus;
|
||||
import de.danoeh.antennapod.playback.cast.CastEnabledActivity;
|
||||
import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
/**
|
||||
* Activity for playing video files.
|
||||
*/
|
||||
public class VideoplayerActivity extends CastEnabledActivity implements SeekBar.OnSeekBarChangeListener {
|
||||
private static final String TAG = "VideoplayerActivity";
|
||||
|
||||
/**
|
||||
* True if video controls are currently visible.
|
||||
*/
|
||||
private boolean videoControlsShowing = true;
|
||||
private boolean videoSurfaceCreated = false;
|
||||
private boolean destroyingDueToReload = false;
|
||||
private long lastScreenTap = 0;
|
||||
private Handler videoControlsHider = new Handler(Looper.getMainLooper());
|
||||
private VideoplayerActivityBinding viewBinding;
|
||||
private PlaybackController controller;
|
||||
private boolean showTimeLeft = false;
|
||||
private boolean isFavorite = false;
|
||||
private boolean switchToAudioOnly = false;
|
||||
private Disposable disposable;
|
||||
private float prog;
|
||||
|
||||
@SuppressLint("AppCompatMethod")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);
|
||||
// has to be called before setting layout content
|
||||
supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR_OVERLAY);
|
||||
setTheme(R.style.Theme_AntennaPod_VideoPlayer);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Log.d(TAG, "onCreate()");
|
||||
|
||||
getWindow().setFormat(PixelFormat.TRANSPARENT);
|
||||
viewBinding = VideoplayerActivityBinding.inflate(LayoutInflater.from(this));
|
||||
setContentView(viewBinding.getRoot());
|
||||
setupView();
|
||||
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(0x80000000));
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
switchToAudioOnly = false;
|
||||
if (PlaybackService.isCasting()) {
|
||||
Intent intent = PlaybackService.getPlayerActivityIntent(this);
|
||||
if (!intent.getComponent().getClassName().equals(VideoplayerActivity.class.getName())) {
|
||||
destroyingDueToReload = true;
|
||||
finish();
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
if (controller != null) {
|
||||
controller.release();
|
||||
controller = null; // prevent leak
|
||||
}
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
EventBus.getDefault().unregister(this);
|
||||
super.onStop();
|
||||
if (!PictureInPictureUtil.isInPictureInPictureMode(this)) {
|
||||
videoControlsHider.removeCallbacks(hideVideoControls);
|
||||
}
|
||||
// Controller released; we will not receive buffering updates
|
||||
viewBinding.progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserLeaveHint() {
|
||||
if (!PictureInPictureUtil.isInPictureInPictureMode(this)) {
|
||||
compatEnterPictureInPicture();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
controller = newPlaybackController();
|
||||
controller.init();
|
||||
loadMediaInfo();
|
||||
onPositionObserverUpdate();
|
||||
EventBus.getDefault().register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
if (!PictureInPictureUtil.isInPictureInPictureMode(this)) {
|
||||
if (controller != null && controller.getStatus() == PlayerStatus.PLAYING) {
|
||||
controller.pause();
|
||||
}
|
||||
}
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
super.onTrimMemory(level);
|
||||
Glide.get(this).trimMemory(level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
Glide.get(this).clearMemory();
|
||||
}
|
||||
|
||||
private PlaybackController newPlaybackController() {
|
||||
return new PlaybackController(this) {
|
||||
@Override
|
||||
public void onPositionObserverUpdate() {
|
||||
VideoplayerActivity.this.onPositionObserverUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReloadNotification(int code) {
|
||||
VideoplayerActivity.this.onReloadNotification(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updatePlayButtonShowsPlay(boolean showPlay) {
|
||||
viewBinding.playButton.setIsShowPlay(showPlay);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMediaInfo() {
|
||||
VideoplayerActivity.this.loadMediaInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAwaitingVideoSurface() {
|
||||
setupVideoAspectRatio();
|
||||
if (videoSurfaceCreated && controller != null) {
|
||||
Log.d(TAG, "Videosurface already created, setting videosurface now");
|
||||
controller.setVideoSurface(viewBinding.videoView.getHolder());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackEnd() {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setScreenOn(boolean enable) {
|
||||
if (enable) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
} else {
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@SuppressWarnings("unused")
|
||||
public void bufferUpdate(BufferUpdateEvent event) {
|
||||
if (event.hasStarted()) {
|
||||
viewBinding.progressBar.setVisibility(View.VISIBLE);
|
||||
} else if (event.hasEnded()) {
|
||||
viewBinding.progressBar.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
viewBinding.sbPosition.setSecondaryProgress((int) (event.getProgress() * viewBinding.sbPosition.getMax()));
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@SuppressWarnings("unused")
|
||||
public void sleepTimerUpdate(SleepTimerUpdatedEvent event) {
|
||||
if (event.isCancelled() || event.wasJustEnabled()) {
|
||||
supportInvalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
|
||||
protected void loadMediaInfo() {
|
||||
Log.d(TAG, "loadMediaInfo()");
|
||||
if (controller == null || controller.getMedia() == null) {
|
||||
return;
|
||||
}
|
||||
showTimeLeft = UserPreferences.shouldShowRemainingTime();
|
||||
onPositionObserverUpdate();
|
||||
checkFavorite();
|
||||
Playable media = controller.getMedia();
|
||||
if (media != null) {
|
||||
getSupportActionBar().setSubtitle(media.getEpisodeTitle());
|
||||
getSupportActionBar().setTitle(media.getFeedTitle());
|
||||
}
|
||||
}
|
||||
|
||||
protected void setupView() {
|
||||
showTimeLeft = UserPreferences.shouldShowRemainingTime();
|
||||
Log.d("timeleft", showTimeLeft ? "true" : "false");
|
||||
viewBinding.durationLabel.setOnClickListener(v -> {
|
||||
showTimeLeft = !showTimeLeft;
|
||||
Playable media = controller.getMedia();
|
||||
if (media == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TimeSpeedConverter converter = new TimeSpeedConverter(controller.getCurrentPlaybackSpeedMultiplier());
|
||||
String length;
|
||||
if (showTimeLeft) {
|
||||
int remainingTime = converter.convert(media.getDuration() - media.getPosition());
|
||||
length = "-" + Converter.getDurationStringLong(remainingTime);
|
||||
} else {
|
||||
int duration = converter.convert(media.getDuration());
|
||||
length = Converter.getDurationStringLong(duration);
|
||||
}
|
||||
viewBinding.durationLabel.setText(length);
|
||||
|
||||
UserPreferences.setShowRemainTimeSetting(showTimeLeft);
|
||||
Log.d("timeleft on click", showTimeLeft ? "true" : "false");
|
||||
});
|
||||
|
||||
viewBinding.sbPosition.setOnSeekBarChangeListener(this);
|
||||
viewBinding.rewindButton.setOnClickListener(v -> onRewind());
|
||||
viewBinding.rewindButton.setOnLongClickListener(v -> {
|
||||
SkipPreferenceDialog.showSkipPreference(VideoplayerActivity.this,
|
||||
SkipPreferenceDialog.SkipDirection.SKIP_REWIND, null);
|
||||
return true;
|
||||
});
|
||||
viewBinding.playButton.setIsVideoScreen(true);
|
||||
viewBinding.playButton.setOnClickListener(v -> onPlayPause());
|
||||
viewBinding.fastForwardButton.setOnClickListener(v -> onFastForward());
|
||||
viewBinding.fastForwardButton.setOnLongClickListener(v -> {
|
||||
SkipPreferenceDialog.showSkipPreference(VideoplayerActivity.this,
|
||||
SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, null);
|
||||
return false;
|
||||
});
|
||||
// To suppress touches directly below the slider
|
||||
viewBinding.bottomControlsContainer.setOnTouchListener((view, motionEvent) -> true);
|
||||
viewBinding.bottomControlsContainer.setFitsSystemWindows(true);
|
||||
viewBinding.videoView.getHolder().addCallback(surfaceHolderCallback);
|
||||
viewBinding.videoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
|
||||
|
||||
setupVideoControlsToggler();
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
|
||||
viewBinding.videoPlayerContainer.setOnTouchListener(onVideoviewTouched);
|
||||
viewBinding.videoPlayerContainer.getViewTreeObserver().addOnGlobalLayoutListener(() ->
|
||||
viewBinding.videoView.setAvailableSize(
|
||||
viewBinding.videoPlayerContainer.getWidth(), viewBinding.videoPlayerContainer.getHeight()));
|
||||
}
|
||||
|
||||
private final Runnable hideVideoControls = () -> {
|
||||
if (videoControlsShowing) {
|
||||
Log.d(TAG, "Hiding video controls");
|
||||
getSupportActionBar().hide();
|
||||
hideVideoControls(true);
|
||||
videoControlsShowing = false;
|
||||
}
|
||||
};
|
||||
|
||||
private final View.OnTouchListener onVideoviewTouched = (v, event) -> {
|
||||
if (event.getAction() != MotionEvent.ACTION_DOWN) {
|
||||
return false;
|
||||
}
|
||||
if (PictureInPictureUtil.isInPictureInPictureMode(this)) {
|
||||
return true;
|
||||
}
|
||||
videoControlsHider.removeCallbacks(hideVideoControls);
|
||||
|
||||
if (System.currentTimeMillis() - lastScreenTap < 300) {
|
||||
if (event.getX() > v.getMeasuredWidth() / 2.0f) {
|
||||
onFastForward();
|
||||
showSkipAnimation(true);
|
||||
} else {
|
||||
onRewind();
|
||||
showSkipAnimation(false);
|
||||
}
|
||||
if (videoControlsShowing) {
|
||||
getSupportActionBar().hide();
|
||||
hideVideoControls(false);
|
||||
videoControlsShowing = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
toggleVideoControlsVisibility();
|
||||
if (videoControlsShowing) {
|
||||
setupVideoControlsToggler();
|
||||
}
|
||||
|
||||
lastScreenTap = System.currentTimeMillis();
|
||||
return true;
|
||||
};
|
||||
|
||||
private void showSkipAnimation(boolean isForward) {
|
||||
AnimationSet skipAnimation = new AnimationSet(true);
|
||||
skipAnimation.addAnimation(new ScaleAnimation(1f, 2f, 1f, 2f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f));
|
||||
skipAnimation.addAnimation(new AlphaAnimation(1f, 0f));
|
||||
skipAnimation.setFillAfter(false);
|
||||
skipAnimation.setDuration(800);
|
||||
|
||||
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) viewBinding.skipAnimationImage.getLayoutParams();
|
||||
if (isForward) {
|
||||
viewBinding.skipAnimationImage.setImageResource(R.drawable.ic_fast_forward_video_white);
|
||||
params.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
|
||||
} else {
|
||||
viewBinding.skipAnimationImage.setImageResource(R.drawable.ic_fast_rewind_video_white);
|
||||
params.gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;
|
||||
}
|
||||
|
||||
viewBinding.skipAnimationImage.setVisibility(View.VISIBLE);
|
||||
viewBinding.skipAnimationImage.setLayoutParams(params);
|
||||
viewBinding.skipAnimationImage.startAnimation(skipAnimation);
|
||||
skipAnimation.setAnimationListener(new Animation.AnimationListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
viewBinding.skipAnimationImage.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation animation) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupVideoControlsToggler() {
|
||||
videoControlsHider.removeCallbacks(hideVideoControls);
|
||||
videoControlsHider.postDelayed(hideVideoControls, 2500);
|
||||
}
|
||||
|
||||
private void setupVideoAspectRatio() {
|
||||
if (videoSurfaceCreated && controller != null) {
|
||||
Pair<Integer, Integer> videoSize = controller.getVideoSize();
|
||||
if (videoSize != null && videoSize.first > 0 && videoSize.second > 0) {
|
||||
Log.d(TAG, "Width,height of video: " + videoSize.first + ", " + videoSize.second);
|
||||
viewBinding.videoView.setVideoSize(videoSize.first, videoSize.second);
|
||||
} else {
|
||||
Log.e(TAG, "Could not determine video size");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleVideoControlsVisibility() {
|
||||
if (videoControlsShowing) {
|
||||
getSupportActionBar().hide();
|
||||
hideVideoControls(true);
|
||||
} else {
|
||||
getSupportActionBar().show();
|
||||
showVideoControls();
|
||||
}
|
||||
videoControlsShowing = !videoControlsShowing;
|
||||
}
|
||||
|
||||
void onRewind() {
|
||||
if (controller == null) {
|
||||
return;
|
||||
}
|
||||
int curr = controller.getPosition();
|
||||
controller.seekTo(curr - UserPreferences.getRewindSecs() * 1000);
|
||||
setupVideoControlsToggler();
|
||||
}
|
||||
|
||||
void onPlayPause() {
|
||||
if (controller == null) {
|
||||
return;
|
||||
}
|
||||
controller.playPause();
|
||||
setupVideoControlsToggler();
|
||||
}
|
||||
|
||||
void onFastForward() {
|
||||
if (controller == null) {
|
||||
return;
|
||||
}
|
||||
int curr = controller.getPosition();
|
||||
controller.seekTo(curr + UserPreferences.getFastForwardSecs() * 1000);
|
||||
setupVideoControlsToggler();
|
||||
}
|
||||
|
||||
private final SurfaceHolder.Callback surfaceHolderCallback = new SurfaceHolder.Callback() {
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
holder.setFixedSize(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
Log.d(TAG, "Videoview holder created");
|
||||
videoSurfaceCreated = true;
|
||||
if (controller != null && controller.getStatus() == PlayerStatus.PLAYING) {
|
||||
controller.setVideoSurface(holder);
|
||||
}
|
||||
setupVideoAspectRatio();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
Log.d(TAG, "Videosurface was destroyed");
|
||||
videoSurfaceCreated = false;
|
||||
if (controller != null && !destroyingDueToReload && !switchToAudioOnly) {
|
||||
controller.notifyVideoSurfaceAbandoned();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected void onReloadNotification(int notificationCode) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && PictureInPictureUtil.isInPictureInPictureMode(this)) {
|
||||
if (notificationCode == PlaybackService.EXTRA_CODE_AUDIO
|
||||
|| notificationCode == PlaybackService.EXTRA_CODE_CAST) {
|
||||
finish();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (notificationCode == PlaybackService.EXTRA_CODE_CAST) {
|
||||
Log.d(TAG, "ReloadNotification received, switching to Castplayer now");
|
||||
destroyingDueToReload = true;
|
||||
finish();
|
||||
new MainActivityStarter(this).withOpenPlayer().start();
|
||||
}
|
||||
}
|
||||
|
||||
private void showVideoControls() {
|
||||
viewBinding.bottomControlsContainer.setVisibility(View.VISIBLE);
|
||||
viewBinding.controlsContainer.setVisibility(View.VISIBLE);
|
||||
final Animation animation = AnimationUtils.loadAnimation(this, R.anim.fade_in);
|
||||
if (animation != null) {
|
||||
viewBinding.bottomControlsContainer.startAnimation(animation);
|
||||
viewBinding.controlsContainer.startAnimation(animation);
|
||||
}
|
||||
viewBinding.videoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
|
||||
}
|
||||
|
||||
private void hideVideoControls(boolean showAnimation) {
|
||||
if (showAnimation) {
|
||||
final Animation animation = AnimationUtils.loadAnimation(this, R.anim.fade_out);
|
||||
if (animation != null) {
|
||||
viewBinding.bottomControlsContainer.startAnimation(animation);
|
||||
viewBinding.controlsContainer.startAnimation(animation);
|
||||
}
|
||||
}
|
||||
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
|
||||
viewBinding.bottomControlsContainer.setFitsSystemWindows(true);
|
||||
|
||||
viewBinding.bottomControlsContainer.setVisibility(View.GONE);
|
||||
viewBinding.controlsContainer.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(PlaybackPositionEvent event) {
|
||||
onPositionObserverUpdate();
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onPlaybackServiceChanged(PlaybackServiceEvent event) {
|
||||
if (event.action == PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onMediaPlayerError(PlayerErrorEvent event) {
|
||||
final AlertDialog.Builder errorDialog = new AlertDialog.Builder(VideoplayerActivity.this);
|
||||
errorDialog.setTitle(R.string.error_label);
|
||||
errorDialog.setMessage(event.getMessage());
|
||||
errorDialog.setNeutralButton(android.R.string.ok, (dialog, which) -> finish());
|
||||
errorDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
requestCastButton(menu);
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.mediaplayer, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
Playable media = controller.getMedia();
|
||||
boolean isFeedMedia = (media instanceof FeedMedia);
|
||||
|
||||
menu.findItem(R.id.open_feed_item).setVisible(isFeedMedia); // FeedMedia implies it belongs to a Feed
|
||||
|
||||
boolean hasWebsiteLink = getWebsiteLinkWithFallback(media) != null;
|
||||
menu.findItem(R.id.visit_website_item).setVisible(hasWebsiteLink);
|
||||
|
||||
boolean isItemAndHasLink = isFeedMedia && ShareUtils.hasLinkToShare(((FeedMedia) media).getItem());
|
||||
boolean isItemHasDownloadLink = isFeedMedia && ((FeedMedia) media).getDownload_url() != null;
|
||||
menu.findItem(R.id.share_item).setVisible(hasWebsiteLink || isItemAndHasLink || isItemHasDownloadLink);
|
||||
|
||||
menu.findItem(R.id.add_to_favorites_item).setVisible(false);
|
||||
menu.findItem(R.id.remove_from_favorites_item).setVisible(false);
|
||||
if (isFeedMedia) {
|
||||
menu.findItem(R.id.add_to_favorites_item).setVisible(!isFavorite);
|
||||
menu.findItem(R.id.remove_from_favorites_item).setVisible(isFavorite);
|
||||
}
|
||||
|
||||
menu.findItem(R.id.set_sleeptimer_item).setVisible(!controller.sleepTimerActive());
|
||||
menu.findItem(R.id.disable_sleeptimer_item).setVisible(controller.sleepTimerActive());
|
||||
|
||||
menu.findItem(R.id.player_switch_to_audio_only).setVisible(true);
|
||||
menu.findItem(R.id.audio_controls).setIcon(R.drawable.ic_sliders);
|
||||
menu.findItem(R.id.playback_speed).setVisible(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.player_switch_to_audio_only) {
|
||||
switchToAudioOnly = true;
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
Intent intent = new Intent(VideoplayerActivity.this, MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Playable media = controller.getMedia();
|
||||
if (media == null) {
|
||||
return false;
|
||||
}
|
||||
final @Nullable FeedItem feedItem = getFeedItem(media); // some options option requires FeedItem
|
||||
if (item.getItemId() == R.id.add_to_favorites_item && feedItem != null) {
|
||||
DBWriter.addFavoriteItem(feedItem);
|
||||
isFavorite = true;
|
||||
invalidateOptionsMenu();
|
||||
} else if (item.getItemId() == R.id.remove_from_favorites_item && feedItem != null) {
|
||||
DBWriter.removeFavoriteItem(feedItem);
|
||||
isFavorite = false;
|
||||
invalidateOptionsMenu();
|
||||
} else if (item.getItemId() == R.id.disable_sleeptimer_item
|
||||
|| item.getItemId() == R.id.set_sleeptimer_item) {
|
||||
new SleepTimerDialog().show(getSupportFragmentManager(), "SleepTimerDialog");
|
||||
} else if (item.getItemId() == R.id.audio_controls) {
|
||||
PlaybackControlsDialog dialog = PlaybackControlsDialog.newInstance();
|
||||
dialog.show(getSupportFragmentManager(), "playback_controls");
|
||||
} else if (item.getItemId() == R.id.open_feed_item && feedItem != null) {
|
||||
Intent intent = MainActivity.getIntentToOpenFeed(this, feedItem.getFeedId());
|
||||
startActivity(intent);
|
||||
} else if (item.getItemId() == R.id.visit_website_item) {
|
||||
IntentUtils.openInBrowser(VideoplayerActivity.this, getWebsiteLinkWithFallback(media));
|
||||
} else if (item.getItemId() == R.id.share_item && feedItem != null) {
|
||||
ShareDialog shareDialog = ShareDialog.newInstance(feedItem);
|
||||
shareDialog.show(getSupportFragmentManager(), "ShareEpisodeDialog");
|
||||
} else if (item.getItemId() == R.id.playback_speed) {
|
||||
new VariableSpeedDialog().show(getSupportFragmentManager(), null);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String getWebsiteLinkWithFallback(Playable media) {
|
||||
if (media == null) {
|
||||
return null;
|
||||
} else if (StringUtils.isNotBlank(media.getWebsiteLink())) {
|
||||
return media.getWebsiteLink();
|
||||
} else if (media instanceof FeedMedia) {
|
||||
return FeedItemUtil.getLinkWithFallback(((FeedMedia) media).getItem());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void onPositionObserverUpdate() {
|
||||
if (controller == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TimeSpeedConverter converter = new TimeSpeedConverter(controller.getCurrentPlaybackSpeedMultiplier());
|
||||
int currentPosition = converter.convert(controller.getPosition());
|
||||
int duration = converter.convert(controller.getDuration());
|
||||
int remainingTime = converter.convert(
|
||||
controller.getDuration() - controller.getPosition());
|
||||
Log.d(TAG, "currentPosition " + Converter.getDurationStringLong(currentPosition));
|
||||
if (currentPosition == PlaybackService.INVALID_TIME
|
||||
|| duration == PlaybackService.INVALID_TIME) {
|
||||
Log.w(TAG, "Could not react to position observer update because of invalid time");
|
||||
return;
|
||||
}
|
||||
viewBinding.positionLabel.setText(Converter.getDurationStringLong(currentPosition));
|
||||
if (showTimeLeft) {
|
||||
viewBinding.durationLabel.setText("-" + Converter.getDurationStringLong(remainingTime));
|
||||
} else {
|
||||
viewBinding.durationLabel.setText(Converter.getDurationStringLong(duration));
|
||||
}
|
||||
updateProgressbarPosition(currentPosition, duration);
|
||||
}
|
||||
|
||||
private void updateProgressbarPosition(int position, int duration) {
|
||||
Log.d(TAG, "updateProgressbarPosition(" + position + ", " + duration + ")");
|
||||
float progress = ((float) position) / duration;
|
||||
viewBinding.sbPosition.setProgress((int) (progress * viewBinding.sbPosition.getMax()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
if (controller == null) {
|
||||
return;
|
||||
}
|
||||
if (fromUser) {
|
||||
prog = progress / ((float) seekBar.getMax());
|
||||
TimeSpeedConverter converter = new TimeSpeedConverter(controller.getCurrentPlaybackSpeedMultiplier());
|
||||
int position = converter.convert((int) (prog * controller.getDuration()));
|
||||
viewBinding.seekPositionLabel.setText(Converter.getDurationStringLong(position));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
viewBinding.seekCardView.setScaleX(.8f);
|
||||
viewBinding.seekCardView.setScaleY(.8f);
|
||||
viewBinding.seekCardView.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.alpha(1f).scaleX(1f).scaleY(1f)
|
||||
.setDuration(200)
|
||||
.start();
|
||||
videoControlsHider.removeCallbacks(hideVideoControls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
if (controller != null) {
|
||||
controller.seekTo((int) (prog * controller.getDuration()));
|
||||
}
|
||||
viewBinding.seekCardView.setScaleX(1f);
|
||||
viewBinding.seekCardView.setScaleY(1f);
|
||||
viewBinding.seekCardView.animate()
|
||||
.setInterpolator(new FastOutSlowInInterpolator())
|
||||
.alpha(0f).scaleX(.8f).scaleY(.8f)
|
||||
.setDuration(200)
|
||||
.start();
|
||||
setupVideoControlsToggler();
|
||||
}
|
||||
|
||||
private void checkFavorite() {
|
||||
FeedItem feedItem = getFeedItem(controller.getMedia());
|
||||
if (feedItem == null) {
|
||||
return;
|
||||
}
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
disposable = Observable.fromCallable(() -> DBReader.getFeedItem(feedItem.getId()))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
item -> {
|
||||
boolean isFav = item.isTagged(FeedItem.TAG_FAVORITE);
|
||||
if (isFavorite != isFav) {
|
||||
isFavorite = isFav;
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static FeedItem getFeedItem(@Nullable Playable playable) {
|
||||
if (playable instanceof FeedMedia) {
|
||||
return ((FeedMedia) playable).getItem();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void compatEnterPictureInPicture() {
|
||||
if (PictureInPictureUtil.supportsPictureInPicture(this) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
getSupportActionBar().hide();
|
||||
hideVideoControls(false);
|
||||
enterPictureInPictureMode();
|
||||
}
|
||||
}
|
||||
|
||||
//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) {
|
||||
case KeyEvent.KEYCODE_P: //Fallthrough
|
||||
case KeyEvent.KEYCODE_SPACE:
|
||||
onPlayPause();
|
||||
toggleVideoControlsVisibility();
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_J: //Fallthrough
|
||||
case KeyEvent.KEYCODE_A:
|
||||
case KeyEvent.KEYCODE_COMMA:
|
||||
onRewind();
|
||||
showSkipAnimation(false);
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_K: //Fallthrough
|
||||
case KeyEvent.KEYCODE_D:
|
||||
case KeyEvent.KEYCODE_PERIOD:
|
||||
onFastForward();
|
||||
showSkipAnimation(true);
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_F: //Fallthrough
|
||||
case KeyEvent.KEYCODE_ESCAPE:
|
||||
//Exit fullscreen mode
|
||||
onBackPressed();
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_I:
|
||||
compatEnterPictureInPicture();
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_PLUS: //Fallthrough
|
||||
case KeyEvent.KEYCODE_W:
|
||||
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
|
||||
AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI);
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_MINUS: //Fallthrough
|
||||
case KeyEvent.KEYCODE_S:
|
||||
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
|
||||
AudioManager.ADJUST_LOWER, AudioManager.FLAG_SHOW_UI);
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_M:
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
|
||||
AudioManager.ADJUST_TOGGLE_MUTE, AudioManager.FLAG_SHOW_UI);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//Go to x% of video:
|
||||
if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
|
||||
controller.seekTo((int) (0.1f * (keyCode - KeyEvent.KEYCODE_0) * controller.getDuration()));
|
||||
return true;
|
||||
}
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
package de.danoeh.antennapod.activity;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.receiver.PlayerWidget;
|
||||
import de.danoeh.antennapod.core.widget.WidgetUpdaterWorker;
|
||||
|
||||
public class WidgetConfigActivity extends AppCompatActivity {
|
||||
private int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
|
||||
|
||||
private SeekBar opacitySeekBar;
|
||||
private TextView opacityTextView;
|
||||
private View widgetPreview;
|
||||
private CheckBox ckPlaybackSpeed;
|
||||
private CheckBox ckRewind;
|
||||
private CheckBox ckFastForward;
|
||||
private CheckBox ckSkip;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(UserPreferences.getTheme());
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_widget_config);
|
||||
|
||||
Intent configIntent = getIntent();
|
||||
Bundle extras = configIntent.getExtras();
|
||||
if (extras != null) {
|
||||
appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,
|
||||
AppWidgetManager.INVALID_APPWIDGET_ID);
|
||||
}
|
||||
|
||||
Intent resultValue = new Intent();
|
||||
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
|
||||
setResult(RESULT_CANCELED, resultValue);
|
||||
if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
|
||||
finish();
|
||||
}
|
||||
|
||||
opacityTextView = findViewById(R.id.widget_opacity_textView);
|
||||
opacitySeekBar = findViewById(R.id.widget_opacity_seekBar);
|
||||
widgetPreview = findViewById(R.id.widgetLayout);
|
||||
findViewById(R.id.butConfirm).setOnClickListener(v -> confirmCreateWidget());
|
||||
opacitySeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
|
||||
opacityTextView.setText(seekBar.getProgress() + "%");
|
||||
int color = getColorWithAlpha(PlayerWidget.DEFAULT_COLOR, opacitySeekBar.getProgress());
|
||||
widgetPreview.setBackgroundColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
ckPlaybackSpeed = findViewById(R.id.ckPlaybackSpeed);
|
||||
ckPlaybackSpeed.setOnClickListener(v -> displayPreviewPanel());
|
||||
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 =
|
||||
ckPlaybackSpeed.isChecked() || 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.butPlaybackSpeed)
|
||||
.setVisibility(ckPlaybackSpeed.isChecked() ? View.VISIBLE : View.GONE);
|
||||
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 confirmCreateWidget() {
|
||||
int backgroundColor = getColorWithAlpha(PlayerWidget.DEFAULT_COLOR, opacitySeekBar.getProgress());
|
||||
|
||||
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_PLAYBACK_SPEED + appWidgetId, ckPlaybackSpeed.isChecked());
|
||||
editor.putBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, ckSkip.isChecked());
|
||||
editor.putBoolean(PlayerWidget.KEY_WIDGET_REWIND + appWidgetId, ckRewind.isChecked());
|
||||
editor.putBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + appWidgetId, ckFastForward.isChecked());
|
||||
editor.apply();
|
||||
|
||||
Intent resultValue = new Intent();
|
||||
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
|
||||
setResult(RESULT_OK, resultValue);
|
||||
finish();
|
||||
WidgetUpdaterWorker.enqueueWork(this);
|
||||
}
|
||||
|
||||
private int getColorWithAlpha(int color, int opacity) {
|
||||
return (int) Math.round(0xFF * (0.01 * opacity)) * 0x1000000 + color;
|
||||
}
|
||||
}
|
|
@ -1,179 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.resource.bitmap.FitCenter;
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.model.feed.Chapter;
|
||||
import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
||||
import de.danoeh.antennapod.core.util.Converter;
|
||||
import de.danoeh.antennapod.model.feed.EmbeddedChapterImage;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
import de.danoeh.antennapod.model.playback.Playable;
|
||||
import de.danoeh.antennapod.ui.common.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) {
|
||||
this.callback = callback;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void setMedia(Playable media) {
|
||||
this.media = media;
|
||||
hasImages = false;
|
||||
if (media.getChapters() != null) {
|
||||
for (Chapter chapter : media.getChapters()) {
|
||||
if (!TextUtils.isEmpty(chapter.getImageUrl())) {
|
||||
hasImages = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ChapterHolder holder, int position) {
|
||||
Chapter sc = getItem(position);
|
||||
if (sc == null) {
|
||||
holder.title.setText("Error");
|
||||
return;
|
||||
}
|
||||
holder.title.setText(sc.getTitle());
|
||||
holder.start.setText(Converter.getDurationStringLong((int) sc
|
||||
.getStart()));
|
||||
|
||||
long duration;
|
||||
if (position + 1 < media.getChapters().size()) {
|
||||
duration = media.getChapters().get(position + 1).getStart() - sc.getStart();
|
||||
} else {
|
||||
duration = media.getDuration() - sc.getStart();
|
||||
}
|
||||
holder.duration.setText(context.getString(R.string.chapter_duration,
|
||||
Converter.getDurationStringLocalized(context, (int) duration)));
|
||||
|
||||
if (TextUtils.isEmpty(sc.getLink())) {
|
||||
holder.link.setVisibility(View.GONE);
|
||||
} else {
|
||||
holder.link.setVisibility(View.VISIBLE);
|
||||
holder.link.setText(sc.getLink());
|
||||
holder.link.setOnClickListener(v -> IntentUtils.openInBrowser(context, sc.getLink()));
|
||||
}
|
||||
holder.secondaryActionIcon.setImageResource(R.drawable.ic_play_48dp);
|
||||
holder.secondaryActionButton.setContentDescription(context.getString(R.string.play_chapter));
|
||||
holder.secondaryActionButton.setOnClickListener(v -> {
|
||||
if (callback != null) {
|
||||
callback.onPlayChapterButtonClicked(position);
|
||||
}
|
||||
});
|
||||
|
||||
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(R.drawable.ic_replay);
|
||||
} else {
|
||||
holder.itemView.setBackgroundColor(ContextCompat.getColor(context, android.R.color.transparent));
|
||||
holder.progressBar.setPercentage(0, null);
|
||||
}
|
||||
|
||||
if (hasImages) {
|
||||
holder.image.setVisibility(View.VISIBLE);
|
||||
if (TextUtils.isEmpty(sc.getImageUrl())) {
|
||||
Glide.with(context).clear(holder.image);
|
||||
} else {
|
||||
Glide.with(context)
|
||||
.load(EmbeddedChapterImage.getModelFor(media, position))
|
||||
.apply(new RequestOptions()
|
||||
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
|
||||
.dontAnimate()
|
||||
.transform(new FitCenter(), new RoundedCorners((int)
|
||||
(4 * context.getResources().getDisplayMetrics().density))))
|
||||
.into(holder.image);
|
||||
}
|
||||
} else {
|
||||
holder.image.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ChapterHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
return new ChapterHolder(inflater.inflate(R.layout.simplechapter_item, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (media == null || media.getChapters() == null) {
|
||||
return 0;
|
||||
}
|
||||
return media.getChapters().size();
|
||||
}
|
||||
|
||||
static class ChapterHolder extends RecyclerView.ViewHolder {
|
||||
final TextView title;
|
||||
final TextView start;
|
||||
final TextView link;
|
||||
final TextView duration;
|
||||
final ImageView image;
|
||||
final View secondaryActionButton;
|
||||
final ImageView secondaryActionIcon;
|
||||
final CircularProgressBar progressBar;
|
||||
|
||||
public ChapterHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
title = itemView.findViewById(R.id.txtvTitle);
|
||||
start = itemView.findViewById(R.id.txtvStart);
|
||||
link = itemView.findViewById(R.id.txtvLink);
|
||||
image = itemView.findViewById(R.id.imgvCover);
|
||||
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");
|
||||
}
|
||||
|
||||
public Chapter getItem(int position) {
|
||||
return media.getChapters().get(position);
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void onPlayChapterButtonClicked(int position);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.palette.graphics.Palette;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.RequestBuilder;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.bumptech.glide.request.target.CustomViewTarget;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import com.bumptech.glide.request.transition.Transition;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
||||
import de.danoeh.antennapod.core.glide.PaletteBitmap;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
|
||||
public class CoverLoader {
|
||||
private int resource = 0;
|
||||
private String uri;
|
||||
private String fallbackUri;
|
||||
private TextView txtvPlaceholder;
|
||||
private ImageView imgvCover;
|
||||
private boolean textAndImageCombined;
|
||||
private MainActivity activity;
|
||||
|
||||
public CoverLoader(MainActivity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
public CoverLoader withUri(String uri) {
|
||||
this.uri = uri;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CoverLoader withResource(int resource) {
|
||||
this.resource = resource;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CoverLoader withFallbackUri(String uri) {
|
||||
fallbackUri = uri;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CoverLoader withCoverView(ImageView coverView) {
|
||||
imgvCover = coverView;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CoverLoader withPlaceholderView(TextView placeholderView) {
|
||||
txtvPlaceholder = placeholderView;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cover text and if it should be shown even if there is a cover image.
|
||||
*
|
||||
* @param placeholderView Cover text.
|
||||
* @param textAndImageCombined Show cover text even if there is a cover image?
|
||||
*/
|
||||
@NonNull
|
||||
public CoverLoader withPlaceholderView(@NonNull TextView placeholderView, boolean textAndImageCombined) {
|
||||
this.txtvPlaceholder = placeholderView;
|
||||
this.textAndImageCombined = textAndImageCombined;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void load() {
|
||||
CoverTarget coverTarget = new CoverTarget(txtvPlaceholder, imgvCover, textAndImageCombined);
|
||||
|
||||
if (resource != 0) {
|
||||
Glide.with(activity).clear(coverTarget);
|
||||
imgvCover.setImageResource(resource);
|
||||
CoverTarget.setPlaceholderVisibility(txtvPlaceholder, textAndImageCombined, null);
|
||||
return;
|
||||
}
|
||||
|
||||
RequestOptions options = new RequestOptions()
|
||||
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
|
||||
.fitCenter()
|
||||
.dontAnimate();
|
||||
|
||||
RequestBuilder<PaletteBitmap> builder = Glide.with(activity)
|
||||
.as(PaletteBitmap.class)
|
||||
.load(uri)
|
||||
.apply(options);
|
||||
|
||||
if (fallbackUri != null && txtvPlaceholder != null && imgvCover != null) {
|
||||
builder = builder.error(Glide.with(activity)
|
||||
.as(PaletteBitmap.class)
|
||||
.load(fallbackUri)
|
||||
.apply(options));
|
||||
}
|
||||
|
||||
builder.into(coverTarget);
|
||||
}
|
||||
|
||||
static class CoverTarget extends CustomViewTarget<ImageView, PaletteBitmap> {
|
||||
private final WeakReference<TextView> placeholder;
|
||||
private final WeakReference<ImageView> cover;
|
||||
private boolean textAndImageCombined;
|
||||
|
||||
public CoverTarget(TextView txtvPlaceholder, ImageView imgvCover, boolean textAndImageCombined) {
|
||||
super(imgvCover);
|
||||
if (txtvPlaceholder != null) {
|
||||
txtvPlaceholder.setVisibility(View.VISIBLE);
|
||||
}
|
||||
placeholder = new WeakReference<>(txtvPlaceholder);
|
||||
cover = new WeakReference<>(imgvCover);
|
||||
this.textAndImageCombined = textAndImageCombined;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFailed(Drawable errorDrawable) {
|
||||
setPlaceholderVisibility(this.placeholder.get(), true, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResourceReady(@NonNull PaletteBitmap resource,
|
||||
@Nullable Transition<? super PaletteBitmap> transition) {
|
||||
ImageView ivCover = cover.get();
|
||||
ivCover.setImageBitmap(resource.bitmap);
|
||||
setPlaceholderVisibility(placeholder.get(), textAndImageCombined, resource.palette);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResourceCleared(@Nullable Drawable placeholder) {
|
||||
ImageView ivCover = cover.get();
|
||||
ivCover.setImageDrawable(placeholder);
|
||||
setPlaceholderVisibility(this.placeholder.get(), textAndImageCombined, null);
|
||||
}
|
||||
|
||||
static void setPlaceholderVisibility(TextView placeholder, boolean textAndImageCombined, Palette palette) {
|
||||
boolean showTitle = UserPreferences.shouldShowSubscriptionTitle();
|
||||
if (placeholder != null) {
|
||||
if (textAndImageCombined || showTitle) {
|
||||
final Context context = placeholder.getContext();
|
||||
placeholder.setVisibility(View.VISIBLE);
|
||||
int bgColor = ContextCompat.getColor(context, R.color.feed_text_bg);
|
||||
if (palette == null || !showTitle) {
|
||||
placeholder.setBackgroundColor(bgColor);
|
||||
placeholder.setTextColor(ThemeUtils.getColorFromAttr(placeholder.getContext(),
|
||||
android.R.attr.textColorPrimary));
|
||||
return;
|
||||
}
|
||||
int dominantColor = palette.getDominantColor(bgColor);
|
||||
int textColor = ContextCompat.getColor(context, R.color.white);
|
||||
if (ColorUtils.calculateLuminance(dominantColor) > 0.5) {
|
||||
textColor = ContextCompat.getColor(context, R.color.black);
|
||||
}
|
||||
placeholder.setTextColor(textColor);
|
||||
placeholder.setBackgroundColor(dominantColor);
|
||||
} else {
|
||||
placeholder.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.format.Formatter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Consumer;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.util.StorageUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DataFolderAdapter extends RecyclerView.Adapter<DataFolderAdapter.ViewHolder> {
|
||||
private final Consumer<String> selectionHandler;
|
||||
private final String currentPath;
|
||||
private final List<StoragePath> entries;
|
||||
private final String freeSpaceString;
|
||||
|
||||
public DataFolderAdapter(Context context, @NonNull Consumer<String> selectionHandler) {
|
||||
this.entries = getStorageEntries(context);
|
||||
this.currentPath = getCurrentPath();
|
||||
this.selectionHandler = selectionHandler;
|
||||
this.freeSpaceString = context.getString(R.string.choose_data_directory_available_space);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||
View entryView = inflater.inflate(R.layout.choose_data_folder_dialog_entry, parent, false);
|
||||
return new ViewHolder(entryView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
StoragePath storagePath = entries.get(position);
|
||||
Context context = holder.root.getContext();
|
||||
String freeSpace = Formatter.formatShortFileSize(context, storagePath.getAvailableSpace());
|
||||
String totalSpace = Formatter.formatShortFileSize(context, storagePath.getTotalSpace());
|
||||
|
||||
holder.path.setText(storagePath.getShortPath());
|
||||
holder.size.setText(String.format(freeSpaceString, freeSpace, totalSpace));
|
||||
holder.progressBar.setProgress(storagePath.getUsagePercentage());
|
||||
View.OnClickListener selectListener = v -> selectionHandler.accept(storagePath.getFullPath());
|
||||
holder.root.setOnClickListener(selectListener);
|
||||
holder.radioButton.setOnClickListener(selectListener);
|
||||
|
||||
if (storagePath.getFullPath().equals(currentPath)) {
|
||||
holder.radioButton.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return entries.size();
|
||||
}
|
||||
|
||||
private String getCurrentPath() {
|
||||
File dataFolder = UserPreferences.getDataFolder(null);
|
||||
if (dataFolder != null) {
|
||||
return dataFolder.getAbsolutePath();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<StoragePath> getStorageEntries(Context context) {
|
||||
File[] mediaDirs = context.getExternalFilesDirs(null);
|
||||
final List<StoragePath> entries = new ArrayList<>(mediaDirs.length);
|
||||
for (File dir : mediaDirs) {
|
||||
if (!isWritable(dir)) {
|
||||
continue;
|
||||
}
|
||||
entries.add(new StoragePath(dir.getAbsolutePath()));
|
||||
}
|
||||
if (entries.isEmpty() && isWritable(context.getFilesDir())) {
|
||||
entries.add(new StoragePath(context.getFilesDir().getAbsolutePath()));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
private boolean isWritable(File dir) {
|
||||
return dir != null && dir.exists() && dir.canRead() && dir.canWrite();
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
private final View root;
|
||||
private final TextView path;
|
||||
private final TextView size;
|
||||
private final RadioButton radioButton;
|
||||
private final ProgressBar progressBar;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
root = itemView.findViewById(R.id.root);
|
||||
path = itemView.findViewById(R.id.path);
|
||||
size = itemView.findViewById(R.id.size);
|
||||
radioButton = itemView.findViewById(R.id.radio_button);
|
||||
progressBar = itemView.findViewById(R.id.used_space);
|
||||
}
|
||||
}
|
||||
|
||||
static class StoragePath {
|
||||
private final String path;
|
||||
|
||||
StoragePath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
String getShortPath() {
|
||||
int prefixIndex = path.indexOf("Android");
|
||||
return (prefixIndex > 0) ? path.substring(0, prefixIndex) : path;
|
||||
}
|
||||
|
||||
String getFullPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
long getAvailableSpace() {
|
||||
return StorageUtils.getFreeSpaceAvailable(path);
|
||||
}
|
||||
|
||||
long getTotalSpace() {
|
||||
return StorageUtils.getTotalSpaceAvailable(path);
|
||||
}
|
||||
|
||||
int getUsagePercentage() {
|
||||
return 100 - (int) (100 * getAvailableSpace() / (float) getTotalSpace());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,228 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.text.format.DateUtils;
|
||||
import android.text.format.Formatter;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Toast;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadRequest;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadRequestCreator;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadService;
|
||||
import de.danoeh.antennapod.core.service.download.Downloader;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.util.DownloadErrorLabel;
|
||||
import de.danoeh.antennapod.model.download.DownloadError;
|
||||
import de.danoeh.antennapod.model.download.DownloadStatus;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
import de.danoeh.antennapod.view.viewholder.DownloadLogItemViewHolder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Displays a list of DownloadStatus entries.
|
||||
*/
|
||||
public class DownloadLogAdapter extends BaseAdapter {
|
||||
private static final String TAG = "DownloadLogAdapter";
|
||||
|
||||
private final Activity context;
|
||||
private List<DownloadStatus> downloadLog = new ArrayList<>();
|
||||
private List<Downloader> runningDownloads = new ArrayList<>();
|
||||
|
||||
public DownloadLogAdapter(Activity context) {
|
||||
super();
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void setDownloadLog(List<DownloadStatus> downloadLog) {
|
||||
this.downloadLog = downloadLog;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setRunningDownloads(List<Downloader> runningDownloads) {
|
||||
this.runningDownloads = runningDownloads;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
DownloadLogItemViewHolder holder;
|
||||
if (convertView == null) {
|
||||
holder = new DownloadLogItemViewHolder(context, parent);
|
||||
holder.itemView.setTag(holder);
|
||||
} else {
|
||||
holder = (DownloadLogItemViewHolder) convertView.getTag();
|
||||
}
|
||||
|
||||
Object item = getItem(position);
|
||||
if (item instanceof DownloadStatus) {
|
||||
bind(holder, (DownloadStatus) item, position);
|
||||
} else if (item instanceof Downloader) {
|
||||
bind(holder, (Downloader) item, position);
|
||||
}
|
||||
return holder.itemView;
|
||||
}
|
||||
|
||||
private void bind(DownloadLogItemViewHolder holder, DownloadStatus status, int position) {
|
||||
String statusText = "";
|
||||
if (status.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
|
||||
statusText += context.getString(R.string.download_type_feed);
|
||||
} else if (status.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
|
||||
statusText += context.getString(R.string.download_type_media);
|
||||
}
|
||||
statusText += " · ";
|
||||
statusText += DateUtils.getRelativeTimeSpanString(status.getCompletionDate().getTime(),
|
||||
System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, 0);
|
||||
holder.status.setText(statusText);
|
||||
|
||||
if (status.getTitle() != null) {
|
||||
holder.title.setText(status.getTitle());
|
||||
} else {
|
||||
holder.title.setText(R.string.download_log_title_unknown);
|
||||
}
|
||||
|
||||
if (status.isSuccessful()) {
|
||||
holder.icon.setTextColor(ContextCompat.getColor(context, R.color.download_success_green));
|
||||
holder.icon.setText("{fa-check-circle}");
|
||||
holder.icon.setContentDescription(context.getString(R.string.download_successful));
|
||||
holder.secondaryActionButton.setVisibility(View.INVISIBLE);
|
||||
holder.reason.setVisibility(View.GONE);
|
||||
holder.tapForDetails.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (status.getReason() == DownloadError.ERROR_PARSER_EXCEPTION_DUPLICATE) {
|
||||
holder.icon.setTextColor(ContextCompat.getColor(context, R.color.download_warning_yellow));
|
||||
holder.icon.setText("{fa-exclamation-circle}");
|
||||
} else {
|
||||
holder.icon.setTextColor(ContextCompat.getColor(context, R.color.download_failed_red));
|
||||
holder.icon.setText("{fa-times-circle}");
|
||||
}
|
||||
holder.icon.setContentDescription(context.getString(R.string.error_label));
|
||||
holder.reason.setText(DownloadErrorLabel.from(status.getReason()));
|
||||
holder.reason.setVisibility(View.VISIBLE);
|
||||
holder.tapForDetails.setVisibility(View.VISIBLE);
|
||||
|
||||
if (newerWasSuccessful(position - runningDownloads.size(),
|
||||
status.getFeedfileType(), status.getFeedfileId())) {
|
||||
holder.secondaryActionButton.setVisibility(View.INVISIBLE);
|
||||
holder.secondaryActionButton.setOnClickListener(null);
|
||||
holder.secondaryActionButton.setTag(null);
|
||||
} else {
|
||||
holder.secondaryActionIcon.setImageResource(R.drawable.ic_refresh);
|
||||
holder.secondaryActionButton.setVisibility(View.VISIBLE);
|
||||
|
||||
if (status.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
|
||||
holder.secondaryActionButton.setOnClickListener(v -> {
|
||||
holder.secondaryActionButton.setVisibility(View.INVISIBLE);
|
||||
Feed feed = DBReader.getFeed(status.getFeedfileId());
|
||||
if (feed == null) {
|
||||
Log.e(TAG, "Could not find feed for feed id: " + status.getFeedfileId());
|
||||
return;
|
||||
}
|
||||
DBTasks.forceRefreshFeed(context, feed, true);
|
||||
});
|
||||
} else if (status.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
|
||||
holder.secondaryActionButton.setOnClickListener(v -> {
|
||||
holder.secondaryActionButton.setVisibility(View.INVISIBLE);
|
||||
FeedMedia media = DBReader.getFeedMedia(status.getFeedfileId());
|
||||
if (media == null) {
|
||||
Log.e(TAG, "Could not find feed media for feed id: " + status.getFeedfileId());
|
||||
return;
|
||||
}
|
||||
DownloadService.download(context, true, DownloadRequestCreator.create(media).build());
|
||||
((MainActivity) context).showSnackbarAbovePlayer(
|
||||
R.string.status_downloading_label, Toast.LENGTH_SHORT);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void bind(DownloadLogItemViewHolder holder, Downloader downloader, int position) {
|
||||
DownloadRequest request = downloader.getDownloadRequest();
|
||||
holder.title.setText(request.getTitle());
|
||||
holder.secondaryActionIcon.setImageResource(R.drawable.ic_cancel);
|
||||
holder.secondaryActionButton.setContentDescription(context.getString(R.string.cancel_download_label));
|
||||
holder.secondaryActionButton.setVisibility(View.VISIBLE);
|
||||
holder.secondaryActionButton.setTag(downloader);
|
||||
holder.secondaryActionButton.setOnClickListener(v -> {
|
||||
DownloadService.cancel(context, request.getSource());
|
||||
if (request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
|
||||
FeedMedia media = DBReader.getFeedMedia(request.getFeedfileId());
|
||||
FeedItem feedItem = media.getItem();
|
||||
feedItem.disableAutoDownload();
|
||||
DBWriter.setFeedItem(feedItem);
|
||||
}
|
||||
});
|
||||
holder.reason.setVisibility(View.GONE);
|
||||
holder.tapForDetails.setVisibility(View.GONE);
|
||||
holder.icon.setTextColor(ThemeUtils.getColorFromAttr(context, R.attr.colorPrimary));
|
||||
holder.icon.setText("{fa-arrow-circle-down}");
|
||||
holder.icon.setContentDescription(context.getString(R.string.status_downloading_label));
|
||||
|
||||
boolean percentageWasSet = false;
|
||||
String status = "";
|
||||
if (request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
|
||||
status += context.getString(R.string.download_type_feed);
|
||||
} else if (request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
|
||||
status += context.getString(R.string.download_type_media);
|
||||
}
|
||||
status += " · ";
|
||||
if (request.getSoFar() <= 0) {
|
||||
status += context.getString(R.string.download_pending);
|
||||
} else {
|
||||
status += Formatter.formatShortFileSize(context, request.getSoFar());
|
||||
if (request.getSize() != DownloadStatus.SIZE_UNKNOWN) {
|
||||
status += " / " + Formatter.formatShortFileSize(context, request.getSize());
|
||||
holder.secondaryActionProgress.setPercentage(
|
||||
0.01f * Math.max(1, request.getProgressPercent()), request);
|
||||
percentageWasSet = true;
|
||||
}
|
||||
}
|
||||
if (!percentageWasSet) {
|
||||
holder.secondaryActionProgress.setPercentage(0, request);
|
||||
}
|
||||
holder.status.setText(status);
|
||||
}
|
||||
|
||||
private boolean newerWasSuccessful(int downloadStatusIndex, int feedTypeId, long id) {
|
||||
for (int i = 0; i < downloadStatusIndex; i++) {
|
||||
DownloadStatus status = downloadLog.get(i);
|
||||
if (status.getFeedfileType() == feedTypeId && status.getFeedfileId() == id && status.isSuccessful()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return downloadLog.size() + runningDownloads.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
if (position < runningDownloads.size()) {
|
||||
return runningDownloads.get(position);
|
||||
} else if (position - runningDownloads.size() < downloadLog.size()) {
|
||||
return downloadLog.get(position - runningDownloads.size());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.InputDevice;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.util.FeedItemUtil;
|
||||
import de.danoeh.antennapod.fragment.ItemPagerFragment;
|
||||
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
|
||||
import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder;
|
||||
|
||||
/**
|
||||
* List adapter for the list of new episodes.
|
||||
*/
|
||||
public class EpisodeItemListAdapter extends SelectableAdapter<EpisodeItemViewHolder>
|
||||
implements View.OnCreateContextMenuListener {
|
||||
|
||||
private final WeakReference<MainActivity> mainActivityRef;
|
||||
private List<FeedItem> episodes = new ArrayList<>();
|
||||
private FeedItem longPressedItem;
|
||||
int longPressedPosition = 0; // used to init actionMode
|
||||
|
||||
public EpisodeItemListAdapter(MainActivity mainActivity) {
|
||||
super(mainActivity);
|
||||
this.mainActivityRef = new WeakReference<>(mainActivity);
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
public void updateItems(List<FeedItem> items) {
|
||||
episodes = items;
|
||||
notifyDataSetChanged();
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getItemViewType(int position) {
|
||||
return R.id.view_type_episode_item;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public final EpisodeItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new EpisodeItemViewHolder(mainActivityRef.get(), parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onBindViewHolder(EpisodeItemViewHolder holder, int pos) {
|
||||
// Reset state of recycled views
|
||||
holder.coverHolder.setVisibility(View.VISIBLE);
|
||||
holder.dragHandle.setVisibility(View.GONE);
|
||||
|
||||
beforeBindViewHolder(holder, pos);
|
||||
|
||||
FeedItem item = episodes.get(pos);
|
||||
holder.bind(item);
|
||||
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
MainActivity activity = mainActivityRef.get();
|
||||
if (activity != null && !inActionMode()) {
|
||||
long[] ids = FeedItemUtil.getIds(episodes);
|
||||
int position = ArrayUtils.indexOf(ids, item.getId());
|
||||
activity.loadChildFragment(ItemPagerFragment.newInstance(ids, position));
|
||||
} else {
|
||||
toggleSelection(holder.getBindingAdapterPosition());
|
||||
}
|
||||
});
|
||||
holder.itemView.setOnCreateContextMenuListener(this);
|
||||
holder.itemView.setOnLongClickListener(v -> {
|
||||
longPressedItem = item;
|
||||
longPressedPosition = holder.getBindingAdapterPosition();
|
||||
return false;
|
||||
});
|
||||
holder.itemView.setOnTouchListener((v, e) -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (e.isFromSource(InputDevice.SOURCE_MOUSE)
|
||||
&& e.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
|
||||
longPressedItem = item;
|
||||
longPressedPosition = holder.getBindingAdapterPosition();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (inActionMode()) {
|
||||
holder.secondaryActionButton.setVisibility(View.GONE);
|
||||
holder.selectCheckBox.setOnClickListener(v -> toggleSelection(holder.getBindingAdapterPosition()));
|
||||
holder.selectCheckBox.setChecked(isSelected(pos));
|
||||
holder.selectCheckBox.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.selectCheckBox.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
afterBindViewHolder(holder, pos);
|
||||
holder.hideSeparatorIfNecessary();
|
||||
}
|
||||
|
||||
protected void beforeBindViewHolder(EpisodeItemViewHolder holder, int pos) {
|
||||
}
|
||||
|
||||
protected void afterBindViewHolder(EpisodeItemViewHolder holder, int pos) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewRecycled(@NonNull EpisodeItemViewHolder holder) {
|
||||
super.onViewRecycled(holder);
|
||||
// Set all listeners to null. This is required to prevent leaking fragments that have set a listener.
|
||||
// Activity -> recycledViewPool -> EpisodeItemViewHolder -> Listener -> Fragment (can not be garbage collected)
|
||||
holder.itemView.setOnClickListener(null);
|
||||
holder.itemView.setOnCreateContextMenuListener(null);
|
||||
holder.itemView.setOnLongClickListener(null);
|
||||
holder.itemView.setOnTouchListener(null);
|
||||
holder.secondaryActionButton.setOnClickListener(null);
|
||||
holder.dragHandle.setOnTouchListener(null);
|
||||
holder.coverHolder.setOnTouchListener(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link #notifyItemChanged(int)} is final, so we can not override.
|
||||
* Calling {@link #notifyItemChanged(int)} may bind the item to a new ViewHolder and execute a transition.
|
||||
* This causes flickering and breaks the download animation that stores the old progress in the View.
|
||||
* Instead, we tell the adapter to use partial binding by calling {@link #notifyItemChanged(int, Object)}.
|
||||
* We actually ignore the payload and always do a full bind but calling the partial bind method ensures
|
||||
* that ViewHolders are always re-used.
|
||||
*
|
||||
* @param position Position of the item that has changed
|
||||
*/
|
||||
public void notifyItemChangedCompat(int position) {
|
||||
notifyItemChanged(position, "foo");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FeedItem getLongPressedItem() {
|
||||
return longPressedItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
FeedItem item = episodes.get(position);
|
||||
return item != null ? item.getId() : RecyclerView.NO_POSITION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return episodes.size();
|
||||
}
|
||||
|
||||
protected FeedItem getItem(int index) {
|
||||
return episodes.get(index);
|
||||
}
|
||||
|
||||
protected Activity getActivity() {
|
||||
return mainActivityRef.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
||||
MenuInflater inflater = mainActivityRef.get().getMenuInflater();
|
||||
if (inActionMode()) {
|
||||
inflater.inflate(R.menu.multi_select_context_popup, menu);
|
||||
} else {
|
||||
if (longPressedItem == null) {
|
||||
return;
|
||||
}
|
||||
inflater.inflate(R.menu.feeditemlist_context, menu);
|
||||
menu.setHeaderTitle(longPressedItem.getTitle());
|
||||
FeedItemMenuHandler.onPrepareMenu(menu, longPressedItem, R.id.skip_episode_item);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.multi_select) {
|
||||
startSelectMode(longPressedPosition);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.select_all_above) {
|
||||
setSelected(0, longPressedPosition, true);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.select_all_below) {
|
||||
shouldSelectLazyLoadedItems = true;
|
||||
setSelected(longPressedPosition + 1, getItemCount(), true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<FeedItem> getSelectedItems() {
|
||||
List<FeedItem> items = new ArrayList<>();
|
||||
for (int i = 0; i < getItemCount(); i++) {
|
||||
if (isSelected(i)) {
|
||||
items.add(getItem(i));
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ImageView;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.net.discovery.PodcastSearchResult;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class FeedDiscoverAdapter extends BaseAdapter {
|
||||
|
||||
private final WeakReference<MainActivity> mainActivityRef;
|
||||
private final List<PodcastSearchResult> data = new ArrayList<>();
|
||||
|
||||
public FeedDiscoverAdapter(MainActivity mainActivity) {
|
||||
this.mainActivityRef = new WeakReference<>(mainActivity);
|
||||
}
|
||||
|
||||
public void updateData(List<PodcastSearchResult> newData) {
|
||||
data.clear();
|
||||
data.addAll(newData);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PodcastSearchResult getItem(int position) {
|
||||
return data.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
Holder holder;
|
||||
|
||||
if (convertView == null) {
|
||||
convertView = View.inflate(mainActivityRef.get(), R.layout.quick_feed_discovery_item, null);
|
||||
holder = new Holder();
|
||||
holder.imageView = convertView.findViewById(R.id.discovery_cover);
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (Holder) convertView.getTag();
|
||||
}
|
||||
|
||||
|
||||
final PodcastSearchResult podcast = getItem(position);
|
||||
holder.imageView.setContentDescription(podcast.title);
|
||||
|
||||
Glide.with(mainActivityRef.get())
|
||||
.load(podcast.imageUrl)
|
||||
.apply(new RequestOptions()
|
||||
.placeholder(R.color.light_gray)
|
||||
.fitCenter()
|
||||
.dontAnimate())
|
||||
.into(holder.imageView);
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
static class Holder {
|
||||
ImageView imageView;
|
||||
}
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.model.playback.MediaType;
|
||||
import de.danoeh.antennapod.core.util.NetworkUtils;
|
||||
import de.danoeh.antennapod.model.playback.RemoteMedia;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.util.DateFormatter;
|
||||
import de.danoeh.antennapod.model.playback.Playable;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
|
||||
import de.danoeh.antennapod.core.util.syndication.HtmlToPlainText;
|
||||
import de.danoeh.antennapod.dialog.StreamingConfirmationDialog;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* List adapter for showing a list of FeedItems with their title and description.
|
||||
*/
|
||||
public class FeedItemlistDescriptionAdapter extends ArrayAdapter<FeedItem> {
|
||||
private static final int MAX_LINES_COLLAPSED = 3;
|
||||
|
||||
public FeedItemlistDescriptionAdapter(Context context, int resource, List<FeedItem> objects) {
|
||||
super(context, resource, objects);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
Holder holder;
|
||||
|
||||
FeedItem item = getItem(position);
|
||||
|
||||
// Inflate layout
|
||||
if (convertView == null) {
|
||||
holder = new Holder();
|
||||
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
convertView = inflater.inflate(R.layout.itemdescription_listitem, parent, false);
|
||||
holder.title = convertView.findViewById(R.id.txtvTitle);
|
||||
holder.pubDate = convertView.findViewById(R.id.txtvPubDate);
|
||||
holder.description = convertView.findViewById(R.id.txtvDescription);
|
||||
holder.preview = convertView.findViewById(R.id.butPreview);
|
||||
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (Holder) convertView.getTag();
|
||||
}
|
||||
|
||||
holder.title.setText(item.getTitle());
|
||||
holder.pubDate.setText(DateFormatter.formatAbbrev(getContext(), item.getPubDate()));
|
||||
if (item.getDescription() != null) {
|
||||
String description = HtmlToPlainText.getPlainText(item.getDescription())
|
||||
.replaceAll("\n", " ")
|
||||
.replaceAll("\\s+", " ")
|
||||
.trim();
|
||||
holder.description.setText(description);
|
||||
holder.description.setMaxLines(MAX_LINES_COLLAPSED);
|
||||
}
|
||||
holder.description.setTag(Boolean.FALSE); // not expanded
|
||||
holder.preview.setVisibility(View.GONE);
|
||||
holder.preview.setOnClickListener(v -> {
|
||||
if (item.getMedia() == null) {
|
||||
return;
|
||||
}
|
||||
Playable playable = new RemoteMedia(item);
|
||||
if (!NetworkUtils.isStreamingAllowed()) {
|
||||
new StreamingConfirmationDialog(getContext(), playable).show();
|
||||
return;
|
||||
}
|
||||
|
||||
new PlaybackServiceStarter(getContext(), playable)
|
||||
.callEvenIfRunning(true)
|
||||
.start();
|
||||
|
||||
if (playable.getMediaType() == MediaType.VIDEO) {
|
||||
getContext().startActivity(PlaybackService.getPlayerActivityIntent(getContext(), playable));
|
||||
}
|
||||
});
|
||||
convertView.setOnClickListener(v -> {
|
||||
if (holder.description.getTag() == Boolean.TRUE) {
|
||||
holder.description.setMaxLines(MAX_LINES_COLLAPSED);
|
||||
holder.preview.setVisibility(View.GONE);
|
||||
holder.description.setTag(Boolean.FALSE);
|
||||
} else {
|
||||
holder.description.setMaxLines(30);
|
||||
holder.description.setTag(Boolean.TRUE);
|
||||
|
||||
holder.preview.setVisibility(item.getMedia() != null ? View.VISIBLE : View.GONE);
|
||||
holder.preview.setText(R.string.preview_episode);
|
||||
}
|
||||
});
|
||||
return convertView;
|
||||
}
|
||||
|
||||
static class Holder {
|
||||
TextView title;
|
||||
TextView pubDate;
|
||||
TextView description;
|
||||
Button preview;
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.fragment.FeedItemlistFragment;
|
||||
import de.danoeh.antennapod.ui.common.SquareImageView;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class FeedSearchResultAdapter extends RecyclerView.Adapter<FeedSearchResultAdapter.Holder> {
|
||||
|
||||
private final WeakReference<MainActivity> mainActivityRef;
|
||||
private final List<Feed> data = new ArrayList<>();
|
||||
|
||||
public FeedSearchResultAdapter(MainActivity mainActivity) {
|
||||
this.mainActivityRef = new WeakReference<>(mainActivity);
|
||||
}
|
||||
|
||||
public void updateData(List<Feed> newData) {
|
||||
data.clear();
|
||||
data.addAll(newData);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View convertView = View.inflate(mainActivityRef.get(), R.layout.searchlist_item_feed, null);
|
||||
return new Holder(convertView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull Holder holder, int position) {
|
||||
final Feed podcast = data.get(position);
|
||||
holder.imageView.setContentDescription(podcast.getTitle());
|
||||
holder.imageView.setOnClickListener(v ->
|
||||
mainActivityRef.get().loadChildFragment(FeedItemlistFragment.newInstance(podcast.getId())));
|
||||
|
||||
Glide.with(mainActivityRef.get())
|
||||
.load(podcast.getImageUrl())
|
||||
.apply(new RequestOptions()
|
||||
.placeholder(R.color.light_gray)
|
||||
.fitCenter()
|
||||
.dontAnimate())
|
||||
.into(holder.imageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return data.get(position).getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
static class Holder extends RecyclerView.ViewHolder {
|
||||
SquareImageView imageView;
|
||||
|
||||
public Holder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
imageView = itemView.findViewById(R.id.discovery_cover);
|
||||
imageView.setDirection(SquareImageView.DIRECTION_HEIGHT);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,427 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.InputDevice;
|
||||
import android.view.LayoutInflater;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.util.TypedValue;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.joanzapata.iconify.Iconify;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.PreferenceActivity;
|
||||
import de.danoeh.antennapod.fragment.AllEpisodesFragment;
|
||||
import de.danoeh.antennapod.fragment.CompletedDownloadsFragment;
|
||||
import de.danoeh.antennapod.fragment.InboxFragment;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.storage.NavDrawerData;
|
||||
import de.danoeh.antennapod.fragment.AddFeedFragment;
|
||||
import de.danoeh.antennapod.fragment.NavDrawerFragment;
|
||||
import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
|
||||
import de.danoeh.antennapod.fragment.QueueFragment;
|
||||
import de.danoeh.antennapod.fragment.SubscriptionFragment;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* BaseAdapter for the navigation drawer
|
||||
*/
|
||||
public class NavListAdapter extends RecyclerView.Adapter<NavListAdapter.Holder>
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
public static final int VIEW_TYPE_NAV = 0;
|
||||
public static final int VIEW_TYPE_SECTION_DIVIDER = 1;
|
||||
private static final int VIEW_TYPE_SUBSCRIPTION = 2;
|
||||
|
||||
/**
|
||||
* a tag used as a placeholder to indicate if the subscription list should be displayed or not
|
||||
* This tag doesn't correspond to any specific activity.
|
||||
*/
|
||||
public static final String SUBSCRIPTION_LIST_TAG = "SubscriptionList";
|
||||
|
||||
private final List<String> fragmentTags = new ArrayList<>();
|
||||
private final String[] titles;
|
||||
private final ItemAccess itemAccess;
|
||||
private final WeakReference<Activity> activity;
|
||||
public boolean showSubscriptionList = true;
|
||||
|
||||
public NavListAdapter(ItemAccess itemAccess, Activity context) {
|
||||
this.itemAccess = itemAccess;
|
||||
this.activity = new WeakReference<>(context);
|
||||
|
||||
titles = context.getResources().getStringArray(R.array.nav_drawer_titles);
|
||||
loadItems();
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (UserPreferences.PREF_HIDDEN_DRAWER_ITEMS.equals(key)) {
|
||||
loadItems();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadItems() {
|
||||
List<String> newTags = new ArrayList<>(Arrays.asList(NavDrawerFragment.NAV_DRAWER_TAGS));
|
||||
List<String> hiddenFragments = UserPreferences.getHiddenDrawerItems();
|
||||
newTags.removeAll(hiddenFragments);
|
||||
|
||||
if (newTags.contains(SUBSCRIPTION_LIST_TAG)) {
|
||||
// we never want SUBSCRIPTION_LIST_TAG to be in 'tags'
|
||||
// since it doesn't actually correspond to a position in the list, but is
|
||||
// a placeholder that indicates if we should show the subscription list in the
|
||||
// nav drawer at all.
|
||||
showSubscriptionList = true;
|
||||
newTags.remove(SUBSCRIPTION_LIST_TAG);
|
||||
} else {
|
||||
showSubscriptionList = false;
|
||||
}
|
||||
|
||||
fragmentTags.clear();
|
||||
fragmentTags.addAll(newTags);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public String getLabel(String tag) {
|
||||
int index = ArrayUtils.indexOf(NavDrawerFragment.NAV_DRAWER_TAGS, tag);
|
||||
return titles[index];
|
||||
}
|
||||
|
||||
private @DrawableRes int getDrawable(String tag) {
|
||||
switch (tag) {
|
||||
case QueueFragment.TAG:
|
||||
return R.drawable.ic_playlist_play;
|
||||
case InboxFragment.TAG:
|
||||
return R.drawable.ic_inbox;
|
||||
case AllEpisodesFragment.TAG:
|
||||
return R.drawable.ic_feed;
|
||||
case CompletedDownloadsFragment.TAG:
|
||||
return R.drawable.ic_download;
|
||||
case PlaybackHistoryFragment.TAG:
|
||||
return R.drawable.ic_history;
|
||||
case SubscriptionFragment.TAG:
|
||||
return R.drawable.ic_folder;
|
||||
case AddFeedFragment.TAG:
|
||||
return R.drawable.ic_add;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getFragmentTags() {
|
||||
return Collections.unmodifiableList(fragmentTags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
int baseCount = getSubscriptionOffset();
|
||||
if (showSubscriptionList) {
|
||||
baseCount += itemAccess.getCount();
|
||||
}
|
||||
return baseCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
int viewType = getItemViewType(position);
|
||||
if (viewType == VIEW_TYPE_SUBSCRIPTION) {
|
||||
return itemAccess.getItem(position - getSubscriptionOffset()).id;
|
||||
} else if (viewType == VIEW_TYPE_NAV) {
|
||||
return -Math.abs((long) fragmentTags.get(position).hashCode()) - 1; // Folder IDs are >0
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (0 <= position && position < fragmentTags.size()) {
|
||||
return VIEW_TYPE_NAV;
|
||||
} else if (position < getSubscriptionOffset()) {
|
||||
return VIEW_TYPE_SECTION_DIVIDER;
|
||||
} else {
|
||||
return VIEW_TYPE_SUBSCRIPTION;
|
||||
}
|
||||
}
|
||||
|
||||
public int getSubscriptionOffset() {
|
||||
return fragmentTags.size() > 0 ? fragmentTags.size() + 1 : 0;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
LayoutInflater inflater = LayoutInflater.from(activity.get());
|
||||
if (viewType == VIEW_TYPE_NAV) {
|
||||
return new NavHolder(inflater.inflate(R.layout.nav_listitem, parent, false));
|
||||
} else if (viewType == VIEW_TYPE_SECTION_DIVIDER) {
|
||||
return new DividerHolder(inflater.inflate(R.layout.nav_section_item, parent, false));
|
||||
} else {
|
||||
return new FeedHolder(inflater.inflate(R.layout.nav_listitem, parent, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull Holder holder, int position) {
|
||||
int viewType = getItemViewType(position);
|
||||
|
||||
holder.itemView.setOnCreateContextMenuListener(null);
|
||||
if (viewType == VIEW_TYPE_NAV) {
|
||||
bindNavView(getLabel(fragmentTags.get(position)), position, (NavHolder) holder);
|
||||
} else if (viewType == VIEW_TYPE_SECTION_DIVIDER) {
|
||||
bindSectionDivider((DividerHolder) holder);
|
||||
} else {
|
||||
int itemPos = position - getSubscriptionOffset();
|
||||
NavDrawerData.DrawerItem item = itemAccess.getItem(itemPos);
|
||||
bindListItem(item, (FeedHolder) holder);
|
||||
if (item.type == NavDrawerData.DrawerItem.Type.FEED) {
|
||||
bindFeedView((NavDrawerData.FeedDrawerItem) item, (FeedHolder) holder);
|
||||
} else {
|
||||
bindTagView((NavDrawerData.TagDrawerItem) item, (FeedHolder) holder);
|
||||
}
|
||||
holder.itemView.setOnCreateContextMenuListener(itemAccess);
|
||||
}
|
||||
if (viewType != VIEW_TYPE_SECTION_DIVIDER) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
|
||||
activity.get().getTheme().resolveAttribute(itemAccess.isSelected(position)
|
||||
? R.attr.drawer_activated_color : android.R.attr.windowBackground, typedValue, true);
|
||||
holder.itemView.setBackgroundResource(typedValue.resourceId);
|
||||
|
||||
holder.itemView.setOnClickListener(v -> itemAccess.onItemClick(position));
|
||||
holder.itemView.setOnLongClickListener(v -> itemAccess.onItemLongClick(position));
|
||||
holder.itemView.setOnTouchListener((v, e) -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (e.isFromSource(InputDevice.SOURCE_MOUSE)
|
||||
&& e.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
|
||||
itemAccess.onItemLongClick(position);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void bindNavView(String title, int position, NavHolder holder) {
|
||||
Activity context = activity.get();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
holder.title.setText(title);
|
||||
|
||||
// reset for re-use
|
||||
holder.count.setVisibility(View.GONE);
|
||||
holder.count.setOnClickListener(null);
|
||||
holder.count.setClickable(false);
|
||||
|
||||
String tag = fragmentTags.get(position);
|
||||
if (tag.equals(QueueFragment.TAG)) {
|
||||
int queueSize = itemAccess.getQueueSize();
|
||||
if (queueSize > 0) {
|
||||
holder.count.setText(NumberFormat.getInstance().format(queueSize));
|
||||
holder.count.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else if (tag.equals(InboxFragment.TAG)) {
|
||||
int unreadItems = itemAccess.getNumberOfNewItems();
|
||||
if (unreadItems > 0) {
|
||||
holder.count.setText(NumberFormat.getInstance().format(unreadItems));
|
||||
holder.count.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else if (tag.equals(SubscriptionFragment.TAG)) {
|
||||
int sum = itemAccess.getFeedCounterSum();
|
||||
if (sum > 0) {
|
||||
holder.count.setText(NumberFormat.getInstance().format(sum));
|
||||
holder.count.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else if (tag.equals(CompletedDownloadsFragment.TAG) && UserPreferences.isEnableAutodownload()) {
|
||||
int epCacheSize = UserPreferences.getEpisodeCacheSize();
|
||||
// don't count episodes that can be reclaimed
|
||||
int spaceUsed = itemAccess.getNumberOfDownloadedItems()
|
||||
- itemAccess.getReclaimableItems();
|
||||
|
||||
if (epCacheSize > 0 && spaceUsed >= epCacheSize) {
|
||||
holder.count.setText("{md-disc-full 150%}");
|
||||
Iconify.addIcons(holder.count);
|
||||
holder.count.setVisibility(View.VISIBLE);
|
||||
holder.count.setOnClickListener(v ->
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.episode_cache_full_title)
|
||||
.setMessage(R.string.episode_cache_full_message)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.setNeutralButton(R.string.open_autodownload_settings, (dialog, which) -> {
|
||||
Intent intent = new Intent(context, PreferenceActivity.class);
|
||||
intent.putExtra(PreferenceActivity.OPEN_AUTO_DOWNLOAD_SETTINGS, true);
|
||||
context.startActivity(intent);
|
||||
})
|
||||
.show()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
holder.image.setImageResource(getDrawable(fragmentTags.get(position)));
|
||||
}
|
||||
|
||||
private void bindSectionDivider(DividerHolder holder) {
|
||||
Activity context = activity.get();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (UserPreferences.getSubscriptionsFilter().isEnabled() && showSubscriptionList) {
|
||||
holder.itemView.setEnabled(true);
|
||||
holder.feedsFilteredMsg.setText("{md-info-outline} "
|
||||
+ context.getString(R.string.subscriptions_are_filtered));
|
||||
Iconify.addIcons(holder.feedsFilteredMsg);
|
||||
holder.feedsFilteredMsg.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.itemView.setEnabled(false);
|
||||
holder.feedsFilteredMsg.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void bindListItem(NavDrawerData.DrawerItem item, FeedHolder holder) {
|
||||
if (item.getCounter() > 0) {
|
||||
holder.count.setVisibility(View.VISIBLE);
|
||||
holder.count.setText(NumberFormat.getInstance().format(item.getCounter()));
|
||||
} else {
|
||||
holder.count.setVisibility(View.GONE);
|
||||
}
|
||||
holder.title.setText(item.getTitle());
|
||||
int padding = (int) (activity.get().getResources().getDimension(R.dimen.thumbnail_length_navlist) / 2);
|
||||
holder.itemView.setPadding(item.getLayer() * padding, 0, 0, 0);
|
||||
}
|
||||
|
||||
private void bindFeedView(NavDrawerData.FeedDrawerItem drawerItem, FeedHolder holder) {
|
||||
Feed feed = drawerItem.feed;
|
||||
Activity context = activity.get();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Glide.with(context)
|
||||
.load(feed.getImageUrl())
|
||||
.apply(new RequestOptions()
|
||||
.placeholder(R.color.light_gray)
|
||||
.error(R.color.light_gray)
|
||||
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
|
||||
.fitCenter()
|
||||
.dontAnimate())
|
||||
.into(holder.image);
|
||||
|
||||
if (feed.hasLastUpdateFailed()) {
|
||||
RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) holder.title.getLayoutParams();
|
||||
p.addRule(RelativeLayout.LEFT_OF, R.id.itxtvFailure);
|
||||
holder.failure.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) holder.title.getLayoutParams();
|
||||
p.addRule(RelativeLayout.LEFT_OF, R.id.txtvCount);
|
||||
holder.failure.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void bindTagView(NavDrawerData.TagDrawerItem tag, FeedHolder holder) {
|
||||
Activity context = activity.get();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
if (tag.isOpen) {
|
||||
holder.count.setVisibility(View.GONE);
|
||||
}
|
||||
Glide.with(context).clear(holder.image);
|
||||
holder.image.setImageResource(R.drawable.ic_tag);
|
||||
holder.failure.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
static class Holder extends RecyclerView.ViewHolder {
|
||||
public Holder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
}
|
||||
|
||||
static class DividerHolder extends Holder {
|
||||
final TextView feedsFilteredMsg;
|
||||
|
||||
public DividerHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
feedsFilteredMsg = itemView.findViewById(R.id.nav_feeds_filtered_message);
|
||||
}
|
||||
}
|
||||
|
||||
static class NavHolder extends Holder {
|
||||
final ImageView image;
|
||||
final TextView title;
|
||||
final TextView count;
|
||||
|
||||
public NavHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
image = itemView.findViewById(R.id.imgvCover);
|
||||
title = itemView.findViewById(R.id.txtvTitle);
|
||||
count = itemView.findViewById(R.id.txtvCount);
|
||||
}
|
||||
}
|
||||
|
||||
static class FeedHolder extends Holder {
|
||||
final ImageView image;
|
||||
final TextView title;
|
||||
final ImageView failure;
|
||||
final TextView count;
|
||||
|
||||
public FeedHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
image = itemView.findViewById(R.id.imgvCover);
|
||||
title = itemView.findViewById(R.id.txtvTitle);
|
||||
failure = itemView.findViewById(R.id.itxtvFailure);
|
||||
count = itemView.findViewById(R.id.txtvCount);
|
||||
}
|
||||
}
|
||||
|
||||
public interface ItemAccess extends View.OnCreateContextMenuListener {
|
||||
int getCount();
|
||||
|
||||
NavDrawerData.DrawerItem getItem(int position);
|
||||
|
||||
boolean isSelected(int position);
|
||||
|
||||
int getQueueSize();
|
||||
|
||||
int getNumberOfNewItems();
|
||||
|
||||
int getNumberOfDownloadedItems();
|
||||
|
||||
int getReclaimableItems();
|
||||
|
||||
int getFeedCounterSum();
|
||||
|
||||
void onItemClick(int position);
|
||||
|
||||
boolean onItemLongClick(int position);
|
||||
|
||||
@Override
|
||||
void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.fragment.swipeactions.SwipeActions;
|
||||
import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder;
|
||||
|
||||
/**
|
||||
* List adapter for the queue.
|
||||
*/
|
||||
public class QueueRecyclerAdapter extends EpisodeItemListAdapter {
|
||||
private static final String TAG = "QueueRecyclerAdapter";
|
||||
|
||||
private final SwipeActions swipeActions;
|
||||
private boolean dragDropEnabled;
|
||||
|
||||
|
||||
public QueueRecyclerAdapter(MainActivity mainActivity, SwipeActions swipeActions) {
|
||||
super(mainActivity);
|
||||
this.swipeActions = swipeActions;
|
||||
dragDropEnabled = ! (UserPreferences.isQueueKeepSorted() || UserPreferences.isQueueLocked());
|
||||
}
|
||||
|
||||
public void updateDragDropEnabled() {
|
||||
dragDropEnabled = ! (UserPreferences.isQueueKeepSorted() || UserPreferences.isQueueLocked());
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
protected void afterBindViewHolder(EpisodeItemViewHolder holder, int pos) {
|
||||
if (!dragDropEnabled || inActionMode()) {
|
||||
holder.dragHandle.setVisibility(View.GONE);
|
||||
holder.dragHandle.setOnTouchListener(null);
|
||||
holder.coverHolder.setOnTouchListener(null);
|
||||
} else {
|
||||
holder.dragHandle.setVisibility(View.VISIBLE);
|
||||
holder.dragHandle.setOnTouchListener((v1, event) -> {
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
Log.d(TAG, "startDrag()");
|
||||
swipeActions.startDrag(holder);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
holder.coverHolder.setOnTouchListener((v1, event) -> {
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
boolean isLtr = holder.itemView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
|
||||
float factor = isLtr ? 1 : -1;
|
||||
if (factor * event.getX() < factor * 0.5 * v1.getWidth()) {
|
||||
Log.d(TAG, "startDrag()");
|
||||
swipeActions.startDrag(holder);
|
||||
} else {
|
||||
Log.d(TAG, "Ignoring drag in right half of the image");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
holder.isInQueue.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(final ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
||||
MenuInflater inflater = getActivity().getMenuInflater();
|
||||
inflater.inflate(R.menu.queue_context, menu);
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
|
||||
if (!inActionMode()) {
|
||||
menu.findItem(R.id.multi_select).setVisible(true);
|
||||
final boolean keepSorted = UserPreferences.isQueueKeepSorted();
|
||||
if (getItem(0).getId() == getLongPressedItem().getId() || keepSorted) {
|
||||
menu.findItem(R.id.move_to_top_item).setVisible(false);
|
||||
}
|
||||
if (getItem(getItemCount() - 1).getId() == getLongPressedItem().getId() || keepSorted) {
|
||||
menu.findItem(R.id.move_to_bottom_item).setVisible(false);
|
||||
}
|
||||
} else {
|
||||
menu.findItem(R.id.move_to_top_item).setVisible(false);
|
||||
menu.findItem(R.id.move_to_bottom_item).setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,200 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.ActionMode;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Used by Recyclerviews that need to provide ability to select items.
|
||||
*/
|
||||
public abstract class SelectableAdapter<T extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<T> {
|
||||
public static final int COUNT_AUTOMATICALLY = -1;
|
||||
private ActionMode actionMode;
|
||||
private final HashSet<Long> selectedIds = new HashSet<>();
|
||||
private final Activity activity;
|
||||
private OnSelectModeListener onSelectModeListener;
|
||||
boolean shouldSelectLazyLoadedItems = false;
|
||||
private int totalNumberOfItems = COUNT_AUTOMATICALLY;
|
||||
|
||||
public SelectableAdapter(Activity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
public void startSelectMode(int pos) {
|
||||
if (inActionMode()) {
|
||||
endSelectMode();
|
||||
}
|
||||
|
||||
if (onSelectModeListener != null) {
|
||||
onSelectModeListener.onStartSelectMode();
|
||||
}
|
||||
|
||||
shouldSelectLazyLoadedItems = false;
|
||||
selectedIds.clear();
|
||||
selectedIds.add(getItemId(pos));
|
||||
notifyDataSetChanged();
|
||||
|
||||
actionMode = activity.startActionMode(new ActionMode.Callback() {
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
MenuInflater inflater = mode.getMenuInflater();
|
||||
inflater.inflate(R.menu.multi_select_options, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||
updateTitle();
|
||||
toggleSelectAllIcon(menu.findItem(R.id.select_toggle), false);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||
if (item.getItemId() == R.id.select_toggle) {
|
||||
boolean selectAll = selectedIds.size() != getItemCount();
|
||||
shouldSelectLazyLoadedItems = selectAll;
|
||||
setSelected(0, getItemCount(), selectAll);
|
||||
toggleSelectAllIcon(item, selectAll);
|
||||
updateTitle();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
callOnEndSelectMode();
|
||||
actionMode = null;
|
||||
shouldSelectLazyLoadedItems = false;
|
||||
selectedIds.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
* End action mode if currently in select mode, otherwise do nothing
|
||||
*/
|
||||
public void endSelectMode() {
|
||||
if (inActionMode()) {
|
||||
callOnEndSelectMode();
|
||||
actionMode.finish();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSelected(int pos) {
|
||||
return selectedIds.contains(getItemId(pos));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the selected state of item at given position
|
||||
*
|
||||
* @param pos the position to select
|
||||
* @param selected true for selected state and false for unselected
|
||||
*/
|
||||
public void setSelected(int pos, boolean selected) {
|
||||
if (selected) {
|
||||
selectedIds.add(getItemId(pos));
|
||||
} else {
|
||||
selectedIds.remove(getItemId(pos));
|
||||
}
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the selected state of item for a given range
|
||||
*
|
||||
* @param startPos start position of range, inclusive
|
||||
* @param endPos end position of range, inclusive
|
||||
* @param selected indicates the selection state
|
||||
* @throws IllegalArgumentException if start and end positions are not valid
|
||||
*/
|
||||
public void setSelected(int startPos, int endPos, boolean selected) throws IllegalArgumentException {
|
||||
for (int i = startPos; i < endPos && i < getItemCount(); i++) {
|
||||
setSelected(i, selected);
|
||||
}
|
||||
notifyItemRangeChanged(startPos, (endPos - startPos));
|
||||
}
|
||||
|
||||
protected void toggleSelection(int pos) {
|
||||
setSelected(pos, !isSelected(pos));
|
||||
notifyItemChanged(pos);
|
||||
|
||||
if (selectedIds.size() == 0) {
|
||||
endSelectMode();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean inActionMode() {
|
||||
return actionMode != null;
|
||||
}
|
||||
|
||||
public int getSelectedCount() {
|
||||
return selectedIds.size();
|
||||
}
|
||||
|
||||
private void toggleSelectAllIcon(MenuItem selectAllItem, boolean allSelected) {
|
||||
if (allSelected) {
|
||||
selectAllItem.setIcon(R.drawable.ic_select_none);
|
||||
selectAllItem.setTitle(R.string.deselect_all_label);
|
||||
} else {
|
||||
selectAllItem.setIcon(R.drawable.ic_select_all);
|
||||
selectAllItem.setTitle(R.string.select_all_label);
|
||||
}
|
||||
}
|
||||
|
||||
void updateTitle() {
|
||||
if (actionMode == null) {
|
||||
return;
|
||||
}
|
||||
int totalCount = getItemCount();
|
||||
int selectedCount = selectedIds.size();
|
||||
if (totalNumberOfItems != COUNT_AUTOMATICALLY) {
|
||||
totalCount = totalNumberOfItems;
|
||||
if (shouldSelectLazyLoadedItems) {
|
||||
selectedCount += (totalNumberOfItems - getItemCount());
|
||||
}
|
||||
}
|
||||
actionMode.setTitle(activity.getResources()
|
||||
.getQuantityString(R.plurals.num_selected_label, selectedIds.size(),
|
||||
selectedCount, totalCount));
|
||||
}
|
||||
|
||||
public void setOnSelectModeListener(OnSelectModeListener onSelectModeListener) {
|
||||
this.onSelectModeListener = onSelectModeListener;
|
||||
}
|
||||
|
||||
private void callOnEndSelectMode() {
|
||||
if (onSelectModeListener != null) {
|
||||
onSelectModeListener.onEndSelectMode();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldSelectLazyLoadedItems() {
|
||||
return shouldSelectLazyLoadedItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the total number of items that could be lazy-loaded.
|
||||
* Can also be set to {@link #COUNT_AUTOMATICALLY} to simply use {@link #getItemCount}
|
||||
*/
|
||||
public void setTotalNumberOfItems(int totalNumberOfItems) {
|
||||
this.totalNumberOfItems = totalNumberOfItems;
|
||||
}
|
||||
|
||||
public interface OnSelectModeListener {
|
||||
void onStartSelectMode();
|
||||
|
||||
void onEndSelectMode();
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import de.danoeh.antennapod.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Displays a list of items that have a subtitle and an icon.
|
||||
*/
|
||||
public class SimpleIconListAdapter<T extends SimpleIconListAdapter.ListItem> extends ArrayAdapter<T> {
|
||||
private final Context context;
|
||||
private final List<T> listItems;
|
||||
|
||||
public SimpleIconListAdapter(Context context, List<T> listItems) {
|
||||
super(context, R.layout.simple_icon_list_item, listItems);
|
||||
this.context = context;
|
||||
this.listItems = listItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup parent) {
|
||||
if (view == null) {
|
||||
view = View.inflate(context, R.layout.simple_icon_list_item, null);
|
||||
}
|
||||
|
||||
ListItem item = listItems.get(position);
|
||||
((TextView) view.findViewById(R.id.title)).setText(item.title);
|
||||
((TextView) view.findViewById(R.id.subtitle)).setText(item.subtitle);
|
||||
Glide.with(context)
|
||||
.load(item.imageUrl)
|
||||
.apply(new RequestOptions()
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.fitCenter()
|
||||
.dontAnimate())
|
||||
.into(((ImageView) view.findViewById(R.id.icon)));
|
||||
return view;
|
||||
}
|
||||
|
||||
public static class ListItem {
|
||||
public final String title;
|
||||
public final String subtitle;
|
||||
public final String imageUrl;
|
||||
|
||||
public ListItem(String title, String subtitle, String imageUrl) {
|
||||
this.title = title;
|
||||
this.subtitle = subtitle;
|
||||
this.imageUrl = imageUrl;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,296 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.InputDevice;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.storage.NavDrawerData;
|
||||
import de.danoeh.antennapod.fragment.FeedItemlistFragment;
|
||||
import de.danoeh.antennapod.fragment.SubscriptionFragment;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.ui.common.TriangleLabelView;
|
||||
|
||||
/**
|
||||
* Adapter for subscriptions
|
||||
*/
|
||||
public class SubscriptionsRecyclerAdapter extends SelectableAdapter<SubscriptionsRecyclerAdapter.SubscriptionViewHolder>
|
||||
implements View.OnCreateContextMenuListener {
|
||||
private static final int COVER_WITH_TITLE = 1;
|
||||
|
||||
private final WeakReference<MainActivity> mainActivityRef;
|
||||
private List<NavDrawerData.DrawerItem> listItems;
|
||||
private NavDrawerData.DrawerItem selectedItem = null;
|
||||
int longPressedPosition = 0; // used to init actionMode
|
||||
|
||||
public SubscriptionsRecyclerAdapter(MainActivity mainActivity) {
|
||||
super(mainActivity);
|
||||
this.mainActivityRef = new WeakReference<>(mainActivity);
|
||||
this.listItems = new ArrayList<>();
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
public Object getItem(int position) {
|
||||
return listItems.get(position);
|
||||
}
|
||||
|
||||
public NavDrawerData.DrawerItem getSelectedItem() {
|
||||
return selectedItem;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SubscriptionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View itemView = LayoutInflater.from(mainActivityRef.get()).inflate(R.layout.subscription_item, parent, false);
|
||||
TextView feedTitle = itemView.findViewById(R.id.txtvTitle);
|
||||
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) feedTitle.getLayoutParams();
|
||||
int topAndBottomItemId = R.id.imgvCover;
|
||||
int belowItemId = 0;
|
||||
|
||||
if (viewType == COVER_WITH_TITLE) {
|
||||
topAndBottomItemId = 0;
|
||||
belowItemId = R.id.imgvCover;
|
||||
feedTitle.setBackgroundColor(
|
||||
ContextCompat.getColor(feedTitle.getContext(), R.color.feed_text_bg));
|
||||
int padding = (int) convertDpToPixel(feedTitle.getContext(), 6);
|
||||
feedTitle.setPadding(padding, padding, padding, padding);
|
||||
}
|
||||
params.addRule(RelativeLayout.BELOW, belowItemId);
|
||||
params.addRule(RelativeLayout.ALIGN_TOP, topAndBottomItemId);
|
||||
params.addRule(RelativeLayout.ALIGN_BOTTOM, topAndBottomItemId);
|
||||
feedTitle.setLayoutParams(params);
|
||||
feedTitle.setSingleLine(viewType == COVER_WITH_TITLE);
|
||||
return new SubscriptionViewHolder(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull SubscriptionViewHolder holder, int position) {
|
||||
NavDrawerData.DrawerItem drawerItem = listItems.get(position);
|
||||
boolean isFeed = drawerItem.type == NavDrawerData.DrawerItem.Type.FEED;
|
||||
holder.bind(drawerItem);
|
||||
holder.itemView.setOnCreateContextMenuListener(this);
|
||||
if (inActionMode()) {
|
||||
if (isFeed) {
|
||||
holder.selectCheckbox.setVisibility(View.VISIBLE);
|
||||
holder.selectView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
holder.selectCheckbox.setChecked((isSelected(position)));
|
||||
holder.selectCheckbox.setOnCheckedChangeListener((buttonView, isChecked)
|
||||
-> setSelected(holder.getBindingAdapterPosition(), isChecked));
|
||||
holder.imageView.setAlpha(0.6f);
|
||||
holder.count.setVisibility(View.GONE);
|
||||
} else {
|
||||
holder.selectView.setVisibility(View.GONE);
|
||||
holder.imageView.setAlpha(1.0f);
|
||||
}
|
||||
|
||||
holder.itemView.setOnLongClickListener(v -> {
|
||||
if (!inActionMode()) {
|
||||
if (isFeed) {
|
||||
longPressedPosition = holder.getBindingAdapterPosition();
|
||||
}
|
||||
selectedItem = drawerItem;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
holder.itemView.setOnTouchListener((v, e) -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (e.isFromSource(InputDevice.SOURCE_MOUSE)
|
||||
&& e.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
|
||||
if (!inActionMode()) {
|
||||
if (isFeed) {
|
||||
longPressedPosition = holder.getBindingAdapterPosition();
|
||||
}
|
||||
selectedItem = drawerItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (isFeed) {
|
||||
if (inActionMode()) {
|
||||
holder.selectCheckbox.setChecked(!isSelected(holder.getBindingAdapterPosition()));
|
||||
} else {
|
||||
Fragment fragment = FeedItemlistFragment
|
||||
.newInstance(((NavDrawerData.FeedDrawerItem) drawerItem).feed.getId());
|
||||
mainActivityRef.get().loadChildFragment(fragment);
|
||||
}
|
||||
} else if (!inActionMode()) {
|
||||
Fragment fragment = SubscriptionFragment.newInstance(drawerItem.getTitle());
|
||||
mainActivityRef.get().loadChildFragment(fragment);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return listItems.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return listItems.get(position).id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
||||
if (inActionMode() || selectedItem == null) {
|
||||
return;
|
||||
}
|
||||
MenuInflater inflater = mainActivityRef.get().getMenuInflater();
|
||||
if (selectedItem.type == NavDrawerData.DrawerItem.Type.FEED) {
|
||||
inflater.inflate(R.menu.nav_feed_context, menu);
|
||||
menu.findItem(R.id.multi_select).setVisible(true);
|
||||
} else {
|
||||
inflater.inflate(R.menu.nav_folder_context, menu);
|
||||
}
|
||||
menu.setHeaderTitle(selectedItem.getTitle());
|
||||
}
|
||||
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.multi_select) {
|
||||
startSelectMode(longPressedPosition);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<Feed> getSelectedItems() {
|
||||
List<Feed> items = new ArrayList<>();
|
||||
for (int i = 0; i < getItemCount(); i++) {
|
||||
if (isSelected(i)) {
|
||||
NavDrawerData.DrawerItem drawerItem = listItems.get(i);
|
||||
if (drawerItem.type == NavDrawerData.DrawerItem.Type.FEED) {
|
||||
Feed feed = ((NavDrawerData.FeedDrawerItem) drawerItem).feed;
|
||||
items.add(feed);
|
||||
}
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
public void setItems(List<NavDrawerData.DrawerItem> listItems) {
|
||||
this.listItems = listItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelected(int pos, boolean selected) {
|
||||
NavDrawerData.DrawerItem drawerItem = listItems.get(pos);
|
||||
if (drawerItem.type == NavDrawerData.DrawerItem.Type.FEED) {
|
||||
super.setSelected(pos, selected);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return UserPreferences.shouldShowSubscriptionTitle() ? COVER_WITH_TITLE : 0;
|
||||
}
|
||||
|
||||
public class SubscriptionViewHolder extends RecyclerView.ViewHolder {
|
||||
private final TextView feedTitle;
|
||||
private final ImageView imageView;
|
||||
private final TriangleLabelView count;
|
||||
private final FrameLayout selectView;
|
||||
private final CheckBox selectCheckbox;
|
||||
|
||||
public SubscriptionViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
feedTitle = itemView.findViewById(R.id.txtvTitle);
|
||||
imageView = itemView.findViewById(R.id.imgvCover);
|
||||
count = itemView.findViewById(R.id.triangleCountView);
|
||||
selectView = itemView.findViewById(R.id.selectView);
|
||||
selectCheckbox = itemView.findViewById(R.id.selectCheckBox);
|
||||
}
|
||||
|
||||
public void bind(NavDrawerData.DrawerItem drawerItem) {
|
||||
Drawable drawable = AppCompatResources.getDrawable(selectView.getContext(),
|
||||
R.drawable.ic_checkbox_background);
|
||||
selectView.setBackground(drawable); // Setting this in XML crashes API <= 21
|
||||
feedTitle.setText(drawerItem.getTitle());
|
||||
imageView.setContentDescription(drawerItem.getTitle());
|
||||
feedTitle.setVisibility(View.VISIBLE);
|
||||
if (TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL) {
|
||||
count.setCorner(TriangleLabelView.Corner.TOP_LEFT);
|
||||
}
|
||||
|
||||
if (drawerItem.getCounter() > 0) {
|
||||
count.setPrimaryText(NumberFormat.getInstance().format(drawerItem.getCounter()));
|
||||
count.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
count.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (drawerItem.type == NavDrawerData.DrawerItem.Type.FEED) {
|
||||
Feed feed = ((NavDrawerData.FeedDrawerItem) drawerItem).feed;
|
||||
boolean textAndImageCombind = feed.isLocalFeed()
|
||||
&& feed.getImageUrl() != null && feed.getImageUrl().startsWith(Feed.PREFIX_GENERATIVE_COVER);
|
||||
new CoverLoader(mainActivityRef.get())
|
||||
.withUri(feed.getImageUrl())
|
||||
.withPlaceholderView(feedTitle, textAndImageCombind)
|
||||
.withCoverView(imageView)
|
||||
.load();
|
||||
} else {
|
||||
new CoverLoader(mainActivityRef.get())
|
||||
.withResource(R.drawable.ic_tag)
|
||||
.withPlaceholderView(feedTitle, true)
|
||||
.withCoverView(imageView)
|
||||
.load();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static float convertDpToPixel(Context context, float dp) {
|
||||
return dp * context.getResources().getDisplayMetrics().density;
|
||||
}
|
||||
|
||||
public static class GridDividerItemDecorator extends RecyclerView.ItemDecoration {
|
||||
@Override
|
||||
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
|
||||
super.onDraw(c, parent, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect,
|
||||
@NonNull View view,
|
||||
@NonNull RecyclerView parent,
|
||||
@NonNull RecyclerView.State state) {
|
||||
super.getItemOffsets(outRect, view, parent, state);
|
||||
Context context = parent.getContext();
|
||||
int insetOffset = (int) convertDpToPixel(context, 1f);
|
||||
outRect.set(insetOffset, insetOffset, insetOffset, insetOffset);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadService;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
|
||||
public class CancelDownloadActionButton extends ItemActionButton {
|
||||
|
||||
public CancelDownloadActionButton(FeedItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
public int getLabel() {
|
||||
return R.string.cancel_download_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DrawableRes
|
||||
public int getDrawable() {
|
||||
return R.drawable.ic_cancel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Context context) {
|
||||
FeedMedia media = item.getMedia();
|
||||
DownloadService.cancel(context, media.getDownload_url());
|
||||
if (UserPreferences.isEnableAutodownload()) {
|
||||
item.disableAutoDownload();
|
||||
DBWriter.setFeedItem(item);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
|
||||
public class DeleteActionButton extends ItemActionButton {
|
||||
|
||||
public DeleteActionButton(FeedItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
public int getLabel() {
|
||||
return R.string.delete_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DrawableRes
|
||||
public int getDrawable() {
|
||||
return R.drawable.ic_delete;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Context context) {
|
||||
final FeedMedia media = item.getMedia();
|
||||
if (media == null) {
|
||||
return;
|
||||
}
|
||||
DBWriter.deleteFeedMediaOfItem(context, media.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVisibility() {
|
||||
return (item.getMedia() != null && item.getMedia().isDownloaded()) ? View.VISIBLE : View.INVISIBLE;
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadRequestCreator;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadService;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.preferences.UsageStatistics;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.util.NetworkUtils;
|
||||
|
||||
public class DownloadActionButton extends ItemActionButton {
|
||||
|
||||
public DownloadActionButton(FeedItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
public int getLabel() {
|
||||
return R.string.download_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DrawableRes
|
||||
public int getDrawable() {
|
||||
return R.drawable.ic_download;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVisibility() {
|
||||
return item.getFeed().isLocalFeed() ? View.INVISIBLE : View.VISIBLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Context context) {
|
||||
final FeedMedia media = item.getMedia();
|
||||
if (media == null || shouldNotDownload(media)) {
|
||||
return;
|
||||
}
|
||||
|
||||
UsageStatistics.logAction(UsageStatistics.ACTION_DOWNLOAD);
|
||||
|
||||
if (NetworkUtils.isEpisodeDownloadAllowed() || MobileDownloadHelper.userAllowedMobileDownloads()) {
|
||||
DownloadService.download(context, false, DownloadRequestCreator.create(item.getMedia()).build());
|
||||
} else if (MobileDownloadHelper.userChoseAddToQueue() && !item.isTagged(FeedItem.TAG_QUEUE)) {
|
||||
DBWriter.addQueueItem(context, item);
|
||||
Toast.makeText(context, R.string.added_to_queue_label, Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
MobileDownloadHelper.confirmMobileDownload(context, item);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldNotDownload(@NonNull FeedMedia media) {
|
||||
boolean isDownloading = DownloadService.isDownloadingFile(media.getDownload_url());
|
||||
return isDownloading || media.isDownloaded();
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.ImageView;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import android.view.View;
|
||||
|
||||
import de.danoeh.antennapod.core.service.download.DownloadService;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.util.FeedItemUtil;
|
||||
|
||||
public abstract class ItemActionButton {
|
||||
FeedItem item;
|
||||
|
||||
ItemActionButton(FeedItem item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
@StringRes
|
||||
public abstract int getLabel();
|
||||
|
||||
@DrawableRes
|
||||
public abstract int getDrawable();
|
||||
|
||||
public abstract void onClick(Context context);
|
||||
|
||||
public int getVisibility() {
|
||||
return View.VISIBLE;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ItemActionButton forItem(@NonNull FeedItem item) {
|
||||
final FeedMedia media = item.getMedia();
|
||||
if (media == null) {
|
||||
return new MarkAsPlayedActionButton(item);
|
||||
}
|
||||
|
||||
final boolean isDownloadingMedia = DownloadService.isDownloadingFile(media.getDownload_url());
|
||||
if (FeedItemUtil.isCurrentlyPlaying(media)) {
|
||||
return new PauseActionButton(item);
|
||||
} else if (item.getFeed().isLocalFeed()) {
|
||||
return new PlayLocalActionButton(item);
|
||||
} else if (media.isDownloaded()) {
|
||||
return new PlayActionButton(item);
|
||||
} else if (isDownloadingMedia) {
|
||||
return new CancelDownloadActionButton(item);
|
||||
} else if (UserPreferences.isStreamOverDownload()) {
|
||||
return new StreamActionButton(item);
|
||||
} else {
|
||||
return new DownloadActionButton(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void configure(@NonNull View button, @NonNull ImageView icon, Context context) {
|
||||
button.setVisibility(getVisibility());
|
||||
button.setContentDescription(context.getString(getLabel()));
|
||||
button.setOnClickListener((view) -> onClick(context));
|
||||
icon.setImageResource(getDrawable());
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
import android.view.View;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
|
||||
public class MarkAsPlayedActionButton extends ItemActionButton {
|
||||
|
||||
public MarkAsPlayedActionButton(FeedItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
public int getLabel() {
|
||||
return (item.hasMedia() ? R.string.mark_read_label : R.string.mark_read_no_media_label);
|
||||
}
|
||||
|
||||
@Override
|
||||
@DrawableRes
|
||||
public int getDrawable() {
|
||||
return R.drawable.ic_check;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Context context) {
|
||||
if (!item.isPlayed()) {
|
||||
DBWriter.markItemPlayed(item, FeedItem.PLAYED, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVisibility() {
|
||||
return (item.isPlayed()) ? View.INVISIBLE : View.VISIBLE;
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadRequestCreator;
|
||||
import de.danoeh.antennapod.core.service.download.DownloadService;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
|
||||
class MobileDownloadHelper {
|
||||
private static long addToQueueTimestamp;
|
||||
private static long allowMobileDownloadTimestamp;
|
||||
private static final int TEN_MINUTES_IN_MILLIS = 10 * 60 * 1000;
|
||||
|
||||
static boolean userChoseAddToQueue() {
|
||||
return System.currentTimeMillis() - addToQueueTimestamp < TEN_MINUTES_IN_MILLIS;
|
||||
}
|
||||
|
||||
static boolean userAllowedMobileDownloads() {
|
||||
return System.currentTimeMillis() - allowMobileDownloadTimestamp < TEN_MINUTES_IN_MILLIS;
|
||||
}
|
||||
|
||||
static void confirmMobileDownload(final Context context, final FeedItem item) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.confirm_mobile_download_dialog_title)
|
||||
.setMessage(R.string.confirm_mobile_download_dialog_message)
|
||||
.setPositiveButton(context.getText(R.string.confirm_mobile_download_dialog_enable_temporarily),
|
||||
(dialog, which) -> downloadFeedItems(context, item));
|
||||
if (!DBReader.getQueueIDList().contains(item.getId())) {
|
||||
builder.setMessage(R.string.confirm_mobile_download_dialog_message_not_in_queue)
|
||||
.setNeutralButton(R.string.confirm_mobile_download_dialog_only_add_to_queue,
|
||||
(dialog, which) -> addToQueue(context, item));
|
||||
}
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private static void addToQueue(Context context, FeedItem item) {
|
||||
addToQueueTimestamp = System.currentTimeMillis();
|
||||
DBWriter.addQueueItem(context, item);
|
||||
}
|
||||
|
||||
private static void downloadFeedItems(Context context, FeedItem item) {
|
||||
allowMobileDownloadTimestamp = System.currentTimeMillis();
|
||||
DownloadService.download(context, true, DownloadRequestCreator.create(item.getMedia()).build());
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.util.FeedItemUtil;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
|
||||
import static de.danoeh.antennapod.core.service.playback.PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE;
|
||||
|
||||
public class PauseActionButton extends ItemActionButton {
|
||||
|
||||
public PauseActionButton(FeedItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
public int getLabel() {
|
||||
return R.string.pause_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DrawableRes
|
||||
public int getDrawable() {
|
||||
return R.drawable.ic_pause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Context context) {
|
||||
FeedMedia media = item.getMedia();
|
||||
if (media == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (FeedItemUtil.isCurrentlyPlaying(media)) {
|
||||
IntentUtils.sendLocalBroadcast(context, ACTION_PAUSE_PLAY_CURRENT_EPISODE);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.model.playback.MediaType;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
|
||||
|
||||
public class PlayActionButton extends ItemActionButton {
|
||||
|
||||
public PlayActionButton(FeedItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
public int getLabel() {
|
||||
return R.string.play_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DrawableRes
|
||||
public int getDrawable() {
|
||||
return R.drawable.ic_play_24dp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Context context) {
|
||||
FeedMedia media = item.getMedia();
|
||||
if (media == null) {
|
||||
return;
|
||||
}
|
||||
if (!media.fileExists()) {
|
||||
DBTasks.notifyMissingFeedMediaFile(context, media);
|
||||
return;
|
||||
}
|
||||
new PlaybackServiceStarter(context, media)
|
||||
.callEvenIfRunning(true)
|
||||
.start();
|
||||
|
||||
if (media.getMediaType() == MediaType.VIDEO) {
|
||||
context.startActivity(PlaybackService.getPlayerActivityIntent(context, media));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.model.playback.MediaType;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
|
||||
|
||||
public class PlayLocalActionButton extends ItemActionButton {
|
||||
|
||||
public PlayLocalActionButton(FeedItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
public int getLabel() {
|
||||
return R.string.play_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DrawableRes
|
||||
public int getDrawable() {
|
||||
return R.drawable.ic_play_24dp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Context context) {
|
||||
final FeedMedia media = item.getMedia();
|
||||
if (media == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
new PlaybackServiceStarter(context, media)
|
||||
.callEvenIfRunning(true)
|
||||
.start();
|
||||
|
||||
if (media.getMediaType() == MediaType.VIDEO) {
|
||||
context.startActivity(PlaybackService.getPlayerActivityIntent(context, media));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.model.playback.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 StreamActionButton extends ItemActionButton {
|
||||
|
||||
public StreamActionButton(FeedItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
public int getLabel() {
|
||||
return R.string.stream_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DrawableRes
|
||||
public int getDrawable() {
|
||||
return R.drawable.ic_stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Context context) {
|
||||
final FeedMedia media = item.getMedia();
|
||||
if (media == null) {
|
||||
return;
|
||||
}
|
||||
UsageStatistics.logAction(UsageStatistics.ACTION_STREAM);
|
||||
|
||||
if (!NetworkUtils.isStreamingAllowed()) {
|
||||
new StreamingConfirmationDialog(context, media).show();
|
||||
return;
|
||||
}
|
||||
new PlaybackServiceStarter(context, media)
|
||||
.callEvenIfRunning(true)
|
||||
.start();
|
||||
|
||||
if (media.getMediaType() == MediaType.VIDEO) {
|
||||
context.startActivity(PlaybackService.getPlayerActivityIntent(context, media));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package de.danoeh.antennapod.adapter.actionbutton;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.util.IntentUtils;
|
||||
|
||||
public class VisitWebsiteActionButton extends ItemActionButton {
|
||||
|
||||
public VisitWebsiteActionButton(FeedItem item) {
|
||||
super(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
public int getLabel() {
|
||||
return R.string.visit_website_label;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DrawableRes
|
||||
public int getDrawable() {
|
||||
return R.drawable.ic_web;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Context context) {
|
||||
IntentUtils.openInBrowser(context, item.getLink());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVisibility() {
|
||||
return (item.getLink() == null) ? View.INVISIBLE : View.VISIBLE;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue