Compare commits
1149 Commits
Author | SHA1 | Date |
---|---|---|
Óscar García Amor | 90205fe0fb | |
Óscar García Amor | 2165ce75b3 | |
Óscar García Amor | 8b3ee0a8d6 | |
birdbird | 695b2df63f | |
tzugen | 798d795e81 | |
tzugen | ecfce59e0f | |
tzugen | de0cb7713b | |
tzugen | 78bfab3753 | |
tzugen | b955d77152 | |
tzugen | b11694d6a2 | |
tzugen | 31a1fdace1 | |
tzugen | 5b03b632fd | |
tzugen | 152b1d261a | |
tzugen | 53a1a5545a | |
tzugen | ad54db5bcb | |
tzugen | 177329abcf | |
tzugen | 241e51015f | |
tzugen | 60dbe70ca5 | |
tzugen | 8490f7115d | |
tzugen | ee67f4c744 | |
tzugen | 3a3bd10fdb | |
birdbird | 3445576dc9 | |
tzugen | 8c40f662a1 | |
birdbird | 6c6227ce41 | |
tzugen | 240a2fa8f6 | |
tzugen | 7de775dc26 | |
birdbird | d034fc9c71 | |
birdbird | 05ada9297d | |
Maxence G | aa6c037b20 | |
Maxence G | b8c924be27 | |
Maxence G | 0929a6a1bd | |
Maxence G | fefee74a66 | |
Maxence G | 37e3ce09c1 | |
Maxence G | 16b3fcad32 | |
Maxence G | d6aebd9989 | |
Maxence G | 3f408600cb | |
Maxence G | 9014b47b74 | |
tzugen | ac489ae8b9 | |
tzugen | e7f8fa21cb | |
tzugen | b1c3cabfef | |
tzugen | 77865a143d | |
Óscar García Amor | ff9c7b2435 | |
Óscar García Amor | 737563bf6b | |
tzugen | 9a73d72fa4 | |
tzugen | 98ce519014 | |
tzugen | 83fc54d332 | |
Maxence G | a2b9c6b9a3 | |
Maxence G | 5ae56d26c5 | |
Maxence G | 4efb6dcb58 | |
tzugen | 8a90e98989 | |
tzugen | 46a8f4640d | |
tzugen | ab41966943 | |
tzugen | 00d7ce326c | |
Maxence G | bc4b0aa832 | |
Maxence G | 23fd336ffd | |
Maxence G | b57a973510 | |
Maxence G | 8796006ced | |
Maxence G | 545b65921e | |
Maxence G | cf367ead92 | |
Maxence G | 9961213f09 | |
tzugen | 5deb7d4d58 | |
tzugen | 5f31eaaffe | |
tzugen | cad6477cd9 | |
tzugen | b440821ea8 | |
Holger Müller | 8663b9d50e | |
Óscar García Amor | 2bae243be0 | |
Óscar García Amor | 139e810186 | |
tzugen | 66443ba018 | |
tzugen | f8b78a47d2 | |
tzugen | 4cda114f4c | |
tzugen | d8b5b774ee | |
tzugen | b6730f5a93 | |
tzugen | 87c160610f | |
tzugen | 70f8b75019 | |
tzugen | 147d7cd46e | |
tzugen | 59e37e62a6 | |
tzugen | 1e571e165c | |
tzugen | 5e0dd14c4f | |
tzugen | 53ae0cd232 | |
tzugen | 608f86ac5f | |
tzugen | 669b51c0d2 | |
tzugen | 6e1478d896 | |
Nite | d9e4b8b3d3 | |
Nite | f790e29add | |
Nite | faf07f2887 | |
i-do-cpp | 057644f592 | |
tzugen | 926081f84c | |
tzugen | 4a00494647 | |
Nite | 34e0178db3 | |
Nite | cbe3992b01 | |
Nite | 46846bd5c9 | |
tzugen | 707339b88b | |
tzugen | 827654c0c1 | |
tzugen | 1d236aa6e3 | |
tzugen | 9cdba9a27a | |
tzugen | 7ba599f58c | |
tzugen | 2e1e627b7a | |
tzugen | d550eabf88 | |
tzugen | dda86b42c7 | |
tzugen | b6e890b26c | |
tzugen | c2ac1d436f | |
tzugen | 2aaa3c2119 | |
tzugen | 5d4aff1f21 | |
tzugen | 6115ac995f | |
tzugen | 647435fe55 | |
tzugen | 81d24f6cbb | |
tzugen | 69c78f4c37 | |
tzugen | 3691428a68 | |
tzugen | 788538ee6a | |
tzugen | 762aeec5d3 | |
tzugen | a3a0c7f41d | |
tzugen | 1564379bd1 | |
tzugen | 7d33770fd6 | |
tzugen | 728afad00c | |
Óscar García Amor | f121e297df | |
Óscar García Amor | 3f2cfb131a | |
Óscar García Amor | b8b4b81726 | |
Óscar García Amor | 383089a409 | |
Óscar García Amor | 8d8a5f05ea | |
Óscar García Amor | 46a2e5d67b | |
tzugen | 92ef78a36a | |
tzugen | e5021959c3 | |
tzugen | 3ca25ed1c6 | |
tzugen | 6da83db9df | |
tzugen | 9779844620 | |
tzugen | f936ad690c | |
tzugen | 5230ce011d | |
Holger Müller | a98c9e2ffd | |
tzugen | 0128a8b29d | |
tzugen | 41f5520f1f | |
tzugen | fd34199c27 | |
tzugen | bb77216eff | |
tzugen | e1f4ee15d5 | |
tzugen | d0959ffcb5 | |
tzugen | 4c22c8b41b | |
tzugen | ba1a1c5538 | |
tzugen | 7742f67796 | |
tzugen | 1a69507e34 | |
tzugen | 46fb7664c3 | |
tzugen | dd65a12b53 | |
tzugen | 2f7f47783a | |
tzugen | b1c2d020b5 | |
tzugen | 5dc9fda7a4 | |
tzugen | 1313fb6c0c | |
tzugen | 5966dd7299 | |
tzugen | 1703f02aad | |
tzugen | 922022ab03 | |
tzugen | bfc11f9924 | |
tzugen | e77b5abd3e | |
tzugen | 988bf62acf | |
tzugen | 1a46f7e2c6 | |
tzugen | 1d88c585c4 | |
tzugen | 287169649a | |
tzugen | 020f67d5e6 | |
Óscar García Amor | fcc57ae316 | |
tzugen | 0fd17bfe8c | |
tzugen | 0c016bff41 | |
tzugen | 12435ed9ec | |
tzugen | c2226ba202 | |
tzugen | 892b441c0d | |
tzugen | e53da92dac | |
tzugen | 2de59b2206 | |
tzugen | 34c13d7908 | |
tzugen | 88918bd839 | |
tzugen | f49063664b | |
tzugen | 126efd35c6 | |
tzugen | a6a052781d | |
tzugen | 107b01fd91 | |
tzugen | d05ac1489e | |
Holger Müller | fc94d28862 | |
Holger Müller | 8bec74e66a | |
Holger Müller | acf6c5a681 | |
tzugen | 81a21ce8b7 | |
Holger Müller | cf86101de2 | |
Nite | 5a44fcfe29 | |
Nite | 2aa5174fbd | |
Nite | cf7cef9831 | |
Nite | 423957d954 | |
Holger Müller | 0944bd2217 | |
Holger Müller | 7b750c692c | |
Holger Müller | c247e930c4 | |
tzugen | 44d68a71da | |
tzugen | 7a51c271ba | |
Óscar García Amor | 4d91068535 | |
tzugen | fe3b713241 | |
Holger Müller | 2f5704548c | |
tzugen | 17850980e1 | |
tzugen | 34d2b45d71 | |
tzugen | 555ef5b7ff | |
tzugen | c269243a0d | |
tzugen | 2d8b93301f | |
tzugen | e4a41de3ef | |
tzugen | f0447105d2 | |
tzugen | 65c4f2b100 | |
tzugen | f6f9683a9c | |
tzugen | c2d62e8688 | |
tzugen | e153565086 | |
tzugen | f30a582c7b | |
Holger Müller | cc5f29ca98 | |
Holger Müller | 8f18192c36 | |
Holger Müller | ae2055e324 | |
Nite | dee4675715 | |
Nite | e8c31db90f | |
Nite | 34fb63c783 | |
Nite | 9ee03aae2f | |
Nite | ebfc06c423 | |
Nite | 0587f4d837 | |
Nite | 6bfd06c6a0 | |
Óscar García Amor | 6442bae882 | |
Nite | c81c685800 | |
Holger Müller | 5941e5ab87 | |
Holger Müller | cf52d76698 | |
Cem Eren | 273ac8f9b8 | |
Nite | 465c211017 | |
Nite | 4fbedc3d2b | |
Cem Eren | 0961f56a7d | |
Cem Eren | eb0fa67431 | |
Óscar García Amor | 3d65c0a90c | |
Nite | 7dd479c0d2 | |
Nite | b5bfd87fcc | |
Philippe Daouadi | d03b633eeb | |
Nite | 4ee4b70e09 | |
Nite | 8675f25668 | |
Nite | 00dc87d5df | |
Nite | b7d1e4acf6 | |
Óscar García Amor | baa0c92c7a | |
Nite | 7c9d51f758 | |
Nite | 9bf7e99abd | |
Nite | b5e606455e | |
Óscar García Amor | e2ddb35ce3 | |
Óscar García Amor | 9581a17bd9 | |
Óscar García Amor | 3fbf47dedf | |
Óscar García Amor | 87aea4847c | |
Nite | f210a6e363 | |
Nite | a9ee09bc2f | |
Nite | bf96f36cb4 | |
Nite | da69fb9f1f | |
Nite | 5962cc2add | |
Nite | 0d4b400105 | |
Nite | ca9ba68a5e | |
Nite | d00a30940e | |
Nite | 586bc51a9c | |
Óscar García Amor | a53d5378bf | |
Óscar García Amor | 4778d18fb9 | |
Óscar García Amor | 2ff944d77d | |
Nite | 462719ccd1 | |
Nite | e39f232732 | |
dependabot[bot] | ca89d4b55e | |
Óscar García Amor | 3558479278 | |
dependabot[bot] | c66ea1c437 | |
Óscar García Amor | 23d0783164 | |
Óscar García Amor | 22c9db59c3 | |
Óscar García Amor | 121bcde18a | |
Óscar García Amor | 41a462708d | |
Nite | 5e24c3ab40 | |
tzugen | f68b46678a | |
tzugen | fd239e8e72 | |
tzugen | 10892f7527 | |
Óscar García Amor | e2a6a32b76 | |
Óscar García Amor | 0dd666415d | |
Óscar García Amor | 8f43f69a66 | |
Óscar García Amor | 193fe5ac3a | |
Óscar García Amor | 3588bb9380 | |
Óscar García Amor | 95b78a3c11 | |
tzugen | 9f5bc85cf2 | |
tzugen | 5a02467ee8 | |
tzugen | 4a996f8edc | |
tzugen | 8d5675f461 | |
tzugen | a5e8daa912 | |
tzugen | c2b23c4001 | |
tzugen | 10b83805a9 | |
Óscar García Amor | fbf5a63396 | |
Óscar García Amor | bb370bfc44 | |
Óscar García Amor | 2153650d8b | |
tzugen | 987fbbe02a | |
tzugen | b958dcabe4 | |
tzugen | d66de5955b | |
tzugen | 1eca5a756e | |
tzugen | dd92d00be5 | |
tzugen | 7531a4d4aa | |
tzugen | 82f45bd5dd | |
tzugen | c0ef964a3e | |
Nite | 34e232a43a | |
tzugen | 65347a20fa | |
tzugen | 7f42ed6a37 | |
dependabot[bot] | 95f37ba2e2 | |
tzugen | fa434342d9 | |
Nite | 17e49ff49e | |
Nite | adf72d6460 | |
Nite | 505405e87f | |
Nite | a3ad17692b | |
Nite | 5c3e2f6e37 | |
Nite | 941fbd907f | |
Nite | 095cf4ef4a | |
tzugen | 3be480a9a2 | |
tzugen | 5bfa0044ab | |
tzugen | 6b1fc7575a | |
tzugen | 6fcea86097 | |
tzugen | 28d5e5043f | |
tzugen | eb2aeabd5d | |
tzugen | a0da791b28 | |
tzugen | f4554ff29e | |
tzugen | 9acc5121a4 | |
Nite | 107146c8d9 | |
Nite | d51544f927 | |
Nite | 66e7732ec2 | |
Nite | fa4214a0ac | |
Nite | 34c5ced32e | |
Nite | 90638e5fd7 | |
tzugen | 6730713763 | |
dependabot[bot] | 38fa4964f8 | |
tzugen | b85230c056 | |
dependabot[bot] | fb85fb4e82 | |
Nite | 8c716da213 | |
tzugen | 80e587c1aa | |
tzugen | e337177715 | |
tzugen | d8cdc81424 | |
tzugen | 351ad914e7 | |
Nite | 0d24c87eef | |
tzugen | de04f4cbe6 | |
tzugen | 026aa79572 | |
tzugen | 6daa17efd5 | |
tzugen | f2948cd3db | |
tzugen | 2ac1ea3f89 | |
tzugen | f1e789ea9b | |
tzugen | bdac092eff | |
tzugen | aa33d7c882 | |
tzugen | 775f56c6fa | |
tzugen | 2f0ff384d0 | |
tzugen | 82d90a6aee | |
tzugen | b33fe2d451 | |
tzugen | 4e37a2483c | |
tzugen | 5dfb66eec2 | |
tzugen | ad793e27a5 | |
tzugen | 2086a6cac5 | |
tzugen | eeb2d13d96 | |
tzugen | f8a87f7c85 | |
tzugen | 7640f4c4aa | |
tzugen | d243ae1b44 | |
tzugen | 6277ee73c0 | |
tzugen | 7a2dbf65d9 | |
tzugen | 19d014709f | |
tzugen | d0e39efc50 | |
tzugen | e81b1ef8c2 | |
tzugen | 5f716f5008 | |
Nite | 5cf914f555 | |
Nite | 5c7cde2349 | |
Nite | 3f570636dd | |
Nite | 1d0bb944e1 | |
Nite | 5fac1b74a3 | |
Nite | d84a0a3929 | |
Nite | f3ac843a9c | |
tzugen | cddbe72752 | |
tzugen | 744282f10a | |
tzugen | 51d6a23208 | |
tzugen | a327a5b390 | |
tzugen | 36b581e3c1 | |
tzugen | 1ed9360bc7 | |
tzugen | ed152fa52a | |
Nite | 45e9728e0f | |
tzugen | b1cb70764c | |
tzugen | 00cd4fce44 | |
tzugen | 97eb753413 | |
tzugen | 00781ba7de | |
Nite | d6f908b80c | |
Nite | e019ec788d | |
tzugen | f73457298d | |
tzugen | c9e276dc76 | |
Nite | 28ef67a210 | |
tzugen | bb36116d70 | |
tzugen | 8830d76497 | |
tzugen | 61f23fa948 | |
tzugen | 92adcf47bd | |
tzugen | 7e3cb19bac | |
tzugen | dc312d4592 | |
dependabot[bot] | 435376b48b | |
Nite | 69825b28bb | |
Nite | 66df5b1daf | |
Nite | 38979bf9d4 | |
Nite | 68549992f4 | |
Nite | 2e482e02a2 | |
Nite | bf18c43d73 | |
Nite | 7fe4845305 | |
dependabot[bot] | c29b8ebe0e | |
Nite | 3bacabe480 | |
tzugen | aac73cd6d7 | |
tzugen | a66d07ae84 | |
Nite | 210ae35ee0 | |
Nite | 4e3102f131 | |
Nite | eba42b82dc | |
Nite | a34fc809d9 | |
Nite | ffb2d59886 | |
tzugen | a6e76e9d53 | |
tzugen | c4e2c786d1 | |
tzugen | 050161bbb0 | |
tzugen | f085a8ab65 | |
tzugen | dfb3561965 | |
tzugen | 8c99c84a90 | |
tzugen | 4fb4ab18da | |
tzugen | e6624ada9a | |
tzugen | a58e541ccc | |
tzugen | 416bc57eea | |
dependabot[bot] | 34c3936513 | |
dependabot[bot] | aece29559e | |
tzugen | e32b3461c9 | |
Nite | fec2d78d30 | |
Nite | 7ed91db250 | |
Nite | 7c43d01f8e | |
Nite | 5eaf9cccb1 | |
tzugen | bd23f54783 | |
tzugen | 5fe1921ca5 | |
tzugen | e5f7ca6310 | |
tzugen | 4d42c0d9d2 | |
tzugen | e19d43d6b3 | |
tzugen | 2d9a212b5c | |
Nite | f0c02f5551 | |
tzugen | 7c956566a0 | |
Nite | ada780ab25 | |
Nite | 70e42fb443 | |
tzugen | c83a9778fc | |
Nite | 6636d6a558 | |
Nite | 7ccb9d055c | |
Nite | 9782e18b6e | |
tzugen | eb2e6ada0a | |
Nite | cef1153f89 | |
tzugen | fea515a526 | |
tzugen | c44257f569 | |
tzugen | 939cd8583c | |
tzugen | 7d2923230c | |
tzugen | ece53f7687 | |
Nite | 2847a51674 | |
tzugen | 24092ce465 | |
tzugen | 6f676d20b0 | |
tzugen | 1d5b335f97 | |
tzugen | 9bc19ec044 | |
tzugen | 0bcf51a409 | |
Nite | f58c361e4e | |
Nite | 949e7e58ba | |
Nite | 77a2dcf935 | |
dependabot[bot] | 974099630a | |
Nite | 2197959507 | |
tzugen | 62bade916f | |
Nite | ccf39661ab | |
Nite | 7c66bc7ec8 | |
Nite | 477f6f5d7c | |
Nite | 784c65f96d | |
Nite | 16b2a99631 | |
Nite | 427034053c | |
Nite | 6905c68898 | |
Nite | 2b40577d4b | |
Nite | 23cca33d5a | |
Óscar García Amor | 62986ca79d | |
Óscar García Amor | e2cff64e4e | |
Óscar García Amor | 704f78366a | |
Óscar García Amor | e426081d1d | |
tzugen | bc43cc6874 | |
tzugen | 83f4ecb15a | |
tzugen | fbdf6d846b | |
tzugen | d1e636f553 | |
tzugen | d7cd68c39e | |
tzugen | b892b7b8d3 | |
tzugen | 5c9b149bec | |
tzugen | ec4f57b5b6 | |
tzugen | 87c2e44ab8 | |
tzugen | 910a05d49d | |
Nite | b44e0517ce | |
Nite | 5a3eb6482a | |
Nite | 7a6f993602 | |
Nite | 2f98bf9ae0 | |
tzugen | 855f111cb2 | |
tzugen | aea2e6baef | |
Nite | eff1a714e2 | |
Nite | f752307a38 | |
Nite | 86f2aa1656 | |
Nite | 256f785d39 | |
Nite | 62150b77d3 | |
Nite | 652f85b070 | |
Nite | 381e29b2d1 | |
Nite | 4b4853374c | |
Óscar García Amor | 7091967fa5 | |
Óscar García Amor | c3df5ead37 | |
Nite | a3128c5f7f | |
Nite | 221f218856 | |
Nite | ed59136fa6 | |
dependabot[bot] | 0da22ef75b | |
tzugen | 6b24e0ae4b | |
Nite | 90e269525e | |
dependabot[bot] | 1d26d9ba3a | |
Nite | d89ae50ef4 | |
dependabot[bot] | e77a8e67a8 | |
tzugen | a7a895af96 | |
tzugen | 35a0dd761d | |
Nite | fda746905d | |
Nite | 2237b476dc | |
Nite | b640805559 | |
Nite | d4ce10ebfa | |
Nite | be49145aa8 | |
Nite | 01aa1fe887 | |
Nite | 77f857b1c6 | |
tzugen | 02129a8fd0 | |
tzugen | ecc7e870f1 | |
tzugen | 28097bf325 | |
tzugen | 611539be55 | |
tzugen | 5ff4d21abc | |
tzugen | ec49775d7e | |
tzugen | c48c41284e | |
tzugen | ba745a5f9a | |
Nite | 8ab903c7d2 | |
dependabot[bot] | ac3635a8f4 | |
Nite | 546039fd5e | |
dependabot[bot] | 86a772f115 | |
Nite | d3c7fcf7e7 | |
Nite | 1f5de82004 | |
Óscar García Amor | a64fe47353 | |
Óscar García Amor | 8928980bae | |
tzugen | 950691daab | |
Nite | ee1e4548e3 | |
Nite | 07f334a7fd | |
Nite | 5ef3ced111 | |
Óscar García Amor | 4ce5cfcb8e | |
Nite | 25fd2f4772 | |
dependabot[bot] | 008520fbd0 | |
Nite | 225bb78263 | |
Nite | 84d28178bf | |
Nite | f29db522d4 | |
Nite | 9f1315b6dc | |
Nite | 9f02ee95a4 | |
tzugen | d08a38ea1c | |
tzugen | ad81f3bf6d | |
tzugen | 594e94eea7 | |
tzugen | f9aac1ca43 | |
tzugen | b8eddb2d24 | |
tzugen | e0df24182e | |
tzugen | 029f0fa4da | |
tzugen | 662cb1728b | |
tzugen | e8baea6195 | |
tzugen | e00137a635 | |
tzugen | d83cf0917c | |
Maxence G (Hello-Fluffy) | 0a2e7358fa | |
Maxence G (Hello-Fluffy) | 7eb3d01ae8 | |
Maxence G (Hello-Fluffy) | a23e5ff794 | |
tzugen | 6aa3124ee9 | |
dependabot[bot] | 413707e482 | |
tzugen | e753d95ed3 | |
tzugen | 0579bd9754 | |
Nite | bb1451b800 | |
dependabot[bot] | 01be4c7148 | |
Nite | cb51c0b723 | |
tzugen | 0a39891b93 | |
tzugen | 7e82efddad | |
Óscar García Amor | 6202484df7 | |
Óscar García Amor | 4a10b76067 | |
dependabot[bot] | 9ea0246032 | |
Nite | bd34345c12 | |
Nite | 2655a4a606 | |
tzugen | 059f6d8f32 | |
tzugen | 27971cb426 | |
tzugen | a051d4d040 | |
tzugen | 8b15c9a57f | |
tzugen | 54f39be7ca | |
tzugen | 3afb86c22c | |
dependabot[bot] | d01030b61d | |
Nite | fc2e4b7ae4 | |
Nite | 6e6cf3a86a | |
dependabot[bot] | 43a806a699 | |
Nite | c64420c11f | |
dependabot[bot] | adca273d3f | |
Nite | 2703a4f35b | |
Nite | 9546bdeab5 | |
James Wells | 961c726da8 | |
Nite | c9f2050c46 | |
Nite | 982639d2c7 | |
Nite | cf05d3c781 | |
Nite | f50d6f13f4 | |
Nite | 635ea2f55e | |
Nite | 51dbdfb39a | |
Nite | 56af9e4bf2 | |
Nite | 83c6b76d0a | |
dependabot[bot] | ba323fa9ac | |
Nite | ea3b055288 | |
dependabot[bot] | 6558f871ca | |
Nite | ee76c661a1 | |
dependabot[bot] | 808eafc85d | |
Nite | 649a702ebb | |
dependabot[bot] | 090d7f8ab7 | |
Nite | 1938b3bb4b | |
dependabot[bot] | e60ac32c3f | |
Nite | 6791f0631a | |
James Wells | db0669098c | |
James Wells | 04de4544ee | |
dependabot[bot] | 62189dce5c | |
tzugen | 69c9739db0 | |
tzugen | b546f2c2fb | |
tzugen | fe9b2f9700 | |
tzugen | dbdb59bbff | |
tzugen | fa94cd24da | |
tzugen | c99c4478f2 | |
tzugen | 67f2d1f9a8 | |
dependabot[bot] | f15891cbdd | |
Óscar García Amor | bc8a78e772 | |
Óscar García Amor | 68acf3789c | |
Óscar García Amor | 2a6c9baa3d | |
tzugen | e3e8d36f5c | |
tzugen | 2da4f79098 | |
James Wells | 4ff167e497 | |
tzugen | d916e937f9 | |
tzugen | d4c0f62b1d | |
tzugen | 6fca6d918c | |
tzugen | 1132ec6c87 | |
tzugen | d6594b8ec4 | |
tzugen | bc16b64be4 | |
tzugen | 16c6b6b03e | |
tzugen | 36dccc845b | |
tzugen | 1ecb577c50 | |
tzugen | 8c2896ea16 | |
tzugen | d8b032e2e3 | |
tzugen | 30bbeb7594 | |
tzugen | 28336c8bf7 | |
tzugen | 5cfb49ac69 | |
tzugen | ffe67e6c50 | |
tzugen | 38f1f5f5c6 | |
tzugen | 956d555b35 | |
James Wells | 793c4a6ca7 | |
James Wells | 3853fce818 | |
Maxence G | d440b8c76d | |
Nite | 2d4c773344 | |
tzugen | 57d740af12 | |
tzugen | 6a370696cd | |
tzugen | bc295a825b | |
tzugen | 914f84a4c8 | |
Nite | a02b620531 | |
tzugen | 3b5b8feb56 | |
tzugen | 58200660d3 | |
tzugen | f7dfdcadad | |
tzugen | a43a525bab | |
tzugen | db31fefe83 | |
tzugen | 172cbd0476 | |
tzugen | a2324eabf2 | |
tzugen | 6ea4ac5829 | |
tzugen | 5e4c6cc627 | |
James Wells | 8855d19113 | |
tzugen | 24ae0d9e81 | |
tzugen | 619f441790 | |
tzugen | 6ab0ff973a | |
tzugen | 620239f859 | |
tzugen | a60a843edf | |
tzugen | d9a7fa2413 | |
tzugen | dd1f55a927 | |
tzugen | 175f7cd6f6 | |
tzugen | 27f3d7c00c | |
dependabot[bot] | 0c839a4cab | |
tzugen | 580a170f4b | |
tzugen | 4eeff191aa | |
tzugen | be356d9c0a | |
tzugen | 3c554caf2e | |
Nite | 78cb4d09cf | |
Nite | fa6566e903 | |
Nite | 7182694c5d | |
tzugen | 6dff5c5a23 | |
tzugen | 566e429e4c | |
tzugen | 9161f9dc99 | |
tzugen | f8efb6d592 | |
tzugen | 92fe7359ff | |
tzugen | 41eb217d8f | |
tzugen | 0989ee20a6 | |
tzugen | 2eaa9a2091 | |
tzugen | 90ecbe4b78 | |
James Wells | e95b2ce09c | |
James Wells | be4ffc2c7e | |
Óscar García Amor | 1dc480b141 | |
Óscar García Amor | e0f9059de8 | |
Nite | 0a886d7095 | |
Maxence G | 17b9b776fb | |
Nite | 132ee3b563 | |
Nite | fe5ee8b12a | |
Nite | 41023282e8 | |
Maxence G | 67870efd67 | |
Maxence G | 3e08735374 | |
Maxence G | 075b80c01f | |
Maxence G | d8e7b991cd | |
Maxence G | fd48367cab | |
tzugen | 7d15f67d2d | |
dependabot[bot] | 2050e21c9c | |
tzugen | ddc0d71538 | |
James Wells | e666498f13 | |
tzugen | 8aa1cf1e2c | |
tzugen | 71068fe635 | |
tzugen | f9401ba1fa | |
tzugen | d8500e45e4 | |
dependabot[bot] | e1d3e61c74 | |
dependabot[bot] | b5801f488f | |
Nite | bf013c02d6 | |
tzugen | ee9c478bfe | |
tzugen | e21ae1299b | |
tzugen | 6dc0eb7ecc | |
tzugen | cc7e273d50 | |
tzugen | ac77d9557c | |
tzugen | 154662bec5 | |
tzugen | aa1c0d8baa | |
tzugen | 38c1480f77 | |
tzugen | 8567fc0105 | |
tzugen | e059d737bc | |
tzugen | b30584f99c | |
tzugen | 3257fb9153 | |
tzugen | 5ac36b749b | |
tzugen | 3f2daaa7ec | |
tzugen | e55d894f45 | |
tzugen | f510638571 | |
tzugen | 31850bf270 | |
Maxence G | 3c4a5064b0 | |
Maxence G | 070e0ac792 | |
Maxence G | 1b7f48c53a | |
tzugen | cebbb39caf | |
dependabot[bot] | faee926c35 | |
Nite | 04b4b154e4 | |
tzugen | 2db7d2daf1 | |
tzugen | 79fd1ac84f | |
tzugen | 0500bd5e88 | |
tzugen | 005c1cb8f7 | |
tzugen | 3d99269c41 | |
tzugen | ed6c6c9057 | |
tzugen | b8fbbd8c49 | |
tzugen | 9b2e54b94e | |
tzugen | 00e64cdddc | |
tzugen | 1b5db9da1f | |
tzugen | df047dd463 | |
tzugen | a801e276ee | |
tzugen | 05cb82921d | |
tzugen | f596dc77c1 | |
tzugen | 7922d6caad | |
tzugen | b25a118148 | |
tzugen | 402fe961ba | |
Nite | 6943dc5895 | |
tzugen | f1713fdbd3 | |
tzugen | b0b03c0fe0 | |
tzugen | ab4c2c1bbe | |
tzugen | 29a98c1263 | |
tzugen | 78e4a00476 | |
tzugen | 387119a90c | |
tzugen | d26f8595ab | |
tzugen | dff054847a | |
tzugen | 10edce9f56 | |
tzugen | 3a05f30907 | |
tzugen | b559f6420e | |
tzugen | 689b704bc5 | |
tzugen | a25a2ff337 | |
tzugen | 8b01118233 | |
Nite | 950cb6254f | |
tzugen | 5f08e7e7cd | |
tzugen | 2cf80707f7 | |
tzugen | 72c03cc500 | |
tzugen | c6a744cc14 | |
tzugen | 1fc7a954e9 | |
tzugen | 13b00f75f0 | |
Nite | 1599c96085 | |
tzugen | fb1d305545 | |
tzugen | 8fbefe7066 | |
tzugen | b136235ccf | |
tzugen | 74ce93f37b | |
dependabot[bot] | 8fd66b2a1c | |
tzugen | 642c90974c | |
dependabot[bot] | a5ad39caba | |
tzugen | 403fead776 | |
dependabot[bot] | 7a012d3bce | |
Óscar García Amor | 426249f370 | |
Nite | 1df6181cc3 | |
tzugen | f897a1b459 | |
tzugen | b78dadc170 | |
dependabot[bot] | 330dade288 | |
Nite | 14941486a5 | |
tzugen | a60be2157b | |
Nite | aed5d9b629 | |
Nite | c7904fad84 | |
tzugen | de44d28257 | |
dependabot[bot] | 4617a210fa | |
dependabot[bot] | cb5cdbe878 | |
Nite | 09b5654f5a | |
dependabot[bot] | d399780d23 | |
Nite | e9cc2880f2 | |
tzugen | c66118172c | |
tzugen | 5b6fa20050 | |
tzugen | f2b1ba0e94 | |
tzugen | 1fa9e604b5 | |
tzugen | 76ba4c8ac9 | |
tzugen | 2c5f8d9dd0 | |
tzugen | 9ec92c6cf3 | |
Nite | 21282b4c17 | |
tzugen | 6c122b9e80 | |
tzugen | e0eb2aaf61 | |
tzugen | e8fe006b1a | |
dependabot[bot] | d46c556e6f | |
tzugen | e06400550b | |
dependabot[bot] | 19614e79c1 | |
Nite | 8d2f4b5bf1 | |
Nite | ec21e14e33 | |
Nite | 48ebe8939b | |
Nite | 0b2a62f391 | |
Nite | e37791e37c | |
dependabot[bot] | 1deefb6a22 | |
Nite | f0e285cc67 | |
Nite | 23c430e923 | |
Nite | e1b2ec29cf | |
Nite | dcd5cc8feb | |
Nite | 7dcc5bbf13 | |
Nite | afccbf3f65 | |
Óscar García Amor | 44c4b2eaf3 | |
Óscar García Amor | 3dc82ed2ee | |
Óscar García Amor | ed2de31fc7 | |
Nite | 70dbfff1e1 | |
tzugen | e4c13a305c | |
tzugen | 5187ac1f54 | |
tzugen | a6c914cf7f | |
tzugen | 9ca15deef2 | |
tzugen | 094c0d2757 | |
tzugen | 8a2d100e96 | |
tzugen | af304cd10f | |
tzugen | 75bcbd97b1 | |
dependabot[bot] | e6b77847b6 | |
tzugen | ddd8a54592 | |
tzugen | c8ecf4c123 | |
tzugen | bc2c591ff2 | |
tzugen | a910818b70 | |
tzugen | 85e2e473e5 | |
tzugen | 8ebd53a8eb | |
dependabot[bot] | 040dba6a77 | |
tzugen | 7b7f470667 | |
dependabot[bot] | 2659634bda | |
tzugen | bedbad658f | |
tzugen | 4d27c35cf4 | |
Nite | 162bb18078 | |
Nite | 3a2c41b879 | |
Nite | 036a7a3a8e | |
Nite | 457a908d72 | |
Nite | 8701facd03 | |
Nite | 115b8c3183 | |
Nite | b06384a15d | |
Nite | ce7e95f59e | |
Nite | 341412a9e8 | |
Nite | e9b22b827a | |
dependabot[bot] | 17533bded1 | |
dependabot[bot] | 2432ca7d1e | |
dependabot[bot] | 0ec94d456f | |
dependabot[bot] | e4af2b603f | |
Nite | 88db9be878 | |
Nite | 5c95d9c45a | |
Nite | 83b186b9b5 | |
Nite | e75137372b | |
tzugen | a3b97fef47 | |
tzugen | 80036034e4 | |
tzugen | 06c076470a | |
tzugen | dc6cfd141b | |
Nite | fb74d6465e | |
Nite | fe332eed91 | |
tzugen | 66a306152d | |
tzugen | f6a8ee7241 | |
tzugen | adc350b5c4 | |
tzugen | 98ba9ecd3e | |
tzugen | 4af0d1db25 | |
tzugen | d385cf271e | |
tzugen | ce2f5a95d9 | |
tzugen | 237f7ab181 | |
tzugen | fe69141e19 | |
tzugen | 25f072a5d5 | |
tzugen | dca26f14eb | |
tzugen | 1802e91fa7 | |
tzugen | fa4f4d6c9c | |
tzugen | 1f57fb334b | |
Óscar García Amor | 1dd43d857d | |
Óscar García Amor | 1c58062496 | |
Óscar García Amor | f9dcc62ed7 | |
tzugen | 13f9dd9b33 | |
Óscar García Amor | 2d4b295f7f | |
Nite | 119cc4589e | |
tzugen | 822b3551d9 | |
Nite | 817cc14ed9 | |
tzugen | 4f70c61592 | |
tzugen | 3e8cf63603 | |
Nite | ae22a0c141 | |
James Wells | d084270c4b | |
tzugen | 0a170918c8 | |
tzugen | cd27734c04 | |
tzugen | 8004bc31fe | |
tzugen | 410e1df980 | |
tzugen | 0ad6d0b691 | |
tzugen | 3aae91bf13 | |
tzugen | 35e89b47c6 | |
tzugen | 9472aa4c22 | |
tzugen | df54d3450b | |
tzugen | e6135dc21a | |
tzugen | 205f477b43 | |
tzugen | 2adb9ffc7e | |
tzugen | f27212f263 | |
tzugen | e63f24242b | |
tzugen | 5f8e3ce851 | |
tzugen | 8640d39438 | |
tzugen | 37632dd031 | |
tzugen | 0550116b35 | |
tzugen | 8bfc5d04ef | |
tzugen | 88f6bdb3a9 | |
tzugen | e4bf431cfe | |
tzugen | d3b94f3d4c | |
tzugen | 1ee36322db | |
tzugen | 116307df56 | |
tzugen | 731447fda5 | |
tzugen | 19580cda8b | |
tzugen | 26ba022003 | |
tzugen | 417496aacc | |
Nite | 2e4dde099d | |
tzugen | 6c681ddb52 | |
Nite | d41bd8bc07 | |
tzugen | c59a38777f | |
Nite | 3aee0d5693 | |
tzugen | d0ad8cef7d | |
tzugen | 9cb2ed3356 | |
tzugen | f1ce568b5e | |
tzugen | b5806cbbbf | |
tzugen | 42967ef45f | |
Nite | 4f7da06e26 | |
tzugen | 467df602ec | |
tzugen | 4787924fcc | |
tzugen | 8d4d1ba660 | |
tzugen | 0efb3547c3 | |
tzugen | d9d82b77d4 | |
tzugen | a97cb30d1c | |
tzugen | aac74d1eef | |
tzugen | cf68038e20 | |
tzugen | 3139c94d11 | |
tzugen | a13306c680 | |
tzugen | 6b750dac4d | |
tzugen | 0bf0d2db87 | |
Nite | c16d6b5181 | |
tzugen | b438559da6 | |
Nite | fbcbc65be8 | |
Nite | ef774cf0bf | |
James Wells | e5158c8a2f | |
James Wells | 39c94fe1e9 | |
Nite | 9e16b69fc3 | |
James Wells | 38b6066258 | |
James Wells | 3a1e2c96fc | |
tzugen | 8fbc2a9fa3 | |
tzugen | 0349746228 | |
tzugen | 86db983f81 | |
tzugen | 46a56a1797 | |
James Wells | 268a41d1f4 | |
James Wells | f00a487b51 | |
James Wells | 580fc1aa89 | |
James Wells | 2672ea7edf | |
Nite | 2650b51a6c | |
James Wells | 3e54e4cf05 | |
James Wells | 191f9512bb | |
tzugen | f738bca1ad | |
tzugen | 05067aaf3c | |
Nite | 97d68eb08f | |
Nite | fa5df96ee3 | |
tzugen | 974afda777 | |
tzugen | 970d93bd91 | |
tzugen | b64a4d1320 | |
tzugen | bf106b0384 | |
tzugen | 51dafd542a | |
tzugen | 8e7cf487fd | |
tzugen | 8d65b1d25f | |
tzugen | 2171c971a3 | |
tzugen | 60a0fe17dd | |
tzugen | 493a587b37 | |
tzugen | 93eced9516 | |
tzugen | 09fb6aa487 | |
tzugen | d017ca9fb2 | |
tzugen | a467abf10b | |
tzugen | 2260cc311f | |
Nite | 031c969730 | |
Nite | d1af2d568e | |
Nite | ef29f26893 | |
Nite | 6a53644355 | |
Óscar García Amor | 4747aa3ce4 | |
Óscar García Amor | f361f584b9 | |
Óscar García Amor | 4cda3730f1 | |
James Wells | f0be828bbf | |
Nite | 63572d84ea | |
Nite | a9ea033802 | |
Óscar García Amor | 88cd866e03 | |
Óscar García Amor | 0828f7bffe | |
Óscar García Amor | 0bf0c1cd21 | |
Óscar García Amor | 670a8c7886 | |
Óscar García Amor | 101d02560f | |
Óscar García Amor | 5a3b52b7fe | |
Óscar García Amor | 51ecfeb43f | |
Nite | b235acf521 | |
Nite | 2eb357a50f | |
Nite | 46859e2413 | |
Nite | 9910792c11 | |
Nite | 8a047c5b78 | |
Nite | 6fcdfea1db | |
Nite | 09c8c56f59 | |
Nite | 81ce23bafd | |
Nite | 785adedd4f | |
Nite | 86bfcefe93 | |
Nite | d70d2cc2fb | |
Nite | 3dc6df9116 | |
Nite | c4ea2086d2 | |
Nite | 1b9b127424 | |
Nite | cf90abb77e | |
Nite | f0917820cb | |
Nite | a395bd6feb | |
Nite | 95773c7994 | |
Óscar García Amor | c3899af521 | |
Óscar García Amor | d01266804c | |
Óscar García Amor | 9908f76555 | |
Óscar García Amor | 6dbe9fb2cc | |
Óscar García Amor | 3c0485c98b | |
Óscar García Amor | bda1deae2b | |
Óscar García Amor | 4fdab06271 | |
Óscar García Amor | 193fa38dd0 | |
Nite | 8ac0b28156 | |
Nite | 90090ba870 | |
Óscar García Amor | 3c47ea890f | |
Nite | 3749f30691 | |
Nite | 20de65d1fd | |
Nite | b7bb0851db | |
Óscar García Amor | 7d779315e5 | |
Óscar García Amor | 3c9dcbc0a3 | |
Óscar García Amor | 5839c804b3 | |
lbonn | f43c107269 | |
lbonn | 7af666037d | |
Óscar García Amor | 0ce5a56051 | |
Óscar García Amor | 70f86441d1 | |
Nite | 41f9b4ad73 | |
Nite | 543cb21cd1 | |
Óscar García Amor | d57248d160 | |
Nite | e446785d0e | |
Nite | fe120a0bf2 | |
Óscar García Amor | 51cdbe7084 | |
Óscar García Amor | 74b484fc1d | |
Óscar García Amor | 3afecf5fea | |
Óscar García Amor | 5796e25923 | |
Óscar García Amor | b471b9cad0 | |
Nite | d98360d3fe | |
Nite | 7068477e23 | |
Nite | c10d08b862 | |
Nite | 4336e8e243 | |
Nite | a3c4815a07 | |
Nite | b3db46b1ab | |
Nite | 7dcaf5b1a6 | |
Nite | 47e5675d1e | |
Nite | 34a6413f10 | |
Óscar García Amor | 6aa29d54fe | |
Nite | 74d0c678df | |
Nite | 92f01fec26 | |
Óscar García Amor | a426b897e3 | |
Óscar García Amor | be3f9c4b15 | |
Óscar García Amor | 7813c0373d | |
Óscar García Amor | 3fca8bd7a5 | |
Óscar García Amor | fb2dab0a25 | |
Óscar García Amor | 8d45a3d106 | |
Óscar García Amor | 3f6c45f25f | |
Nite | d98f64ed25 | |
Nite | 19f8196268 | |
Nite | 8ebda198c6 | |
Nite | 6e1d0e5b2e | |
Nite | df53da2439 | |
Nite | 537478430c | |
Óscar García Amor | 645f524ce5 | |
Óscar García Amor | 440b2109d3 | |
Óscar García Amor | cf19736049 | |
Óscar García Amor | 5c637a2e9c | |
Nite | 5c81097df8 | |
Nite | bd5440bb96 | |
Nite | 9341fd3df3 | |
Nite | 9eeff3fabc | |
Nite | 30a5abaa34 | |
Nite | 0482c540bd | |
Nite | 273bee1341 | |
Nite | eb3ce0db8a | |
Nite | 4e6df12f4e | |
Nite | a396b4b27b | |
Óscar García Amor | 356af198e0 | |
Óscar García Amor | 0c50995ad2 | |
Nite | fe591ce0c6 | |
Óscar García Amor | 768e8e6fb5 | |
Óscar García Amor | 3eeb9618f2 | |
Nite | 0b21dfdb2d | |
Nite | 778ee380e8 | |
Nite | 1f7167e016 | |
Nite | b16dde0b07 | |
Nite | 60a8599a02 | |
Óscar García Amor | 25854ab808 | |
Max Pop | d4ecc5825d | |
Max Pop | c565dfef24 | |
Nite | 8fa7fdd18b | |
Óscar García Amor | dcd27919a2 | |
Max Pop | 675a32a3ef | |
Max Pop | cd4b1239fc | |
Max Pop | 6ae3c9692a | |
Óscar García Amor | 57a89c4a77 | |
Nite | e15fa23def | |
Nite | 6e7ebeabef | |
Nite | 5de20861ca | |
Nite | 7217f2cca8 | |
Nite | 60b6a5dbde | |
Nite | 6355a7ed80 | |
Nite | f6e61b5484 | |
Nite | df3ef9c04e | |
Nite | 75a9c60889 | |
Óscar García Amor | 7a6aa950ac | |
Nite | c805005b02 | |
Nite | 046916221d | |
Nite | 9f8641388b | |
Nite | d189026c18 | |
Óscar García Amor | 81448e34d6 | |
Nite | ff97c0138c | |
Nite | 4b85bc16d5 | |
Nite | 4991eb8d13 | |
Óscar García Amor | bd3e4df805 | |
Óscar García Amor | 8c07346296 | |
Óscar García Amor | 2a7ea19a0f | |
Óscar García Amor | 5387b58e35 | |
Óscar García Amor | 89ef73ccec | |
Óscar García Amor | 81a0ff2967 | |
Nite | 333b147c02 | |
Nite | ef09807e3e | |
Nite | 7bbc3234f2 | |
Nite | db4f8b83d3 | |
Óscar García Amor | 81f248f446 | |
Nite | 02716c827a | |
Nite | 102f3e8a04 | |
Nite | 5b0c9906f1 | |
Nite | 59c0054a11 | |
Nite | 481084686b | |
Óscar García Amor | 4b36730358 | |
Óscar García Amor | a411ed63e2 | |
Óscar García Amor | a7d29a4093 | |
Óscar García Amor | f0f9e3d438 | |
Óscar García Amor | 36e25253be | |
Óscar García Amor | c6ed320e12 | |
Nite | 31853cc8bc | |
Nite | b7c21e4aac | |
Nite | 55105e52b6 | |
Nite | 06109424ec | |
Nite | bed29ce33b | |
Nite | cc954e4d5a | |
Nite | e8310b2ac8 | |
Óscar García Amor | 1f06860dcf | |
Max Pop | 251e575421 | |
Nite | 234e4703a1 | |
Nite | 6721500202 | |
Óscar García Amor | 4d8e7f0631 | |
Óscar García Amor | b04f309b9a | |
Óscar García Amor | 387457cf99 | |
Óscar García Amor | 0f7238e961 | |
Óscar García Amor | f9199eebd3 |
|
@ -1,54 +1,68 @@
|
|||
version: 2
|
||||
version: 2.1
|
||||
parameters:
|
||||
memory-config:
|
||||
type: string
|
||||
default: "-Xmx3200m -Xms256m -XX:MaxMetaspaceSize=1g"
|
||||
memory-config-debug:
|
||||
type: string
|
||||
default: "-Xmx3200m -Xms256m -XX:MaxMetaspaceSize=1g -verbose:gc -Xlog:gc*"
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/android:api-28
|
||||
- image: cimg/android:2022.06.1
|
||||
working_directory: ~/ultrasonic
|
||||
envoronment:
|
||||
JVM_OPTS: -Xmx3200m
|
||||
environment:
|
||||
JVM_OPTS: << pipeline.parameters.memory-config >>
|
||||
JAVA_TOOL_OPTIONS: << pipeline.parameters.memory-config >>
|
||||
GRADLE_OPTS: << pipeline.parameters.memory-config >>
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: gradle-cache-{{ checksum "dependencies.gradle" }}
|
||||
keys:
|
||||
- v2-ultrasonic-{{ .Branch }}-{{ checksum "gradle/libs.versions.toml" }}
|
||||
- v2-ultrasonic-{{ .Branch }}
|
||||
- v2-ultrasonic
|
||||
- run:
|
||||
name: clean gradle.properties
|
||||
command: echo -e "android.useAndroidX=true\nandroid.enableJetifier=true\n" > gradle.properties
|
||||
name: configure gradle.properties for CI building
|
||||
command: |
|
||||
sed -i '/^org.gradle.jvmargs/d' gradle.properties
|
||||
sed -i 's/^org.gradle.daemon=true/org.gradle.daemon=false/g' gradle.properties
|
||||
cat gradle.properties
|
||||
- run:
|
||||
name: checkstyle
|
||||
command: ./gradlew -Pqc ktlintCheck
|
||||
- run:
|
||||
name: static analysis
|
||||
command: ./gradlew -Pqc detektCheck
|
||||
command: ./gradlew -Pqc detekt
|
||||
- run:
|
||||
name: build
|
||||
name: build debug
|
||||
command: ./gradlew assembleDebug
|
||||
- run:
|
||||
name: unit-tests
|
||||
command: |
|
||||
./gradlew ciTest testDebugUnitTest
|
||||
./gradlew jacocoFullReport
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
- run:
|
||||
name: lint
|
||||
command: ./gradlew :ultrasonic:lintRelease
|
||||
- run:
|
||||
name: assemble release build
|
||||
name: build
|
||||
command: ./gradlew buildRelease
|
||||
- run:
|
||||
name: assemble release
|
||||
command: ./gradlew assembleRelease
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.gradle
|
||||
key: gradle-cache-{{ checksum "dependencies.gradle" }}
|
||||
key: v2-ultrasonic-{{ .Branch }}-{{ checksum "gradle/libs.versions.toml" }}
|
||||
- store_artifacts:
|
||||
path: ultrasonic/build/reports
|
||||
destination: reports
|
||||
- store_artifacts:
|
||||
path: subsonic-api/build/reports
|
||||
destination: reports
|
||||
- store_artifacts:
|
||||
path: build/reports/jacoco/jacocoFullReport/
|
||||
push_translations:
|
||||
docker:
|
||||
- image: circleci/python:3.6
|
||||
- image: cimg/python:3.6
|
||||
working_directory: ~/ultrasonic
|
||||
steps:
|
||||
- checkout
|
||||
|
@ -65,17 +79,22 @@ jobs:
|
|||
name: push changes in translation files
|
||||
command: |
|
||||
. ~/venv/bin/activate
|
||||
tx push -st
|
||||
tx push -s
|
||||
generate_signed_apk:
|
||||
docker:
|
||||
- image: circleci/android:api-28
|
||||
- image: cimg/android:2022.06.1
|
||||
working_directory: ~/ultrasonic
|
||||
envoronment:
|
||||
JAVA_TOOL_OPTIONS: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap"
|
||||
environment:
|
||||
JVM_OPTS: << pipeline.parameters.memory-config >>
|
||||
JAVA_TOOL_OPTIONS: << pipeline.parameters.memory-config >>
|
||||
GRADLE_OPTS: << pipeline.parameters.memory-config >>
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: gradle-cache-{{ checksum "dependencies.gradle" }}
|
||||
keys:
|
||||
- v2-ultrasonic-{{ .Branch }}-{{ checksum "gradle/libs.versions.toml" }}
|
||||
- v2-ultrasonic-{{ .Branch }}
|
||||
- v2-ultrasonic
|
||||
- run:
|
||||
name: decrypt ultrasonic-keystore
|
||||
command: openssl aes-256-cbc -K ${ULTRASONIC_KEYSTORE_KEY} -iv ${ULTRASONIC_KEYSTORE_IV} -in ultrasonic-keystore.enc -out ultrasonic-keystore -d
|
||||
|
@ -85,23 +104,24 @@ jobs:
|
|||
- run:
|
||||
name: sign release apk
|
||||
command: |
|
||||
export PATH="${JAVA_HOME}/bin:${PATH}"
|
||||
mkdir -p /tmp/ultrasonic-release
|
||||
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ~/ultrasonic/ultrasonic-keystore -storepass ${ULTRASONIC_KEYSTORE_STOREPASS} -keypass ${ULTRASONIC_KEYSTORE_KEYPASS} ultrasonic/build/outputs/apk/release/ultrasonic-release-unsigned.apk ultrasonic
|
||||
jarsigner -verify ultrasonic/build/outputs/apk/release/ultrasonic-release-unsigned.apk
|
||||
${ANDROID_HOME}/build-tools/27.0.0/zipalign -v 4 ultrasonic/build/outputs/apk/release/ultrasonic-release-unsigned.apk /tmp/ultrasonic-release/ultrasonic-${CIRCLE_TAG}.apk
|
||||
${ANDROID_HOME}/build-tools/32.0.0/zipalign -v 4 ultrasonic/build/outputs/apk/release/ultrasonic-release-unsigned.apk /tmp/ultrasonic-release/ultrasonic-${CIRCLE_TAG}.apk
|
||||
${ANDROID_HOME}/build-tools/32.0.0/apksigner sign --verbose --ks ~/ultrasonic/ultrasonic-keystore --ks-pass pass:${ULTRASONIC_KEYSTORE_STOREPASS} --key-pass pass:${ULTRASONIC_KEYSTORE_KEYPASS} /tmp/ultrasonic-release/ultrasonic-${CIRCLE_TAG}.apk
|
||||
${ANDROID_HOME}/build-tools/32.0.0/apksigner verify --verbose /tmp/ultrasonic-release/ultrasonic-${CIRCLE_TAG}.apk
|
||||
- persist_to_workspace:
|
||||
root: /tmp/ultrasonic-release
|
||||
paths:
|
||||
- ultrasonic-*.apk
|
||||
- ultrasonic-*.apk*
|
||||
publish_github_signed_apk:
|
||||
docker:
|
||||
- image: circleci/golang
|
||||
- image: cimg/go:1.18
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/ultrasonic-release
|
||||
- run:
|
||||
name: install ghr
|
||||
command: go get -v github.com/tcnksm/ghr
|
||||
command: go install -v github.com/tcnksm/ghr@latest
|
||||
- run:
|
||||
name: publish release on github tag
|
||||
command: ghr -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} ${CIRCLE_TAG} /tmp/ultrasonic-release
|
||||
|
@ -117,11 +137,10 @@ workflows:
|
|||
branches:
|
||||
only:
|
||||
- develop
|
||||
- master
|
||||
- generate_signed_apk:
|
||||
filters:
|
||||
tags:
|
||||
only: /^[0-9]+(\.[0-9]+)*/
|
||||
only: /^[0-9]+(\.[0-9]+)*(-beta\.[0-9]+)?/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
- publish_github_signed_apk:
|
||||
|
@ -129,7 +148,7 @@ workflows:
|
|||
- generate_signed_apk
|
||||
filters:
|
||||
tags:
|
||||
only: /^[0-9]+(\.[0-9]+)*/
|
||||
only: /^[0-9]+(\.[0-9]+)*(-beta\.[0-9]+)?/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gradle" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
ignore:
|
||||
- dependency-name: "*"
|
||||
update-types: ["version-update:semver-patch"]
|
||||
|
|
@ -39,6 +39,7 @@ captures/
|
|||
*.iml
|
||||
.idea/
|
||||
|
||||
|
||||
# Keystore files
|
||||
*.jks
|
||||
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value />
|
||||
</option>
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,6 @@
|
|||
<component name="CopyrightManager">
|
||||
<copyright>
|
||||
<option name="notice" value="&#36;file.fileName Copyright (C) 2009-&#36;today.year Ultrasonic developers Distributed under terms of the GNU GPLv3 license." />
|
||||
<option name="myName" value="Default" />
|
||||
</copyright>
|
||||
</component>
|
|
@ -0,0 +1,7 @@
|
|||
<component name="CopyrightManager">
|
||||
<settings default="Default">
|
||||
<LanguageOptions name="Kotlin">
|
||||
<option name="fileTypeOverride" value="3" />
|
||||
</LanguageOptions>
|
||||
</settings>
|
||||
</component>
|
|
@ -0,0 +1,8 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Reformat" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="processChangedFilesOnly" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK" />
|
||||
</project>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -18,18 +18,46 @@ By default Pull Request should be opened against **develop** branch, PR against
|
|||
### Here are a few guidelines you should follow before submitting:
|
||||
|
||||
1. **License Acceptance:** All contributions must be licensed as [GNU GPLv3](LICENSE) to be accepted.
|
||||
Use `git commit --signoff` to acknowledge this.
|
||||
2. **App is migrating to [Kotlin](https://kotlinlang.org/) programming language:** new Pull Requests
|
||||
should be written in this programming language.
|
||||
3. **No Breakage:** New features or changes to existing ones must not degrade the user experience.
|
||||
4. **Coding standards:** best-practices should be followed, comment generously, and avoid "clever" algorithms.
|
||||
Use `git commit --signoff` to acknowledge this.
|
||||
2. **No Breakage:** New features or changes to existing ones must not degrade the user experience.
|
||||
3. **Coding standards:** best-practices should be followed, comment generously, and avoid "clever" algorithms.
|
||||
Refactoring existing messes is great, but watch out for breakage.
|
||||
5. **No large PR:** Try to limit the scope of PR only to the related issue, so it will be easier to review
|
||||
4. **No large PR:** Try to limit the scope of PR only to the related issue, so it will be easier to review
|
||||
and test.
|
||||
|
||||
### Pull Request Process
|
||||
On each Pull Request Github runs a number of checks to make sure there are no problems.
|
||||
|
||||
#### Signed commits
|
||||
Commits must be signed. [See here how to set it up](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits)
|
||||
|
||||
#### KtLint
|
||||
This programm checks if the source code is formatted correctly.
|
||||
You can run it yourself locally with
|
||||
|
||||
`./gradlew -Pqc ktlintFormat`
|
||||
|
||||
Running this command will fix common problems and will notify you of problems it couldn't fix automatically.
|
||||
|
||||
#### Detekt
|
||||
|
||||
Detekt is a static analyser. It helps to find potential bugs in our code.
|
||||
|
||||
You can run it yourself locally with
|
||||
|
||||
`./gradlew -Pqc detekt`
|
||||
|
||||
There is a "baseline" file, in which errors which have been in the code base before are noted.
|
||||
Sometimes it is necessary to regenerate this file by running:
|
||||
|
||||
`./gradlew -Pqc detektBaseline`
|
||||
|
||||
#### Lint
|
||||
Lint looks for general problems in the code or unused resources etc.
|
||||
You can run it with
|
||||
|
||||
`./gradlew -Pqc lintRelease`
|
||||
|
||||
If there is a need to regenerate the baseline, remove `ultrasonic/lint-baseline.xml` and rerun the command.
|
||||
|
||||
|
||||
1. Ensure all commits are signed-off.
|
||||
2. Check tests for the new code are added.
|
||||
3. Check code style is passing.
|
||||
4. Check code static analysis is passing.
|
||||
|
|
|
@ -1,20 +1,28 @@
|
|||
## Problem description
|
||||
|
||||
Describe your problem here. Describe what you want to happen, and what happens
|
||||
if you try to do it. If you have a stack trace or any logs, please format them using
|
||||
github triple backquote notation
|
||||
Describe your problem here. Describe what you want to happen, and what
|
||||
happens if you try to do it. If you have a stack trace or any logs, please
|
||||
format them using GitHub triple backquote notation.
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
Describe how somebody else could observe the same behavior you do. Don't share here any logins and
|
||||
passwords!
|
||||
Describe how somebody else could observe the same behavior you do. Don't
|
||||
share here any logins and passwords!
|
||||
|
||||
## System information
|
||||
|
||||
### Ultrasonic client
|
||||
|
||||
* **Ultrasonic version**: *version of the app*
|
||||
* **Android version**: *Version of Android OS on the device*
|
||||
* **Device info**: *Device manufacturer, model*
|
||||
|
||||
### Server
|
||||
|
||||
* **Server name**: *Airsonic, Ampache, Supysonic...*
|
||||
* **Server version**: *version of server software*
|
||||
* **Protocol used**: *http or https (self certificate, letsencrypt...)*
|
||||
|
||||
## Additional notes
|
||||
|
||||
Include any extra notes here. Otherwise you may remove this section.
|
||||
|
|
61
README.md
61
README.md
|
@ -1,14 +1,25 @@
|
|||
# Ultrasonic
|
||||
[![Build Status](https://circleci.com/gh/ultrasonic/ultrasonic/tree/develop.svg?style=shield&circle-token=:circle-token)](https://circleci.com/gh/ultrasonic)
|
||||
[![Codecov branch](https://img.shields.io/codecov/c/github/ultrasonic/ultrasonic/develop.svg)]()
|
||||
[![ktlint](https://img.shields.io/badge/code%20style-%E2%9D%A4-FF4081.svg)](https://ktlint.github.io/)
|
||||
# WE HAVE MOVED
|
||||
|
||||
Ultrasonic is free and open-source music streaming Android client for [Subsonic](http://www.subsonic.org/) [API](http://www.subsonic.org/pages/api.jsp) (version 1.7.0 or higher) compatible servers.
|
||||
Ultrasonic code is now hosted in [GitLab][ultrasonic].
|
||||
|
||||
- New Web: https://ultrasonic.gitlab.io
|
||||
- New Git: https://gitlab.com/ultrasonic/ultrasonic
|
||||
- New bugtracker: https://gitlab.com/ultrasonic/ultrasonic/-/issues
|
||||
- New releases: https://gitlab.com/ultrasonic/ultrasonic/-/packages
|
||||
|
||||
[ultrasonic]: https://gitlab.com/ultrasonic/ultrasonic
|
||||
|
||||
# Ultrasonic
|
||||
|
||||
Ultrasonic is free and open-source music streaming Android client for
|
||||
[Subsonic][subsonic] [API][subapi] (version 1.7.0 or higher) compatible
|
||||
servers.
|
||||
|
||||
## Help wanted
|
||||
|
||||
We currently don't have that much time to spend developing Subsonic, so any
|
||||
contributions or active developers are always welcomed.
|
||||
Have a look at [CONTRIBUTING](CONTRIBUTING.md) to get started.
|
||||
|
||||
## Download
|
||||
|
||||
|
@ -16,22 +27,26 @@ App is available to download at following stores:
|
|||
|
||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" alt="Get it on Google Play" height="70">](https://play.google.com/store/apps/details?id=org.moire.ultrasonic)
|
||||
[<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="70">](https://f-droid.org/packages/org.moire.ultrasonic/)
|
||||
[<img src="https://ultrasonic.github.io/assets/img/get-it-on-github.png" alt="Get it on GitHub" height="70">](https://github.com/ultrasonic/ultrasonic/releases)
|
||||
[<img src="https://ultrasonic.gitlab.io/assets/img/get-it-on-gitlab.png" alt="Get it on GitLab" height="70">](https://gitlab.com/ultrasonic/ultrasonic/-/releases)
|
||||
|
||||
**Warning**: All three versions (Google Play, F-Droid and the APKs) are not
|
||||
compatible (not signed by the same key)! You must uninstall one to install
|
||||
the other, which will delete all your data.
|
||||
|
||||
If you want to use the version downloaded from F-Droid or from GitLab with
|
||||
**Android Auto**, you must enable Unknown Sources as it is described in
|
||||
[this wiki page][wikiaa].
|
||||
|
||||
## Bugs and issues
|
||||
|
||||
First, see if your issue haven’t been yet reported [here](https://github.com/ultrasonic/ultrasonic/issues),
|
||||
otherwise open [a new issue](https://github.com/ultrasonic/ultrasonic/issues/new).
|
||||
First, see if your issue haven’t been yet reported [here][issues], otherwise
|
||||
open [a new issue][newissue].
|
||||
|
||||
### Known (not our) bugs
|
||||
|
||||
If you are using *Madsonic 5.1.X* several sections of Ultrasonic will not
|
||||
work. This is caused by bad implementation of Subsonic API by Madsonic. For
|
||||
more info about this you can read [this bug](https://github.com/ultrasonic/ultrasonic/issues/129).
|
||||
more info about this you can read [this bug][madbug].
|
||||
|
||||
## Contributing
|
||||
|
||||
|
@ -39,15 +54,29 @@ See [CONTRIBUTING](CONTRIBUTING.md).
|
|||
|
||||
## Supported (tested) Subsonic API implementations
|
||||
|
||||
- [Subsonic](http://www.subsonic.org/pages/index.jsp)
|
||||
- [Airsonic](https://github.com/airsonic/airsonic)
|
||||
- [Supysonic](https://github.com/spl0k/supysonic)
|
||||
- [Subsonic][subsonic]
|
||||
- [Airsonic-Advanced][airsonic]
|
||||
- [Supysonic][supysonic]
|
||||
- [Ampache][ampache]
|
||||
|
||||
Other *Subsonic API* implementations should work as well as long as they follow API
|
||||
[documentation](http://www.subsonic.org/pages/api.jsp).
|
||||
Other *Subsonic API* implementations should work as well as long as they
|
||||
follow API [documentation][subapi].
|
||||
|
||||
## License
|
||||
|
||||
This software is licensed under the terms of the GNU General Public License version 3 (GPLv3).
|
||||
This software is licensed under the terms of the GNU General Public License
|
||||
version 3 (GPLv3).
|
||||
|
||||
Full text of the license is available in the [LICENSE](LICENSE) file and [online](https://opensource.org/licenses/gpl-3.0.html).
|
||||
Full text of the license is available in the [LICENSE](LICENSE) file and
|
||||
[online][gpl3].
|
||||
|
||||
[wikiaa]: https://gitlab.com/ultrasonic/ultrasonic/-/wikis/Using-Ultrasonic-with-Android-Auto
|
||||
[issues]: https://gitlab.com/ultrasonic/ultrasonic/-/issues
|
||||
[newissue]: https://gitlab.com/ultrasonic/ultrasonic/-/issues/new
|
||||
[madbug]: https://gitlab.com/ultrasonic/ultrasonic/-/issues/129
|
||||
[subsonic]: http://www.subsonic.org/
|
||||
[subapi]: http://www.subsonic.org/pages/api.jsp
|
||||
[airsonic]: https://github.com/airsonic-advanced/airsonic-advanced
|
||||
[supysonic]: https://github.com/spl0k/supysonic
|
||||
[ampache]: https://ampache.org/
|
||||
[gpl3]: https://opensource.org/licenses/gpl-3.0.html
|
||||
|
|
29
build.gradle
29
build.gradle
|
@ -1,6 +1,6 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
apply from: 'dependencies.gradle'
|
||||
apply from: 'gradle/versions.gradle'
|
||||
|
||||
ext.bootstrap = [
|
||||
kotlinModule : "${project.rootDir}/gradle_scripts/kotlin-module-bootstrap.gradle",
|
||||
|
@ -8,16 +8,15 @@ buildscript {
|
|||
]
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url "https://plugins.gradle.org/m2/" }
|
||||
}
|
||||
dependencies {
|
||||
classpath gradlePlugins.androidTools
|
||||
classpath gradlePlugins.kotlin
|
||||
classpath gradlePlugins.ktlintGradle
|
||||
classpath gradlePlugins.detekt
|
||||
classpath gradlePlugins.jacoco
|
||||
classpath libs.gradle
|
||||
classpath libs.kotlin
|
||||
classpath libs.ktlintGradle
|
||||
classpath libs.detekt
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,20 +24,26 @@ allprojects {
|
|||
// Buildscript here is required by detekt
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
google()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
|
||||
// Set Kotlin JVM target to the same for all subprojects
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: 'gradle_scripts/jacoco.gradle'
|
||||
|
||||
wrapper {
|
||||
gradleVersion(versions.gradle)
|
||||
gradleVersion(libs.versions.gradle.get())
|
||||
distributionType("all")
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
apply from: bootstrap.kotlinModule
|
||||
|
||||
dependencies {
|
||||
api project(':core:domain')
|
||||
api other.twitterSerial
|
||||
|
||||
testImplementation testing.kotlinJunit
|
||||
testImplementation testing.mockito
|
||||
testImplementation testing.mockitoInline
|
||||
testImplementation testing.mockitoKotlin
|
||||
testImplementation testing.kluent
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package org.moire.ultrasonic.cache
|
||||
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Provides access to generic directories:
|
||||
* - for temporary caches
|
||||
* - for permanent data storage
|
||||
*/
|
||||
interface Directories {
|
||||
fun getInternalCacheDir(): File
|
||||
fun getInternalDataDir(): File
|
||||
fun getExternalCacheDir(): File?
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
package org.moire.ultrasonic.cache
|
||||
|
||||
import com.twitter.serial.serializer.SerializationContext
|
||||
import com.twitter.serial.serializer.Serializer
|
||||
import com.twitter.serial.stream.Serial
|
||||
import com.twitter.serial.stream.bytebuffer.ByteBufferSerial
|
||||
import java.io.File
|
||||
|
||||
typealias DomainEntitySerializer<T> = Serializer<T>
|
||||
|
||||
internal const val STORAGE_DIR_NAME = "persistent_storage"
|
||||
|
||||
/**
|
||||
* Provides access to permanent file based storage.
|
||||
*
|
||||
* [serverId] is currently active server. Should be unique per server so stored data will not
|
||||
* interfere with other server data.
|
||||
*
|
||||
* Look at [org.moire.ultrasonic.cache.serializers] package for available [DomainEntitySerializer]s.
|
||||
*/
|
||||
class PermanentFileStorage(
|
||||
private val directories: Directories,
|
||||
private val serverId: String,
|
||||
private val debug: Boolean = false
|
||||
) {
|
||||
private val serializationContext = object : SerializationContext {
|
||||
override fun isDebug(): Boolean = debug
|
||||
override fun isRelease(): Boolean = !debug
|
||||
}
|
||||
|
||||
private val serializer: Serial = ByteBufferSerial(serializationContext)
|
||||
|
||||
/**
|
||||
* Stores given [objectToStore] using [name] as a key and [objectSerializer] as serializer.
|
||||
*/
|
||||
fun <T> store(
|
||||
name: String,
|
||||
objectToStore: T,
|
||||
objectSerializer: DomainEntitySerializer<T>
|
||||
) {
|
||||
val storeFile = getFile(name)
|
||||
if (!storeFile.exists()) storeFile.createNewFile()
|
||||
storeFile.writeBytes(serializer.toByteArray(objectToStore, objectSerializer))
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads object with [name] key using [objectDeserializer] deserializer.
|
||||
*/
|
||||
fun <T> load(
|
||||
name: String,
|
||||
objectDeserializer: DomainEntitySerializer<T>
|
||||
): T? {
|
||||
val storeFile = getFile(name)
|
||||
if (!storeFile.exists()) return null
|
||||
|
||||
return serializer.fromByteArray(storeFile.readBytes(), objectDeserializer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all files in storage.
|
||||
*/
|
||||
fun clearAll() {
|
||||
val storageDir = getStorageDir()
|
||||
storageDir.listFiles().forEach { it.deleteRecursively() }
|
||||
}
|
||||
|
||||
private fun getFile(name: String) = File(getStorageDir(), "$name.ser")
|
||||
|
||||
private fun getStorageDir(): File {
|
||||
val mainDir = File(directories.getInternalDataDir(), STORAGE_DIR_NAME)
|
||||
val serverDir = File(mainDir, serverId)
|
||||
if (!serverDir.exists()) serverDir.mkdirs()
|
||||
return serverDir
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
@file:JvmMultifileClass
|
||||
@file:JvmName("DomainSerializers")
|
||||
package org.moire.ultrasonic.cache.serializers
|
||||
|
||||
import com.twitter.serial.serializer.CollectionSerializers
|
||||
import com.twitter.serial.serializer.ObjectSerializer
|
||||
import com.twitter.serial.serializer.SerializationContext
|
||||
import com.twitter.serial.stream.SerializerDefs
|
||||
import com.twitter.serial.stream.SerializerInput
|
||||
import com.twitter.serial.stream.SerializerOutput
|
||||
import org.moire.ultrasonic.cache.DomainEntitySerializer
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
|
||||
private const val SERIALIZER_VERSION = 1
|
||||
|
||||
private val artistSerializer get() = object : ObjectSerializer<Artist>(SERIALIZER_VERSION) {
|
||||
override fun serializeObject(
|
||||
context: SerializationContext,
|
||||
output: SerializerOutput<out SerializerOutput<*>>,
|
||||
item: Artist
|
||||
) {
|
||||
output.writeString(item.id)
|
||||
.writeString(item.name)
|
||||
.writeString(item.index)
|
||||
.writeString(item.coverArt)
|
||||
.apply {
|
||||
val albumCount = item.albumCount
|
||||
if (albumCount != null) writeLong(albumCount) else writeNull()
|
||||
}
|
||||
.writeInt(item.closeness)
|
||||
}
|
||||
|
||||
override fun deserializeObject(
|
||||
context: SerializationContext,
|
||||
input: SerializerInput,
|
||||
versionNumber: Int
|
||||
): Artist? {
|
||||
if (versionNumber != SERIALIZER_VERSION) return null
|
||||
|
||||
val id = input.readString()
|
||||
val name = input.readString()
|
||||
val index = input.readString()
|
||||
val coverArt = input.readString()
|
||||
val albumCount = if (input.peekType() == SerializerDefs.TYPE_NULL) {
|
||||
input.readNull()
|
||||
null
|
||||
} else {
|
||||
input.readLong()
|
||||
}
|
||||
val closeness = input.readInt()
|
||||
return Artist(id, name, index, coverArt, albumCount, closeness)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializer/deserializer for [Artist] domain entity.
|
||||
*/
|
||||
fun getArtistsSerializer(): DomainEntitySerializer<Artist> = artistSerializer
|
||||
|
||||
private val artistListSerializer = CollectionSerializers.getListSerializer(artistSerializer)
|
||||
|
||||
/**
|
||||
* Serializer/deserializer for list of [Artist] domain entities.
|
||||
*/
|
||||
fun getArtistListSerializer(): DomainEntitySerializer<List<Artist>> = artistListSerializer
|
|
@ -1,50 +0,0 @@
|
|||
@file:JvmMultifileClass
|
||||
@file:JvmName("DomainSerializers")
|
||||
package org.moire.ultrasonic.cache.serializers
|
||||
|
||||
import com.twitter.serial.serializer.ObjectSerializer
|
||||
import com.twitter.serial.serializer.SerializationContext
|
||||
import com.twitter.serial.stream.SerializerInput
|
||||
import com.twitter.serial.stream.SerializerOutput
|
||||
import org.moire.ultrasonic.cache.DomainEntitySerializer
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
import org.moire.ultrasonic.domain.Indexes
|
||||
|
||||
private const val SERIALIZATION_VERSION = 1
|
||||
|
||||
private val indexesSerializer get() = object : ObjectSerializer<Indexes>(SERIALIZATION_VERSION) {
|
||||
override fun serializeObject(
|
||||
context: SerializationContext,
|
||||
output: SerializerOutput<out SerializerOutput<*>>,
|
||||
item: Indexes
|
||||
) {
|
||||
val artistListSerializer = getArtistListSerializer()
|
||||
output.writeLong(item.lastModified)
|
||||
.writeString(item.ignoredArticles)
|
||||
.writeObject<MutableList<Artist>>(context, item.shortcuts, artistListSerializer)
|
||||
.writeObject<MutableList<Artist>>(context, item.artists, artistListSerializer)
|
||||
}
|
||||
|
||||
override fun deserializeObject(
|
||||
context: SerializationContext,
|
||||
input: SerializerInput,
|
||||
versionNumber: Int
|
||||
): Indexes? {
|
||||
if (versionNumber != SERIALIZATION_VERSION) return null
|
||||
|
||||
val artistListDeserializer = getArtistListSerializer()
|
||||
val lastModified = input.readLong()
|
||||
val ignoredArticles = input.readString() ?: return null
|
||||
val shortcutsList = input.readObject(context, artistListDeserializer) ?: return null
|
||||
val artistsList = input.readObject(context, artistListDeserializer) ?: return null
|
||||
return Indexes(
|
||||
lastModified, ignoredArticles, shortcutsList.toMutableList(),
|
||||
artistsList.toMutableList()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get serializer/deserializer for [Indexes] entity.
|
||||
*/
|
||||
fun getIndexesSerializer(): DomainEntitySerializer<Indexes> = indexesSerializer
|
|
@ -1,50 +0,0 @@
|
|||
@file:JvmMultifileClass
|
||||
@file:JvmName("DomainSerializers")
|
||||
package org.moire.ultrasonic.cache.serializers
|
||||
|
||||
import com.twitter.serial.serializer.CollectionSerializers
|
||||
import com.twitter.serial.serializer.ObjectSerializer
|
||||
import com.twitter.serial.serializer.SerializationContext
|
||||
import com.twitter.serial.stream.SerializerInput
|
||||
import com.twitter.serial.stream.SerializerOutput
|
||||
import org.moire.ultrasonic.cache.DomainEntitySerializer
|
||||
import org.moire.ultrasonic.domain.MusicFolder
|
||||
|
||||
private const val SERIALIZATION_VERSION = 1
|
||||
|
||||
private val musicFolderSerializer = object : ObjectSerializer<MusicFolder>(SERIALIZATION_VERSION) {
|
||||
|
||||
override fun serializeObject(
|
||||
context: SerializationContext,
|
||||
output: SerializerOutput<out SerializerOutput<*>>,
|
||||
item: MusicFolder
|
||||
) {
|
||||
output.writeString(item.id).writeString(item.name)
|
||||
}
|
||||
|
||||
override fun deserializeObject(
|
||||
context: SerializationContext,
|
||||
input: SerializerInput,
|
||||
versionNumber: Int
|
||||
): MusicFolder? {
|
||||
if (versionNumber != SERIALIZATION_VERSION) return null
|
||||
|
||||
val id = input.readString() ?: return null
|
||||
val name = input.readString() ?: return null
|
||||
return MusicFolder(id, name)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializer/deserializer for [MusicFolder] domain entity.
|
||||
*/
|
||||
fun getMusicFolderSerializer(): DomainEntitySerializer<MusicFolder> = musicFolderSerializer
|
||||
|
||||
private val musicFolderListSerializer =
|
||||
CollectionSerializers.getListSerializer(musicFolderSerializer)
|
||||
|
||||
/**
|
||||
* Serializer/deserializer for [List] of [MusicFolder] items.
|
||||
*/
|
||||
fun getMusicFolderListSerializer(): DomainEntitySerializer<List<MusicFolder>> =
|
||||
musicFolderListSerializer
|
|
@ -1,42 +0,0 @@
|
|||
package org.moire.ultrasonic.cache
|
||||
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import com.twitter.serial.util.SerializationUtils
|
||||
import java.io.File
|
||||
import org.amshove.kluent.`it returns`
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
|
||||
internal const val INTERNAL_DATA_FOLDER = "data"
|
||||
internal const val INTERNAL_CACHE_FOLDER = "cache"
|
||||
internal const val EXTERNAL_CACHE_FOLDER = "external_cache"
|
||||
|
||||
/**
|
||||
* Base test class that inits the storage
|
||||
*/
|
||||
abstract class BaseStorageTest {
|
||||
@get:Rule val tempFileRule = TemporaryFolder()
|
||||
|
||||
protected lateinit var mockDirectories: Directories
|
||||
protected lateinit var storage: PermanentFileStorage
|
||||
|
||||
open val serverId: String = ""
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mockDirectories = mock<Directories> {
|
||||
on { getInternalDataDir() } `it returns` tempFileRule.newFolder(INTERNAL_DATA_FOLDER)
|
||||
on { getInternalCacheDir() } `it returns` tempFileRule.newFolder(INTERNAL_CACHE_FOLDER)
|
||||
on { getExternalCacheDir() } `it returns` tempFileRule.newFolder(EXTERNAL_CACHE_FOLDER)
|
||||
}
|
||||
storage = PermanentFileStorage(mockDirectories, serverId, true)
|
||||
}
|
||||
|
||||
protected val storageDir get() = File(mockDirectories.getInternalDataDir(), STORAGE_DIR_NAME)
|
||||
|
||||
protected fun validateSerializedData(index: Int = 0) {
|
||||
val serializedFileBytes = storageDir.listFiles()[index].readBytes()
|
||||
SerializationUtils.validateSerializedData(serializedFileBytes)
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
package org.moire.ultrasonic.cache
|
||||
|
||||
import java.io.File
|
||||
import org.amshove.kluent.`should be equal to`
|
||||
import org.amshove.kluent.`should contain`
|
||||
import org.amshove.kluent.`should equal`
|
||||
import org.junit.Test
|
||||
import org.moire.ultrasonic.cache.serializers.getMusicFolderSerializer
|
||||
import org.moire.ultrasonic.domain.MusicFolder
|
||||
|
||||
/**
|
||||
* Integration test for [PermanentFileStorage].
|
||||
*/
|
||||
class PermanentFileStorageTest : BaseStorageTest() {
|
||||
override val serverId: String
|
||||
get() = "some-server-id"
|
||||
|
||||
@Test
|
||||
fun `Should create storage dir if it is not exist`() {
|
||||
val item = MusicFolder("1", "2")
|
||||
storage.store("test", item, getMusicFolderSerializer())
|
||||
|
||||
storageDir.exists() `should be equal to` true
|
||||
getServerStorageDir().exists() `should be equal to` true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should serialize to file`() {
|
||||
val item = MusicFolder("1", "23")
|
||||
val name = "some-name"
|
||||
|
||||
storage.store(name, item, getMusicFolderSerializer())
|
||||
|
||||
val storageFiles = getServerStorageDir().listFiles()
|
||||
storageFiles.size `should be equal to` 1
|
||||
storageFiles[0].name `should contain` name
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should deserialize stored object`() {
|
||||
val item = MusicFolder("some", "nice")
|
||||
val name = "some-name"
|
||||
storage.store(name, item, getMusicFolderSerializer())
|
||||
|
||||
val loadedItem = storage.load(name, getMusicFolderSerializer())
|
||||
|
||||
loadedItem `should equal` item
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should overwrite existing stored object`() {
|
||||
val name = "some-nice-name"
|
||||
val item1 = MusicFolder("1", "1")
|
||||
val item2 = MusicFolder("2", "2")
|
||||
storage.store(name, item1, getMusicFolderSerializer())
|
||||
storage.store(name, item2, getMusicFolderSerializer())
|
||||
|
||||
val loadedItem = storage.load(name, getMusicFolderSerializer())
|
||||
|
||||
loadedItem `should equal` item2
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should clear all files when clearAll is called`() {
|
||||
storage.store("name1", MusicFolder("1", "1"), getMusicFolderSerializer())
|
||||
storage.store("name2", MusicFolder("2", "2"), getMusicFolderSerializer())
|
||||
|
||||
storage.clearAll()
|
||||
|
||||
getServerStorageDir().listFiles().size `should be equal to` 0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should return null if serialized file not available`() {
|
||||
val loadedItem = storage.load("some-name", getMusicFolderSerializer())
|
||||
|
||||
loadedItem `should equal` null
|
||||
}
|
||||
|
||||
private fun getServerStorageDir() = File(storageDir, serverId)
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package org.moire.ultrasonic.cache.serializers
|
||||
|
||||
import org.amshove.kluent.`should equal`
|
||||
import org.junit.Test
|
||||
import org.moire.ultrasonic.cache.BaseStorageTest
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
|
||||
/**
|
||||
* [Artist] serializers test.
|
||||
*/
|
||||
class ArtistSerializerTest : BaseStorageTest() {
|
||||
@Test
|
||||
fun `Should correctly serialize Artist object`() {
|
||||
val item = Artist("id", "name", "index", "coverArt", 1, 0)
|
||||
|
||||
storage.store("some-name", item, getArtistsSerializer())
|
||||
|
||||
validateSerializedData()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should correctly deserialize Artist object`() {
|
||||
val itemName = "some-name"
|
||||
val item = Artist("id", "name", "index", "coverArt", null, 0)
|
||||
storage.store(itemName, item, getArtistsSerializer())
|
||||
|
||||
val loadedItem = storage.load(itemName, getArtistsSerializer())
|
||||
|
||||
loadedItem `should equal` item
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should correctly serialize list of Artists`() {
|
||||
val itemsList = listOf(
|
||||
Artist(id = "1"),
|
||||
Artist(id = "2", name = "some")
|
||||
)
|
||||
|
||||
storage.store("some-name", itemsList, getArtistListSerializer())
|
||||
|
||||
validateSerializedData()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should correctly deserialize list of Artists`() {
|
||||
val name = "some-name"
|
||||
val itemsList = listOf(
|
||||
Artist(id = "1"),
|
||||
Artist(id = "2", name = "some")
|
||||
)
|
||||
storage.store(name, itemsList, getArtistListSerializer())
|
||||
|
||||
val loadedItems = storage.load(name, getArtistListSerializer())
|
||||
|
||||
loadedItems `should equal` itemsList
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package org.moire.ultrasonic.cache.serializers
|
||||
|
||||
import org.amshove.kluent.`should equal`
|
||||
import org.junit.Test
|
||||
import org.moire.ultrasonic.cache.BaseStorageTest
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
import org.moire.ultrasonic.domain.Indexes
|
||||
|
||||
/**
|
||||
* Test [Indexes] domain entity serializer.
|
||||
*/
|
||||
class IndexesSerializerTest : BaseStorageTest() {
|
||||
@Test
|
||||
fun `Should correctly serialize Indexes object`() {
|
||||
val item = Indexes(
|
||||
220L, "", mutableListOf(Artist("12")),
|
||||
mutableListOf(Artist("233", "some"))
|
||||
)
|
||||
|
||||
storage.store("some-name", item, getIndexesSerializer())
|
||||
|
||||
validateSerializedData()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should correctly deserialize Indexes object`() {
|
||||
val name = "some-name"
|
||||
val item = Indexes(
|
||||
220L, "", mutableListOf(Artist("12")),
|
||||
mutableListOf(Artist("233", "some"))
|
||||
)
|
||||
storage.store(name, item, getIndexesSerializer())
|
||||
|
||||
val loadedItem = storage.load(name, getIndexesSerializer())
|
||||
|
||||
loadedItem `should equal` item
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package org.moire.ultrasonic.cache.serializers
|
||||
|
||||
import org.amshove.kluent.`should equal`
|
||||
import org.junit.Test
|
||||
import org.moire.ultrasonic.cache.BaseStorageTest
|
||||
import org.moire.ultrasonic.domain.MusicFolder
|
||||
|
||||
/**
|
||||
* [MusicFolder] serializers test.
|
||||
*/
|
||||
class MusicFolderSerializerTest : BaseStorageTest() {
|
||||
@Test
|
||||
fun `Should correctly serialize MusicFolder object`() {
|
||||
val item = MusicFolder("Music", "Folder")
|
||||
|
||||
storage.store("some-name", item, getMusicFolderSerializer())
|
||||
|
||||
validateSerializedData()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should correctly deserialize MusicFolder object`() {
|
||||
val name = "name"
|
||||
val item = MusicFolder("some", "none")
|
||||
storage.store(name, item, getMusicFolderSerializer())
|
||||
|
||||
val loadedItem = storage.load(name, getMusicFolderSerializer())
|
||||
|
||||
loadedItem `should equal` item
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should correctly serialize list of MusicFolders objects`() {
|
||||
val itemsList = listOf(
|
||||
MusicFolder("1", "1"),
|
||||
MusicFolder("2", "2")
|
||||
)
|
||||
|
||||
storage.store("some-name", itemsList, getMusicFolderListSerializer())
|
||||
|
||||
validateSerializedData()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Should correctly deserialize list of MusicFolder objects`() {
|
||||
val name = "some-name"
|
||||
val itemsList = listOf(
|
||||
MusicFolder("1", "1"),
|
||||
MusicFolder("2", "2")
|
||||
)
|
||||
storage.store(name, itemsList, getMusicFolderListSerializer())
|
||||
|
||||
val loadedItem = storage.load(name, getMusicFolderListSerializer())
|
||||
|
||||
loadedItem `should equal` itemsList
|
||||
}
|
||||
}
|
|
@ -1,10 +1,8 @@
|
|||
apply from: bootstrap.kotlinModule
|
||||
apply from: bootstrap.androidModule
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
ext {
|
||||
jacocoExclude = [
|
||||
'**/domain/**'
|
||||
]
|
||||
}
|
||||
dependencies {
|
||||
api other.semver
|
||||
implementation libs.roomRuntime
|
||||
implementation libs.roomKtx
|
||||
kapt libs.room
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.moire.ultrasonic.subsonic.domain">
|
||||
</manifest>
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Album.kt
|
||||
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import java.util.Date
|
||||
|
||||
@Entity(tableName = "albums", primaryKeys = ["id", "serverId"])
|
||||
data class Album(
|
||||
override var id: String,
|
||||
@ColumnInfo(defaultValue = "-1")
|
||||
override var serverId: Int = -1,
|
||||
override var parent: String? = null,
|
||||
override var album: String? = null,
|
||||
override var title: String? = null,
|
||||
override val name: String? = null,
|
||||
override var discNumber: Int? = 0,
|
||||
override var coverArt: String? = null,
|
||||
override var songCount: Long? = null,
|
||||
override var created: Date? = null,
|
||||
override var artist: String? = null,
|
||||
override var artistId: String? = null,
|
||||
override var duration: Int? = 0,
|
||||
override var year: Int? = 0,
|
||||
override var genre: String? = null,
|
||||
override var starred: Boolean = false,
|
||||
override var path: String? = null,
|
||||
override var closeness: Int = 0,
|
||||
) : MusicDirectory.Child() {
|
||||
override var isDirectory = true
|
||||
override var isVideo = false
|
||||
}
|
|
@ -1,16 +1,23 @@
|
|||
/*
|
||||
* Artist.kt
|
||||
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import java.io.Serializable
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
|
||||
@Entity(tableName = "artists", primaryKeys = ["id", "serverId"])
|
||||
data class Artist(
|
||||
var id: String? = null,
|
||||
var name: String? = null,
|
||||
var index: String? = null,
|
||||
var coverArt: String? = null,
|
||||
var albumCount: Long? = null,
|
||||
var closeness: Int = 0
|
||||
) : Serializable {
|
||||
companion object {
|
||||
private const val serialVersionUID = -5790532593784846982L
|
||||
}
|
||||
}
|
||||
override var id: String,
|
||||
@ColumnInfo(defaultValue = "-1")
|
||||
override var serverId: Int = -1,
|
||||
override var name: String? = null,
|
||||
override var index: String? = null,
|
||||
override var coverArt: String? = null,
|
||||
override var albumCount: Long? = null,
|
||||
override var closeness: Int = 0
|
||||
) : ArtistOrIndex(id, serverId)
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* ArtistOrIndex.kt
|
||||
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import androidx.room.Ignore
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
abstract class ArtistOrIndex(
|
||||
@Ignore
|
||||
override var id: String,
|
||||
@Ignore
|
||||
open var serverId: Int,
|
||||
@Ignore
|
||||
override var name: String? = null,
|
||||
@Ignore
|
||||
open var index: String? = null,
|
||||
@Ignore
|
||||
open var coverArt: String? = null,
|
||||
@Ignore
|
||||
open var albumCount: Long? = null,
|
||||
@Ignore
|
||||
open var closeness: Int = 0
|
||||
) : GenericEntry() {
|
||||
|
||||
fun compareTo(other: ArtistOrIndex): Int {
|
||||
return when {
|
||||
this.closeness == other.closeness -> {
|
||||
0
|
||||
}
|
||||
this.closeness > other.closeness -> {
|
||||
-1
|
||||
}
|
||||
else -> {
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun compareTo(other: Identifiable) = compareTo(other as ArtistOrIndex)
|
||||
}
|
|
@ -2,7 +2,6 @@ package org.moire.ultrasonic.domain
|
|||
|
||||
import java.io.Serializable
|
||||
import java.util.Date
|
||||
import org.moire.ultrasonic.domain.MusicDirectory.Entry
|
||||
|
||||
data class Bookmark(
|
||||
val position: Int = 0,
|
||||
|
@ -10,7 +9,7 @@ data class Bookmark(
|
|||
val comment: String,
|
||||
val created: Date? = null,
|
||||
val changed: Date? = null,
|
||||
val entry: Entry
|
||||
val track: Track
|
||||
) : Serializable {
|
||||
companion object {
|
||||
private const val serialVersionUID = 8988990025189807803L
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package org.moire.ultrasonic.domain
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.io.Serializable
|
||||
|
||||
@Entity
|
||||
data class Genre(
|
||||
val name: String,
|
||||
val index: String
|
||||
@PrimaryKey val index: String,
|
||||
val name: String
|
||||
) : Serializable {
|
||||
companion object {
|
||||
private const val serialVersionUID = -3943025175219134028L
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package org.moire.ultrasonic.domain
|
||||
|
||||
import androidx.room.Ignore
|
||||
|
||||
abstract class GenericEntry : Identifiable {
|
||||
@Ignore
|
||||
open val name: String? = null
|
||||
}
|
||||
|
||||
interface Identifiable : Comparable<Identifiable> {
|
||||
val id: String
|
||||
|
||||
val longId: Long
|
||||
get() = id.hashCode().toLong()
|
||||
|
||||
override fun compareTo(other: Identifiable): Int {
|
||||
return longId.compareTo(other.longId)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Index.kt
|
||||
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
|
||||
@Entity(tableName = "indexes", primaryKeys = ["id", "serverId"])
|
||||
data class Index(
|
||||
override var id: String,
|
||||
@ColumnInfo(defaultValue = "-1")
|
||||
override var serverId: Int = -1,
|
||||
override var name: String? = null,
|
||||
override var index: String? = null,
|
||||
override var coverArt: String? = null,
|
||||
override var albumCount: Long? = null,
|
||||
override var closeness: Int = 0,
|
||||
var musicFolderId: String? = null
|
||||
) : ArtistOrIndex(id, serverId)
|
|
@ -1,14 +0,0 @@
|
|||
package org.moire.ultrasonic.domain
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
data class Indexes(
|
||||
val lastModified: Long,
|
||||
val ignoredArticles: String,
|
||||
val shortcuts: MutableList<Artist> = mutableListOf(),
|
||||
val artists: MutableList<Artist> = mutableListOf()
|
||||
) : Serializable {
|
||||
companion object {
|
||||
private const val serialVersionUID = 8156117238598414701L
|
||||
}
|
||||
}
|
|
@ -1,78 +1,61 @@
|
|||
/*
|
||||
* MusicDirectory.kt
|
||||
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import java.io.Serializable
|
||||
import java.util.Date
|
||||
|
||||
class MusicDirectory {
|
||||
class MusicDirectory : ArrayList<MusicDirectory.Child>() {
|
||||
var name: String? = null
|
||||
private val children = mutableListOf<Entry>()
|
||||
|
||||
fun addAll(entries: Collection<Entry>) {
|
||||
children.addAll(entries)
|
||||
}
|
||||
|
||||
fun addFirst(child: Entry) {
|
||||
children.add(0, child)
|
||||
}
|
||||
|
||||
fun addChild(child: Entry) {
|
||||
children.add(child)
|
||||
}
|
||||
|
||||
fun findChild(id: String): Entry? = children.lastOrNull { it.id == id }
|
||||
|
||||
fun getAllChild(): List<Entry> = children.toList()
|
||||
|
||||
@JvmOverloads
|
||||
fun getChildren(
|
||||
includeDirs: Boolean = true,
|
||||
includeFiles: Boolean = true
|
||||
): List<Entry> {
|
||||
): List<Child> {
|
||||
if (includeDirs && includeFiles) {
|
||||
return children
|
||||
return toList()
|
||||
}
|
||||
|
||||
return children.filter { it.isDirectory && includeDirs || !it.isDirectory && includeFiles }
|
||||
return filter { it.isDirectory && includeDirs || !it.isDirectory && includeFiles }
|
||||
}
|
||||
|
||||
data class Entry(
|
||||
var id: String? = null,
|
||||
var parent: String? = null,
|
||||
var isDirectory: Boolean = false,
|
||||
var title: String? = null,
|
||||
var album: String? = null,
|
||||
var albumId: String? = null,
|
||||
var artist: String? = null,
|
||||
var artistId: String? = null,
|
||||
var track: Int? = 0,
|
||||
var year: Int? = 0,
|
||||
var genre: String? = null,
|
||||
var contentType: String? = null,
|
||||
var suffix: String? = null,
|
||||
var transcodedContentType: String? = null,
|
||||
var transcodedSuffix: String? = null,
|
||||
var coverArt: String? = null,
|
||||
var size: Long? = null,
|
||||
var songCount: Long? = null,
|
||||
var duration: Int? = null,
|
||||
var bitRate: Int? = null,
|
||||
var path: String? = null,
|
||||
var isVideo: Boolean = false,
|
||||
var starred: Boolean = false,
|
||||
var discNumber: Int? = null,
|
||||
var type: String? = null,
|
||||
var created: Date? = null,
|
||||
var closeness: Int = 0,
|
||||
var bookmarkPosition: Int = 0,
|
||||
var userRating: Int? = null,
|
||||
var averageRating: Float? = null
|
||||
) : Serializable {
|
||||
fun setDuration(duration: Long) {
|
||||
this.duration = duration.toInt()
|
||||
fun getTracks(): List<Track> {
|
||||
return mapNotNull {
|
||||
it as? Track
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val serialVersionUID = -3339106650010798108L
|
||||
fun getAlbums(): List<Album> {
|
||||
return mapNotNull {
|
||||
it as? Album
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Child : GenericEntry() {
|
||||
abstract override var id: String
|
||||
abstract var serverId: Int
|
||||
abstract var parent: String?
|
||||
abstract var isDirectory: Boolean
|
||||
abstract var album: String?
|
||||
abstract var title: String?
|
||||
abstract override val name: String?
|
||||
abstract var discNumber: Int?
|
||||
abstract var coverArt: String?
|
||||
abstract var songCount: Long?
|
||||
abstract var created: Date?
|
||||
abstract var artist: String?
|
||||
abstract var artistId: String?
|
||||
abstract var duration: Int?
|
||||
abstract var year: Int?
|
||||
abstract var genre: String?
|
||||
abstract var starred: Boolean
|
||||
abstract var path: String?
|
||||
abstract var closeness: Int
|
||||
abstract var isVideo: Boolean
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,22 @@
|
|||
/*
|
||||
* MusicFolder.kt
|
||||
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
|
||||
/**
|
||||
* Represents a top level directory in which music or other media is stored.
|
||||
*/
|
||||
@Entity(tableName = "music_folders", primaryKeys = ["id", "serverId"])
|
||||
data class MusicFolder(
|
||||
val id: String,
|
||||
val name: String
|
||||
)
|
||||
override val id: String,
|
||||
override val name: String,
|
||||
@ColumnInfo(defaultValue = "-1")
|
||||
var serverId: Int
|
||||
) : GenericEntry()
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
package org.moire.ultrasonic.domain
|
||||
|
||||
enum class PlayerState {
|
||||
IDLE,
|
||||
DOWNLOADING,
|
||||
PREPARING,
|
||||
PREPARED,
|
||||
STARTED,
|
||||
STOPPED,
|
||||
PAUSED,
|
||||
COMPLETED
|
||||
}
|
|
@ -3,14 +3,14 @@ package org.moire.ultrasonic.domain
|
|||
import java.io.Serializable
|
||||
|
||||
data class Playlist @JvmOverloads constructor(
|
||||
val id: String,
|
||||
var name: String,
|
||||
override val id: String,
|
||||
override var name: String,
|
||||
val owner: String = "",
|
||||
val comment: String = "",
|
||||
val songCount: String = "",
|
||||
val created: String = "",
|
||||
val public: Boolean? = null
|
||||
) : Serializable {
|
||||
) : Serializable, GenericEntry() {
|
||||
companion object {
|
||||
private const val serialVersionUID = -4160515427075433798L
|
||||
}
|
||||
|
|
|
@ -3,12 +3,12 @@ package org.moire.ultrasonic.domain
|
|||
import java.io.Serializable
|
||||
|
||||
data class PodcastsChannel(
|
||||
val id: String,
|
||||
override val id: String,
|
||||
val title: String?,
|
||||
val url: String?,
|
||||
val description: String?,
|
||||
val status: String?
|
||||
) : Serializable {
|
||||
) : Serializable, GenericEntry() {
|
||||
companion object {
|
||||
private const val serialVersionUID = -4160515427075433798L
|
||||
}
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
package org.moire.ultrasonic.domain
|
||||
|
||||
enum class RepeatMode {
|
||||
OFF {
|
||||
override operator fun next(): RepeatMode = ALL
|
||||
},
|
||||
ALL {
|
||||
override operator fun next(): RepeatMode = SINGLE
|
||||
},
|
||||
SINGLE {
|
||||
override operator fun next(): RepeatMode = OFF
|
||||
};
|
||||
|
||||
abstract operator fun next(): RepeatMode
|
||||
}
|
|
@ -1,12 +1,10 @@
|
|||
package org.moire.ultrasonic.domain
|
||||
|
||||
import org.moire.ultrasonic.domain.MusicDirectory.Entry
|
||||
|
||||
/**
|
||||
* The result of a search. Contains matching artists, albums and songs.
|
||||
*/
|
||||
data class SearchResult(
|
||||
val artists: List<Artist>,
|
||||
val albums: List<Entry>,
|
||||
val songs: List<Entry>
|
||||
val artists: List<ArtistOrIndex> = listOf(),
|
||||
val albums: List<Album> = listOf(),
|
||||
val songs: List<Track> = listOf()
|
||||
)
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
package org.moire.ultrasonic.domain
|
||||
|
||||
import java.io.Serializable
|
||||
import org.moire.ultrasonic.domain.MusicDirectory.Entry
|
||||
|
||||
data class Share(
|
||||
var id: String? = null,
|
||||
override var id: String,
|
||||
var url: String? = null,
|
||||
var description: String? = null,
|
||||
var username: String? = null,
|
||||
|
@ -12,17 +11,22 @@ data class Share(
|
|||
var lastVisited: String? = null,
|
||||
var expires: String? = null,
|
||||
var visitCount: Long? = null,
|
||||
private val entries: MutableList<Entry> = mutableListOf()
|
||||
) : Serializable {
|
||||
val name: String?
|
||||
get() = url?.let { urlPattern.matcher(url).replaceFirst("$1") }
|
||||
private val tracks: MutableList<Track> = mutableListOf()
|
||||
) : Serializable, GenericEntry() {
|
||||
override val name: String?
|
||||
get() {
|
||||
if (url != null) {
|
||||
return urlPattern.matcher(url!!).replaceFirst("$1")
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getEntries(): List<Entry> {
|
||||
return entries.toList()
|
||||
fun getEntries(): List<Track> {
|
||||
return tracks.toList()
|
||||
}
|
||||
|
||||
fun addEntry(entry: Entry) {
|
||||
entries.add(entry)
|
||||
fun addEntry(track: Track) {
|
||||
tracks.add(track)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Track.kt
|
||||
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.domain
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import java.io.Serializable
|
||||
import java.util.Date
|
||||
|
||||
@Entity(tableName = "tracks", primaryKeys = ["id", "serverId"])
|
||||
data class Track(
|
||||
override var id: String,
|
||||
@ColumnInfo(defaultValue = "-1")
|
||||
override var serverId: Int = -1,
|
||||
override var parent: String? = null,
|
||||
override var isDirectory: Boolean = false,
|
||||
override var title: String? = null,
|
||||
override var album: String? = null,
|
||||
var albumId: String? = null,
|
||||
override var artist: String? = null,
|
||||
override var artistId: String? = null,
|
||||
var track: Int? = null,
|
||||
override var year: Int? = null,
|
||||
override var genre: String? = null,
|
||||
var contentType: String? = null,
|
||||
var suffix: String? = null,
|
||||
var transcodedContentType: String? = null,
|
||||
var transcodedSuffix: String? = null,
|
||||
override var coverArt: String? = null,
|
||||
var size: Long? = null,
|
||||
override var songCount: Long? = null,
|
||||
override var duration: Int? = null,
|
||||
var bitRate: Int? = null,
|
||||
override var path: String? = null,
|
||||
override var isVideo: Boolean = false,
|
||||
override var starred: Boolean = false,
|
||||
override var discNumber: Int? = null,
|
||||
var type: String? = null,
|
||||
override var created: Date? = null,
|
||||
override var closeness: Int = 0,
|
||||
var bookmarkPosition: Int = 0,
|
||||
var userRating: Int? = null,
|
||||
var averageRating: Float? = null,
|
||||
override var name: String? = null
|
||||
) : Serializable, MusicDirectory.Child() {
|
||||
fun setDuration(duration: Long) {
|
||||
this.duration = duration.toInt()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val serialVersionUID = -3339106650010798108L
|
||||
}
|
||||
|
||||
fun compareTo(other: Track): Int {
|
||||
when {
|
||||
this.closeness == other.closeness -> {
|
||||
return 0
|
||||
}
|
||||
this.closeness > other.closeness -> {
|
||||
return -1
|
||||
}
|
||||
else -> {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun compareTo(other: Identifiable) = compareTo(other as Track)
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package org.moire.ultrasonic.domain
|
||||
|
||||
import net.swiftzer.semver.SemVer
|
||||
|
||||
/**
|
||||
* Represents the version number of the Subsonic Android app.
|
||||
*/
|
||||
data class Version(
|
||||
val version: SemVer
|
||||
) : Comparable<Version> {
|
||||
|
||||
override fun compareTo(other: Version): Int {
|
||||
return version.compareTo(other.version)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Creates a new version instance by parsing the given string.
|
||||
*
|
||||
* @param version A string of the format "1.27", "1.27.2" or "1.27.beta3".
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fromCharSequence(version: String): Version {
|
||||
return Version(SemVer.parse(version))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
apply from: bootstrap.androidModule
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
lintOptions {
|
||||
baselineFile file("lint-baseline.xml")
|
||||
abortOnError true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api androidSupport.support
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<issues format="4" by="lint 2.3.3">
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Call requires API level 21 (current min is 14): android.widget.AbsListView#setSelectionFromTop"
|
||||
errorLine1=" setSelectionFromTop(movePos, top - padTop);"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/mobeta/android/dslv/DragSortListView.java"
|
||||
line="2936"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="OldTargetApi"
|
||||
message="Not targeting the latest versions of Android; compatibility modes apply. Consider testing and updating this version. Consult the `android.os.Build.VERSION_CODES` javadoc for details."
|
||||
errorLine1=" <uses-sdk android:targetSdkVersion="7""
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/AndroidManifest.xml"
|
||||
line="6"
|
||||
column="15"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="GradleOverrides"
|
||||
message="This `targetSdkVersion` value (`7`) is not used; it is always overridden by the value specified in the Gradle build script (`22`)"
|
||||
errorLine1=" <uses-sdk android:targetSdkVersion="7""
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/AndroidManifest.xml"
|
||||
line="6"
|
||||
column="15"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="GradleOverrides"
|
||||
message="This `minSdkVersion` value (`7`) is not used; it is always overridden by the value specified in the Gradle build script (`14`)"
|
||||
errorLine1=" android:minSdkVersion="7" />"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/AndroidManifest.xml"
|
||||
line="7"
|
||||
column="7"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ClickableViewAccessibility"
|
||||
message="`com/mobeta/android/dslv/DragSortController#onTouch` should call `View#performClick` when a click is detected"
|
||||
errorLine1=" public boolean onTouch(View v, MotionEvent ev) {"
|
||||
errorLine2=" ~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/mobeta/android/dslv/DragSortController.java"
|
||||
line="238"
|
||||
column="20"/>
|
||||
</issue>
|
||||
|
||||
</issues>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.mobeta.android.dslv"
|
||||
android:versionCode="4"
|
||||
android:versionName="0.6.1">
|
||||
</manifest>
|
|
@ -1,468 +0,0 @@
|
|||
package com.mobeta.android.dslv;
|
||||
|
||||
import android.graphics.Point;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
/**
|
||||
* Class that starts and stops item drags on a {@link DragSortListView}
|
||||
* based on touch gestures. This class also inherits from
|
||||
* {@link SimpleFloatViewManager}, which provides basic float View
|
||||
* creation.
|
||||
*
|
||||
* An instance of this class is meant to be passed to the methods
|
||||
* {@link DragSortListView#setTouchListener()} and
|
||||
* {@link DragSortListView#setFloatViewManager()} of your
|
||||
* {@link DragSortListView} instance.
|
||||
*/
|
||||
public class DragSortController extends SimpleFloatViewManager implements View.OnTouchListener, GestureDetector.OnGestureListener {
|
||||
|
||||
/**
|
||||
* Drag init mode enum.
|
||||
*/
|
||||
public static final int ON_DOWN = 0;
|
||||
public static final int ON_DRAG = 1;
|
||||
public static final int ON_LONG_PRESS = 2;
|
||||
|
||||
private int mDragInitMode = ON_DOWN;
|
||||
|
||||
private boolean mSortEnabled = true;
|
||||
|
||||
/**
|
||||
* Remove mode enum.
|
||||
*/
|
||||
public static final int CLICK_REMOVE = 0;
|
||||
public static final int FLING_REMOVE = 1;
|
||||
|
||||
/**
|
||||
* The current remove mode.
|
||||
*/
|
||||
private int mRemoveMode;
|
||||
|
||||
private boolean mRemoveEnabled = false;
|
||||
private boolean mIsRemoving = false;
|
||||
|
||||
private GestureDetector mDetector;
|
||||
|
||||
private GestureDetector mFlingRemoveDetector;
|
||||
|
||||
private int mTouchSlop;
|
||||
|
||||
public static final int MISS = -1;
|
||||
|
||||
private int mHitPos = MISS;
|
||||
private int mFlingHitPos = MISS;
|
||||
|
||||
private int mClickRemoveHitPos = MISS;
|
||||
|
||||
private int[] mTempLoc = new int[2];
|
||||
|
||||
private int mItemX;
|
||||
private int mItemY;
|
||||
|
||||
private int mCurrX;
|
||||
private int mCurrY;
|
||||
|
||||
private boolean mDragging = false;
|
||||
|
||||
private float mFlingSpeed = 500f;
|
||||
|
||||
private int mDragHandleId;
|
||||
|
||||
private int mClickRemoveId;
|
||||
|
||||
private int mFlingHandleId;
|
||||
private boolean mCanDrag;
|
||||
|
||||
private DragSortListView mDslv;
|
||||
private int mPositionX;
|
||||
|
||||
/**
|
||||
* Calls {@link #DragSortController(DragSortListView, int)} with a
|
||||
* 0 drag handle id, FLING_RIGHT_REMOVE remove mode,
|
||||
* and ON_DOWN drag init. By default, sorting is enabled, and
|
||||
* removal is disabled.
|
||||
*
|
||||
* @param dslv The DSLV instance
|
||||
*/
|
||||
public DragSortController(DragSortListView dslv) {
|
||||
this(dslv, 0, ON_DOWN, FLING_REMOVE);
|
||||
}
|
||||
|
||||
public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode, int removeMode) {
|
||||
this(dslv, dragHandleId, dragInitMode, removeMode, 0);
|
||||
}
|
||||
|
||||
public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode, int removeMode, int clickRemoveId) {
|
||||
this(dslv, dragHandleId, dragInitMode, removeMode, clickRemoveId, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* By default, sorting is enabled, and removal is disabled.
|
||||
*
|
||||
* @param dslv The DSLV instance
|
||||
* @param dragHandleId The resource id of the View that represents
|
||||
* the drag handle in a list item.
|
||||
*/
|
||||
public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode,
|
||||
int removeMode, int clickRemoveId, int flingHandleId) {
|
||||
super(dslv);
|
||||
mDslv = dslv;
|
||||
mDetector = new GestureDetector(dslv.getContext(), this);
|
||||
mFlingRemoveDetector = new GestureDetector(dslv.getContext(), mFlingRemoveListener);
|
||||
mFlingRemoveDetector.setIsLongpressEnabled(false);
|
||||
mTouchSlop = ViewConfiguration.get(dslv.getContext()).getScaledTouchSlop();
|
||||
mDragHandleId = dragHandleId;
|
||||
mClickRemoveId = clickRemoveId;
|
||||
mFlingHandleId = flingHandleId;
|
||||
setRemoveMode(removeMode);
|
||||
setDragInitMode(dragInitMode);
|
||||
}
|
||||
|
||||
|
||||
public int getDragInitMode() {
|
||||
return mDragInitMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set how a drag is initiated. Needs to be one of
|
||||
* {@link ON_DOWN}, {@link ON_DRAG}, or {@link ON_LONG_PRESS}.
|
||||
*
|
||||
* @param mode The drag init mode.
|
||||
*/
|
||||
public void setDragInitMode(int mode) {
|
||||
mDragInitMode = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/Disable list item sorting. Disabling is useful if only item
|
||||
* removal is desired. Prevents drags in the vertical direction.
|
||||
*
|
||||
* @param enabled Set <code>true</code> to enable list
|
||||
* item sorting.
|
||||
*/
|
||||
public void setSortEnabled(boolean enabled) {
|
||||
mSortEnabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isSortEnabled() {
|
||||
return mSortEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* One of {@link CLICK_REMOVE}, {@link FLING_RIGHT_REMOVE},
|
||||
* {@link FLING_LEFT_REMOVE},
|
||||
* {@link SLIDE_RIGHT_REMOVE}, or {@link SLIDE_LEFT_REMOVE}.
|
||||
*/
|
||||
public void setRemoveMode(int mode) {
|
||||
mRemoveMode = mode;
|
||||
}
|
||||
|
||||
public int getRemoveMode() {
|
||||
return mRemoveMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/Disable item removal without affecting remove mode.
|
||||
*/
|
||||
public void setRemoveEnabled(boolean enabled) {
|
||||
mRemoveEnabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isRemoveEnabled() {
|
||||
return mRemoveEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the resource id for the View that represents the drag
|
||||
* handle in a list item.
|
||||
*
|
||||
* @param id An android resource id.
|
||||
*/
|
||||
public void setDragHandleId(int id) {
|
||||
mDragHandleId = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the resource id for the View that represents the fling
|
||||
* handle in a list item.
|
||||
*
|
||||
* @param id An android resource id.
|
||||
*/
|
||||
public void setFlingHandleId(int id) {
|
||||
mFlingHandleId = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the resource id for the View that represents click
|
||||
* removal button.
|
||||
*
|
||||
* @param id An android resource id.
|
||||
*/
|
||||
public void setClickRemoveId(int id) {
|
||||
mClickRemoveId = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets flags to restrict certain motions of the floating View
|
||||
* based on DragSortController settings (such as remove mode).
|
||||
* Starts the drag on the DragSortListView.
|
||||
*
|
||||
* @param position The list item position (includes headers).
|
||||
* @param deltaX Touch x-coord minus left edge of floating View.
|
||||
* @param deltaY Touch y-coord minus top edge of floating View.
|
||||
*
|
||||
* @return True if drag started, false otherwise.
|
||||
*/
|
||||
public boolean startDrag(int position, int deltaX, int deltaY) {
|
||||
|
||||
int dragFlags = 0;
|
||||
if (mSortEnabled && !mIsRemoving) {
|
||||
dragFlags |= DragSortListView.DRAG_POS_Y | DragSortListView.DRAG_NEG_Y;
|
||||
}
|
||||
if (mRemoveEnabled && mIsRemoving) {
|
||||
dragFlags |= DragSortListView.DRAG_POS_X;
|
||||
dragFlags |= DragSortListView.DRAG_NEG_X;
|
||||
}
|
||||
|
||||
mDragging = mDslv.startDrag(position - mDslv.getHeaderViewsCount(), dragFlags, deltaX,
|
||||
deltaY);
|
||||
return mDragging;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent ev) {
|
||||
if (!mDslv.isDragEnabled() || mDslv.listViewIntercepted()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mDetector.onTouchEvent(ev);
|
||||
if (mRemoveEnabled && mDragging && mRemoveMode == FLING_REMOVE) {
|
||||
mFlingRemoveDetector.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
int action = ev.getAction() & MotionEvent.ACTION_MASK;
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mCurrX = (int) ev.getX();
|
||||
mCurrY = (int) ev.getY();
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (mRemoveEnabled && mIsRemoving) {
|
||||
int x = mPositionX >= 0 ? mPositionX : -mPositionX;
|
||||
int removePoint = mDslv.getWidth() / 2;
|
||||
if (x > removePoint) {
|
||||
mDslv.stopDragWithVelocity(true, 0);
|
||||
}
|
||||
}
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
mIsRemoving = false;
|
||||
mDragging = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides to provide fading when slide removal is enabled.
|
||||
*/
|
||||
@Override
|
||||
public void onDragFloatView(View floatView, Point position, Point touch) {
|
||||
|
||||
if (mRemoveEnabled && mIsRemoving) {
|
||||
mPositionX = position.x;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the position to start dragging based on the ACTION_DOWN
|
||||
* MotionEvent. This function simply calls
|
||||
* {@link #dragHandleHitPosition(MotionEvent)}. Override
|
||||
* to change drag handle behavior;
|
||||
* this function is called internally when an ACTION_DOWN
|
||||
* event is detected.
|
||||
*
|
||||
* @param ev The ACTION_DOWN MotionEvent.
|
||||
*
|
||||
* @return The list position to drag if a drag-init gesture is
|
||||
* detected; MISS if unsuccessful.
|
||||
*/
|
||||
public int startDragPosition(MotionEvent ev) {
|
||||
return dragHandleHitPosition(ev);
|
||||
}
|
||||
|
||||
public int startFlingPosition(MotionEvent ev) {
|
||||
return mRemoveMode == FLING_REMOVE ? flingHandleHitPosition(ev) : MISS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for the touch of an item's drag handle (specified by
|
||||
* {@link #setDragHandleId(int)}), and returns that item's position
|
||||
* if a drag handle touch was detected.
|
||||
*
|
||||
* @param ev The ACTION_DOWN MotionEvent.
|
||||
|
||||
* @return The list position of the item whose drag handle was
|
||||
* touched; MISS if unsuccessful.
|
||||
*/
|
||||
public int dragHandleHitPosition(MotionEvent ev) {
|
||||
return viewIdHitPosition(ev, mDragHandleId);
|
||||
}
|
||||
|
||||
public int flingHandleHitPosition(MotionEvent ev) {
|
||||
return viewIdHitPosition(ev, mFlingHandleId);
|
||||
}
|
||||
|
||||
public int viewIdHitPosition(MotionEvent ev, int id) {
|
||||
final int x = (int) ev.getX();
|
||||
final int y = (int) ev.getY();
|
||||
|
||||
int touchPos = mDslv.pointToPosition(x, y); // includes headers/footers
|
||||
|
||||
final int numHeaders = mDslv.getHeaderViewsCount();
|
||||
final int numFooters = mDslv.getFooterViewsCount();
|
||||
final int count = mDslv.getCount();
|
||||
|
||||
// Log.d("mobeta", "touch down on position " + itemnum);
|
||||
// We're only interested if the touch was on an
|
||||
// item that's not a header or footer.
|
||||
if (touchPos != AdapterView.INVALID_POSITION && touchPos >= numHeaders
|
||||
&& touchPos < (count - numFooters)) {
|
||||
final View item = mDslv.getChildAt(touchPos - mDslv.getFirstVisiblePosition());
|
||||
final int rawX = (int) ev.getRawX();
|
||||
final int rawY = (int) ev.getRawY();
|
||||
|
||||
View dragBox = id == 0 ? item : (View) item.findViewById(id);
|
||||
if (dragBox != null) {
|
||||
dragBox.getLocationOnScreen(mTempLoc);
|
||||
|
||||
if (rawX > mTempLoc[0] && rawY > mTempLoc[1] &&
|
||||
rawX < mTempLoc[0] + dragBox.getWidth() &&
|
||||
rawY < mTempLoc[1] + dragBox.getHeight()) {
|
||||
|
||||
mItemX = item.getLeft();
|
||||
mItemY = item.getTop();
|
||||
|
||||
return touchPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MISS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDown(MotionEvent ev) {
|
||||
if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) {
|
||||
mClickRemoveHitPos = viewIdHitPosition(ev, mClickRemoveId);
|
||||
}
|
||||
|
||||
mHitPos = startDragPosition(ev);
|
||||
if (mHitPos != MISS && mDragInitMode == ON_DOWN) {
|
||||
startDrag(mHitPos, (int) ev.getX() - mItemX, (int) ev.getY() - mItemY);
|
||||
}
|
||||
|
||||
mIsRemoving = false;
|
||||
mCanDrag = true;
|
||||
mPositionX = 0;
|
||||
mFlingHitPos = startFlingPosition(ev);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
|
||||
final int x1 = (int) e1.getX();
|
||||
final int y1 = (int) e1.getY();
|
||||
final int x2 = (int) e2.getX();
|
||||
final int y2 = (int) e2.getY();
|
||||
final int deltaX = x2 - mItemX;
|
||||
final int deltaY = y2 - mItemY;
|
||||
|
||||
if (mCanDrag && !mDragging && (mHitPos != MISS || mFlingHitPos != MISS)) {
|
||||
if (mHitPos != MISS) {
|
||||
if (mDragInitMode == ON_DRAG && Math.abs(y2 - y1) > mTouchSlop && mSortEnabled) {
|
||||
startDrag(mHitPos, deltaX, deltaY);
|
||||
}
|
||||
else if (mDragInitMode != ON_DOWN && Math.abs(x2 - x1) > mTouchSlop && mRemoveEnabled)
|
||||
{
|
||||
mIsRemoving = true;
|
||||
startDrag(mFlingHitPos, deltaX, deltaY);
|
||||
}
|
||||
} else if (mFlingHitPos != MISS) {
|
||||
if (Math.abs(x2 - x1) > mTouchSlop && mRemoveEnabled) {
|
||||
mIsRemoving = true;
|
||||
startDrag(mFlingHitPos, deltaX, deltaY);
|
||||
} else if (Math.abs(y2 - y1) > mTouchSlop) {
|
||||
mCanDrag = false; // if started to scroll the list then
|
||||
// don't allow sorting nor fling-removing
|
||||
}
|
||||
}
|
||||
}
|
||||
// return whatever
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
// Log.d("mobeta", "lift listener long pressed");
|
||||
if (mHitPos != MISS && mDragInitMode == ON_LONG_PRESS) {
|
||||
mDslv.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
startDrag(mHitPos, mCurrX - mItemX, mCurrY - mItemY);
|
||||
}
|
||||
}
|
||||
|
||||
// complete the OnGestureListener interface
|
||||
@Override
|
||||
public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// complete the OnGestureListener interface
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent ev) {
|
||||
if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) {
|
||||
if (mClickRemoveHitPos != MISS) {
|
||||
mDslv.removeItem(mClickRemoveHitPos - mDslv.getHeaderViewsCount());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// complete the OnGestureListener interface
|
||||
@Override
|
||||
public void onShowPress(MotionEvent ev) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
private GestureDetector.OnGestureListener mFlingRemoveListener =
|
||||
new GestureDetector.SimpleOnGestureListener() {
|
||||
@Override
|
||||
public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
|
||||
float velocityY) {
|
||||
// Log.d("mobeta", "on fling remove called");
|
||||
if (mRemoveEnabled && mIsRemoving) {
|
||||
int w = mDslv.getWidth();
|
||||
int minPos = w / 5;
|
||||
if (velocityX > mFlingSpeed) {
|
||||
if (mPositionX > -minPos) {
|
||||
mDslv.stopDragWithVelocity(true, velocityX);
|
||||
}
|
||||
} else if (velocityX < -mFlingSpeed) {
|
||||
if (mPositionX < minPos) {
|
||||
mDslv.stopDragWithVelocity(true, velocityX);
|
||||
}
|
||||
}
|
||||
mIsRemoving = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -1,241 +0,0 @@
|
|||
package com.mobeta.android.dslv;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.cursoradapter.widget.CursorAdapter;
|
||||
|
||||
|
||||
/**
|
||||
* A subclass of {@link android.widget.CursorAdapter} that provides
|
||||
* reordering of the elements in the Cursor based on completed
|
||||
* drag-sort operations. The reordering is a simple mapping of
|
||||
* list positions into Cursor positions (the Cursor is unchanged).
|
||||
* To persist changes made by drag-sorts, one can retrieve the
|
||||
* mapping with the {@link #getCursorPositions()} method, which
|
||||
* returns the reordered list of Cursor positions.
|
||||
*
|
||||
* An instance of this class is passed
|
||||
* to {@link DragSortListView#setAdapter(ListAdapter)} and, since
|
||||
* this class implements the {@link DragSortListView.DragSortListener}
|
||||
* interface, it is automatically set as the DragSortListener for
|
||||
* the DragSortListView instance.
|
||||
*/
|
||||
public abstract class DragSortCursorAdapter extends CursorAdapter implements DragSortListView.DragSortListener {
|
||||
|
||||
public static final int REMOVED = -1;
|
||||
|
||||
/**
|
||||
* Key is ListView position, value is Cursor position
|
||||
*/
|
||||
private SparseIntArray mListMapping = new SparseIntArray();
|
||||
|
||||
private ArrayList<Integer> mRemovedCursorPositions = new ArrayList<Integer>();
|
||||
|
||||
public DragSortCursorAdapter(Context context, Cursor c) {
|
||||
super(context, c);
|
||||
}
|
||||
|
||||
public DragSortCursorAdapter(Context context, Cursor c, boolean autoRequery) {
|
||||
super(context, c, autoRequery);
|
||||
}
|
||||
|
||||
public DragSortCursorAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps Cursor and clears list-Cursor mapping.
|
||||
*
|
||||
* @see android.widget.CursorAdapter#swapCursor(android.database.Cursor)
|
||||
*/
|
||||
@Override
|
||||
public Cursor swapCursor(Cursor newCursor) {
|
||||
Cursor old = super.swapCursor(newCursor);
|
||||
resetMappings();
|
||||
return old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes Cursor and clears list-Cursor mapping.
|
||||
*
|
||||
* @see android.widget.CursorAdapter#changeCursor(android.database.Cursor)
|
||||
*/
|
||||
@Override
|
||||
public void changeCursor(Cursor cursor) {
|
||||
super.changeCursor(cursor);
|
||||
resetMappings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets list-cursor mapping.
|
||||
*/
|
||||
public void reset() {
|
||||
resetMappings();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void resetMappings() {
|
||||
mListMapping.clear();
|
||||
mRemovedCursorPositions.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return super.getItem(mListMapping.get(position, position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return super.getItemId(mListMapping.get(position, position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, View convertView, ViewGroup parent) {
|
||||
return super.getDropDownView(mListMapping.get(position, position), convertView, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
return super.getView(mListMapping.get(position, position), convertView, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* On drop, this updates the mapping between Cursor positions
|
||||
* and ListView positions. The Cursor is unchanged. Retrieve
|
||||
* the current mapping with {@link getCursorPositions()}.
|
||||
*
|
||||
* @see DragSortListView.DropListener#drop(int, int)
|
||||
*/
|
||||
@Override
|
||||
public void drop(int from, int to) {
|
||||
if (from != to) {
|
||||
int cursorFrom = mListMapping.get(from, from);
|
||||
|
||||
if (from > to) {
|
||||
for (int i = from; i > to; --i) {
|
||||
mListMapping.put(i, mListMapping.get(i - 1, i - 1));
|
||||
}
|
||||
} else {
|
||||
for (int i = from; i < to; ++i) {
|
||||
mListMapping.put(i, mListMapping.get(i + 1, i + 1));
|
||||
}
|
||||
}
|
||||
mListMapping.put(to, cursorFrom);
|
||||
|
||||
cleanMapping();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On remove, this updates the mapping between Cursor positions
|
||||
* and ListView positions. The Cursor is unchanged. Retrieve
|
||||
* the current mapping with {@link getCursorPositions()}.
|
||||
*
|
||||
* @see DragSortListView.RemoveListener#remove(int)
|
||||
*/
|
||||
@Override
|
||||
public void remove(int which) {
|
||||
int cursorPos = mListMapping.get(which, which);
|
||||
if (!mRemovedCursorPositions.contains(cursorPos)) {
|
||||
mRemovedCursorPositions.add(cursorPos);
|
||||
}
|
||||
|
||||
int newCount = getCount();
|
||||
for (int i = which; i < newCount; ++i) {
|
||||
mListMapping.put(i, mListMapping.get(i + 1, i + 1));
|
||||
}
|
||||
|
||||
mListMapping.delete(newCount);
|
||||
|
||||
cleanMapping();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing. Just completes DragSortListener interface.
|
||||
*/
|
||||
@Override
|
||||
public void drag(int from, int to) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unnecessary mappings from sparse array.
|
||||
*/
|
||||
private void cleanMapping() {
|
||||
ArrayList<Integer> toRemove = new ArrayList<Integer>();
|
||||
|
||||
int size = mListMapping.size();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
if (mListMapping.keyAt(i) == mListMapping.valueAt(i)) {
|
||||
toRemove.add(mListMapping.keyAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
size = toRemove.size();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
mListMapping.delete(toRemove.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return super.getCount() - mRemovedCursorPositions.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Cursor position mapped to by the provided list position
|
||||
* (given all previously handled drag-sort
|
||||
* operations).
|
||||
*
|
||||
* @param position List position
|
||||
*
|
||||
* @return The mapped-to Cursor position
|
||||
*/
|
||||
public int getCursorPosition(int position) {
|
||||
return mListMapping.get(position, position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current order of Cursor positions presented by the
|
||||
* list.
|
||||
*/
|
||||
public ArrayList<Integer> getCursorPositions() {
|
||||
ArrayList<Integer> result = new ArrayList<Integer>();
|
||||
|
||||
for (int i = 0; i < getCount(); ++i) {
|
||||
result.add(mListMapping.get(i, i));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list position mapped to by the provided Cursor position.
|
||||
* If the provided Cursor position has been removed by a drag-sort,
|
||||
* this returns {@link #REMOVED}.
|
||||
*
|
||||
* @param cursorPosition A Cursor position
|
||||
* @return The mapped-to list position or REMOVED
|
||||
*/
|
||||
public int getListPosition(int cursorPosition) {
|
||||
if (mRemovedCursorPositions.contains(cursorPosition)) {
|
||||
return REMOVED;
|
||||
}
|
||||
|
||||
int index = mListMapping.indexOfValue(cursorPosition);
|
||||
if (index < 0) {
|
||||
return cursorPosition;
|
||||
} else {
|
||||
return mListMapping.keyAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
package com.mobeta.android.dslv;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.View.MeasureSpec;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AbsListView;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Lightweight ViewGroup that wraps list items obtained from user's
|
||||
* ListAdapter. ItemView expects a single child that has a definite
|
||||
* height (i.e. the child's layout height is not MATCH_PARENT).
|
||||
* The width of
|
||||
* ItemView will always match the width of its child (that is,
|
||||
* the width MeasureSpec given to ItemView is passed directly
|
||||
* to the child, and the ItemView measured width is set to the
|
||||
* child's measured width). The height of ItemView can be anything;
|
||||
* the
|
||||
*
|
||||
*
|
||||
* The purpose of this class is to optimize slide
|
||||
* shuffle animations.
|
||||
*/
|
||||
public class DragSortItemView extends ViewGroup {
|
||||
|
||||
private int mGravity = Gravity.TOP;
|
||||
|
||||
public DragSortItemView(Context context) {
|
||||
super(context);
|
||||
|
||||
// always init with standard ListView layout params
|
||||
setLayoutParams(new AbsListView.LayoutParams(
|
||||
ViewGroup.LayoutParams.FILL_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
//setClipChildren(true);
|
||||
}
|
||||
|
||||
public void setGravity(int gravity) {
|
||||
mGravity = gravity;
|
||||
}
|
||||
|
||||
public int getGravity() {
|
||||
return mGravity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
final View child = getChildAt(0);
|
||||
|
||||
if (child == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mGravity == Gravity.TOP) {
|
||||
child.layout(0, 0, getMeasuredWidth(), child.getMeasuredHeight());
|
||||
} else {
|
||||
child.layout(0, getMeasuredHeight() - child.getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
|
||||
int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
|
||||
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
|
||||
final View child = getChildAt(0);
|
||||
if (child == null) {
|
||||
setMeasuredDimension(0, width);
|
||||
return;
|
||||
}
|
||||
|
||||
if (child.isLayoutRequested()) {
|
||||
// Always let child be as tall as it wants.
|
||||
measureChild(child, widthMeasureSpec,
|
||||
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
|
||||
}
|
||||
|
||||
if (heightMode == MeasureSpec.UNSPECIFIED) {
|
||||
ViewGroup.LayoutParams lp = getLayoutParams();
|
||||
|
||||
if (lp.height > 0) {
|
||||
height = lp.height;
|
||||
} else {
|
||||
height = child.getMeasuredHeight();
|
||||
}
|
||||
}
|
||||
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package com.mobeta.android.dslv;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.View.MeasureSpec;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.Checkable;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Lightweight ViewGroup that wraps list items obtained from user's
|
||||
* ListAdapter. ItemView expects a single child that has a definite
|
||||
* height (i.e. the child's layout height is not MATCH_PARENT).
|
||||
* The width of
|
||||
* ItemView will always match the width of its child (that is,
|
||||
* the width MeasureSpec given to ItemView is passed directly
|
||||
* to the child, and the ItemView measured width is set to the
|
||||
* child's measured width). The height of ItemView can be anything;
|
||||
* the
|
||||
*
|
||||
*
|
||||
* The purpose of this class is to optimize slide
|
||||
* shuffle animations.
|
||||
*/
|
||||
public class DragSortItemViewCheckable extends DragSortItemView implements Checkable {
|
||||
|
||||
public DragSortItemViewCheckable(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked() {
|
||||
View child = getChildAt(0);
|
||||
if (child instanceof Checkable)
|
||||
return ((Checkable) child).isChecked();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChecked(boolean checked) {
|
||||
View child = getChildAt(0);
|
||||
if (child instanceof Checkable)
|
||||
((Checkable) child).setChecked(checked);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toggle() {
|
||||
View child = getChildAt(0);
|
||||
if (child instanceof Checkable)
|
||||
((Checkable) child).toggle();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,133 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.mobeta.android.dslv;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
// taken from v4 rev. 10 ResourceCursorAdapter.java
|
||||
|
||||
/**
|
||||
* Static library support version of the framework's {@link android.widget.ResourceCursorAdapter}.
|
||||
* Used to write apps that run on platforms prior to Android 3.0. When running
|
||||
* on Android 3.0 or above, this implementation is still used; it does not try
|
||||
* to switch to the framework's implementation. See the framework SDK
|
||||
* documentation for a class overview.
|
||||
*/
|
||||
public abstract class ResourceDragSortCursorAdapter extends DragSortCursorAdapter {
|
||||
private int mLayout;
|
||||
|
||||
private int mDropDownLayout;
|
||||
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
/**
|
||||
* Constructor the enables auto-requery.
|
||||
*
|
||||
* @deprecated This option is discouraged, as it results in Cursor queries
|
||||
* being performed on the application's UI thread and thus can cause poor
|
||||
* responsiveness or even Application Not Responding errors. As an alternative,
|
||||
* use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
|
||||
*
|
||||
* @param context The context where the ListView associated with this adapter is running
|
||||
* @param layout resource identifier of a layout file that defines the views
|
||||
* for this list item. Unless you override them later, this will
|
||||
* define both the item views and the drop down views.
|
||||
*/
|
||||
@Deprecated
|
||||
public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c) {
|
||||
super(context, c);
|
||||
mLayout = mDropDownLayout = layout;
|
||||
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with default behavior as per
|
||||
* {@link CursorAdapter#CursorAdapter(Context, Cursor, boolean)}; it is recommended
|
||||
* you not use this, but instead {@link #ResourceCursorAdapter(Context, int, Cursor, int)}.
|
||||
* When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER}
|
||||
* will always be set.
|
||||
*
|
||||
* @param context The context where the ListView associated with this adapter is running
|
||||
* @param layout resource identifier of a layout file that defines the views
|
||||
* for this list item. Unless you override them later, this will
|
||||
* define both the item views and the drop down views.
|
||||
* @param c The cursor from which to get the data.
|
||||
* @param autoRequery If true the adapter will call requery() on the
|
||||
* cursor whenever it changes so the most recent
|
||||
* data is always displayed. Using true here is discouraged.
|
||||
*/
|
||||
public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c, boolean autoRequery) {
|
||||
super(context, c, autoRequery);
|
||||
mLayout = mDropDownLayout = layout;
|
||||
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard constructor.
|
||||
*
|
||||
* @param context The context where the ListView associated with this adapter is running
|
||||
* @param layout Resource identifier of a layout file that defines the views
|
||||
* for this list item. Unless you override them later, this will
|
||||
* define both the item views and the drop down views.
|
||||
* @param c The cursor from which to get the data.
|
||||
* @param flags Flags used to determine the behavior of the adapter,
|
||||
* as per {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}.
|
||||
*/
|
||||
public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
mLayout = mDropDownLayout = layout;
|
||||
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates view(s) from the specified XML file.
|
||||
*
|
||||
* @see android.widget.CursorAdapter#newView(android.content.Context,
|
||||
* android.database.Cursor, ViewGroup)
|
||||
*/
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return mInflater.inflate(mLayout, parent, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return mInflater.inflate(mDropDownLayout, parent, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the layout resource of the item views.</p>
|
||||
*
|
||||
* @param layout the layout resources used to create item views
|
||||
*/
|
||||
public void setViewResource(int layout) {
|
||||
mLayout = layout;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the layout resource of the drop down views.</p>
|
||||
*
|
||||
* @param dropDownLayout the layout resources used to create drop down views
|
||||
*/
|
||||
public void setDropDownViewResource(int dropDownLayout) {
|
||||
mDropDownLayout = dropDownLayout;
|
||||
}
|
||||
}
|
|
@ -1,422 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2006 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.mobeta.android.dslv;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ImageView;
|
||||
|
||||
// taken from sdk/sources/android-16/android/widget/SimpleCursorAdapter.java
|
||||
|
||||
/**
|
||||
* An easy adapter to map columns from a cursor to TextViews or ImageViews
|
||||
* defined in an XML file. You can specify which columns you want, which
|
||||
* views you want to display the columns, and the XML file that defines
|
||||
* the appearance of these views.
|
||||
*
|
||||
* Binding occurs in two phases. First, if a
|
||||
* {@link android.widget.SimpleCursorAdapter.ViewBinder} is available,
|
||||
* {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
|
||||
* is invoked. If the returned value is true, binding has occured. If the
|
||||
* returned value is false and the view to bind is a TextView,
|
||||
* {@link #setViewText(TextView, String)} is invoked. If the returned value
|
||||
* is false and the view to bind is an ImageView,
|
||||
* {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
|
||||
* binding can be found, an {@link IllegalStateException} is thrown.
|
||||
*
|
||||
* If this adapter is used with filtering, for instance in an
|
||||
* {@link android.widget.AutoCompleteTextView}, you can use the
|
||||
* {@link android.widget.SimpleCursorAdapter.CursorToStringConverter} and the
|
||||
* {@link android.widget.FilterQueryProvider} interfaces
|
||||
* to get control over the filtering process. You can refer to
|
||||
* {@link #convertToString(android.database.Cursor)} and
|
||||
* {@link #runQueryOnBackgroundThread(CharSequence)} for more information.
|
||||
*/
|
||||
public class SimpleDragSortCursorAdapter extends ResourceDragSortCursorAdapter {
|
||||
/**
|
||||
* A list of columns containing the data to bind to the UI.
|
||||
* This field should be made private, so it is hidden from the SDK.
|
||||
* {@hide}
|
||||
*/
|
||||
protected int[] mFrom;
|
||||
/**
|
||||
* A list of View ids representing the views to which the data must be bound.
|
||||
* This field should be made private, so it is hidden from the SDK.
|
||||
* {@hide}
|
||||
*/
|
||||
protected int[] mTo;
|
||||
|
||||
private int mStringConversionColumn = -1;
|
||||
private CursorToStringConverter mCursorToStringConverter;
|
||||
private ViewBinder mViewBinder;
|
||||
|
||||
String[] mOriginalFrom;
|
||||
|
||||
/**
|
||||
* Constructor the enables auto-requery.
|
||||
*
|
||||
* @deprecated This option is discouraged, as it results in Cursor queries
|
||||
* being performed on the application's UI thread and thus can cause poor
|
||||
* responsiveness or even Application Not Responding errors. As an alternative,
|
||||
* use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
|
||||
*/
|
||||
@Deprecated
|
||||
public SimpleDragSortCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
|
||||
super(context, layout, c);
|
||||
mTo = to;
|
||||
mOriginalFrom = from;
|
||||
findColumns(c, from);
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard constructor.
|
||||
*
|
||||
* @param context The context where the ListView associated with this
|
||||
* SimpleListItemFactory is running
|
||||
* @param layout resource identifier of a layout file that defines the views
|
||||
* for this list item. The layout file should include at least
|
||||
* those named views defined in "to"
|
||||
* @param c The database cursor. Can be null if the cursor is not available yet.
|
||||
* @param from A list of column names representing the data to bind to the UI. Can be null
|
||||
* if the cursor is not available yet.
|
||||
* @param to The views that should display column in the "from" parameter.
|
||||
* These should all be TextViews. The first N views in this list
|
||||
* are given the values of the first N columns in the from
|
||||
* parameter. Can be null if the cursor is not available yet.
|
||||
* @param flags Flags used to determine the behavior of the adapter,
|
||||
* as per {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}.
|
||||
*/
|
||||
public SimpleDragSortCursorAdapter(Context context, int layout,
|
||||
Cursor c, String[] from, int[] to, int flags) {
|
||||
super(context, layout, c, flags);
|
||||
mTo = to;
|
||||
mOriginalFrom = from;
|
||||
findColumns(c, from);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds all of the field names passed into the "to" parameter of the
|
||||
* constructor with their corresponding cursor columns as specified in the
|
||||
* "from" parameter.
|
||||
*
|
||||
* Binding occurs in two phases. First, if a
|
||||
* {@link android.widget.SimpleCursorAdapter.ViewBinder} is available,
|
||||
* {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
|
||||
* is invoked. If the returned value is true, binding has occured. If the
|
||||
* returned value is false and the view to bind is a TextView,
|
||||
* {@link #setViewText(TextView, String)} is invoked. If the returned value is
|
||||
* false and the view to bind is an ImageView,
|
||||
* {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
|
||||
* binding can be found, an {@link IllegalStateException} is thrown.
|
||||
*
|
||||
* @throws IllegalStateException if binding cannot occur
|
||||
*
|
||||
* @see android.widget.CursorAdapter#bindView(android.view.View,
|
||||
* android.content.Context, android.database.Cursor)
|
||||
* @see #getViewBinder()
|
||||
* @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
|
||||
* @see #setViewImage(ImageView, String)
|
||||
* @see #setViewText(TextView, String)
|
||||
*/
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
final ViewBinder binder = mViewBinder;
|
||||
final int count = mTo.length;
|
||||
final int[] from = mFrom;
|
||||
final int[] to = mTo;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
final View v = view.findViewById(to[i]);
|
||||
if (v != null) {
|
||||
boolean bound = false;
|
||||
if (binder != null) {
|
||||
bound = binder.setViewValue(v, cursor, from[i]);
|
||||
}
|
||||
|
||||
if (!bound) {
|
||||
String text = cursor.getString(from[i]);
|
||||
if (text == null) {
|
||||
text = "";
|
||||
}
|
||||
|
||||
if (v instanceof TextView) {
|
||||
setViewText((TextView) v, text);
|
||||
} else if (v instanceof ImageView) {
|
||||
setViewImage((ImageView) v, text);
|
||||
} else {
|
||||
throw new IllegalStateException(v.getClass().getName() + " is not a " +
|
||||
" view that can be bounds by this SimpleCursorAdapter");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ViewBinder} used to bind data to views.
|
||||
*
|
||||
* @return a ViewBinder or null if the binder does not exist
|
||||
*
|
||||
* @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
|
||||
* @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
|
||||
*/
|
||||
public ViewBinder getViewBinder() {
|
||||
return mViewBinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the binder used to bind data to views.
|
||||
*
|
||||
* @param viewBinder the binder used to bind data to views, can be null to
|
||||
* remove the existing binder
|
||||
*
|
||||
* @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
|
||||
* @see #getViewBinder()
|
||||
*/
|
||||
public void setViewBinder(ViewBinder viewBinder) {
|
||||
mViewBinder = viewBinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by bindView() to set the image for an ImageView but only if
|
||||
* there is no existing ViewBinder or if the existing ViewBinder cannot
|
||||
* handle binding to an ImageView.
|
||||
*
|
||||
* By default, the value will be treated as an image resource. If the
|
||||
* value cannot be used as an image resource, the value is used as an
|
||||
* image Uri.
|
||||
*
|
||||
* Intended to be overridden by Adapters that need to filter strings
|
||||
* retrieved from the database.
|
||||
*
|
||||
* @param v ImageView to receive an image
|
||||
* @param value the value retrieved from the cursor
|
||||
*/
|
||||
public void setViewImage(ImageView v, String value) {
|
||||
try {
|
||||
v.setImageResource(Integer.parseInt(value));
|
||||
} catch (NumberFormatException nfe) {
|
||||
v.setImageURI(Uri.parse(value));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by bindView() to set the text for a TextView but only if
|
||||
* there is no existing ViewBinder or if the existing ViewBinder cannot
|
||||
* handle binding to a TextView.
|
||||
*
|
||||
* Intended to be overridden by Adapters that need to filter strings
|
||||
* retrieved from the database.
|
||||
*
|
||||
* @param v TextView to receive text
|
||||
* @param text the text to be set for the TextView
|
||||
*/
|
||||
public void setViewText(TextView v, String text) {
|
||||
v.setText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index of the column used to get a String representation
|
||||
* of the Cursor.
|
||||
*
|
||||
* @return a valid index in the current Cursor or -1
|
||||
*
|
||||
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
|
||||
* @see #setStringConversionColumn(int)
|
||||
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
|
||||
* @see #getCursorToStringConverter()
|
||||
*/
|
||||
public int getStringConversionColumn() {
|
||||
return mStringConversionColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the index of the column in the Cursor used to get a String
|
||||
* representation of that Cursor. The column is used to convert the
|
||||
* Cursor to a String only when the current CursorToStringConverter
|
||||
* is null.
|
||||
*
|
||||
* @param stringConversionColumn a valid index in the current Cursor or -1 to use the default
|
||||
* conversion mechanism
|
||||
*
|
||||
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
|
||||
* @see #getStringConversionColumn()
|
||||
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
|
||||
* @see #getCursorToStringConverter()
|
||||
*/
|
||||
public void setStringConversionColumn(int stringConversionColumn) {
|
||||
mStringConversionColumn = stringConversionColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the converter used to convert the filtering Cursor
|
||||
* into a String.
|
||||
*
|
||||
* @return null if the converter does not exist or an instance of
|
||||
* {@link android.widget.SimpleCursorAdapter.CursorToStringConverter}
|
||||
*
|
||||
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
|
||||
* @see #getStringConversionColumn()
|
||||
* @see #setStringConversionColumn(int)
|
||||
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
|
||||
*/
|
||||
public CursorToStringConverter getCursorToStringConverter() {
|
||||
return mCursorToStringConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the converter used to convert the filtering Cursor
|
||||
* into a String.
|
||||
*
|
||||
* @param cursorToStringConverter the Cursor to String converter, or
|
||||
* null to remove the converter
|
||||
*
|
||||
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
|
||||
* @see #getStringConversionColumn()
|
||||
* @see #setStringConversionColumn(int)
|
||||
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
|
||||
*/
|
||||
public void setCursorToStringConverter(CursorToStringConverter cursorToStringConverter) {
|
||||
mCursorToStringConverter = cursorToStringConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a CharSequence representation of the specified Cursor as defined
|
||||
* by the current CursorToStringConverter. If no CursorToStringConverter
|
||||
* has been set, the String conversion column is used instead. If the
|
||||
* conversion column is -1, the returned String is empty if the cursor
|
||||
* is null or Cursor.toString().
|
||||
*
|
||||
* @param cursor the Cursor to convert to a CharSequence
|
||||
*
|
||||
* @return a non-null CharSequence representing the cursor
|
||||
*/
|
||||
@Override
|
||||
public CharSequence convertToString(Cursor cursor) {
|
||||
if (mCursorToStringConverter != null) {
|
||||
return mCursorToStringConverter.convertToString(cursor);
|
||||
} else if (mStringConversionColumn > -1) {
|
||||
return cursor.getString(mStringConversionColumn);
|
||||
}
|
||||
|
||||
return super.convertToString(cursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a map from an array of strings to an array of column-id integers in cursor c.
|
||||
* If c is null, the array will be discarded.
|
||||
*
|
||||
* @param c the cursor to find the columns from
|
||||
* @param from the Strings naming the columns of interest
|
||||
*/
|
||||
private void findColumns(Cursor c, String[] from) {
|
||||
if (c != null) {
|
||||
int i;
|
||||
int count = from.length;
|
||||
if (mFrom == null || mFrom.length != count) {
|
||||
mFrom = new int[count];
|
||||
}
|
||||
for (i = 0; i < count; i++) {
|
||||
mFrom[i] = c.getColumnIndexOrThrow(from[i]);
|
||||
}
|
||||
} else {
|
||||
mFrom = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor swapCursor(Cursor c) {
|
||||
// super.swapCursor() will notify observers before we have
|
||||
// a valid mapping, make sure we have a mapping before this
|
||||
// happens
|
||||
findColumns(c, mOriginalFrom);
|
||||
return super.swapCursor(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the cursor and change the column-to-view mappings at the same time.
|
||||
*
|
||||
* @param c The database cursor. Can be null if the cursor is not available yet.
|
||||
* @param from A list of column names representing the data to bind to the UI. Can be null
|
||||
* if the cursor is not available yet.
|
||||
* @param to The views that should display column in the "from" parameter.
|
||||
* These should all be TextViews. The first N views in this list
|
||||
* are given the values of the first N columns in the from
|
||||
* parameter. Can be null if the cursor is not available yet.
|
||||
*/
|
||||
public void changeCursorAndColumns(Cursor c, String[] from, int[] to) {
|
||||
mOriginalFrom = from;
|
||||
mTo = to;
|
||||
// super.changeCursor() will notify observers before we have
|
||||
// a valid mapping, make sure we have a mapping before this
|
||||
// happens
|
||||
findColumns(c, mOriginalFrom);
|
||||
super.changeCursor(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class can be used by external clients of SimpleCursorAdapter
|
||||
* to bind values fom the Cursor to views.
|
||||
*
|
||||
* You should use this class to bind values from the Cursor to views
|
||||
* that are not directly supported by SimpleCursorAdapter or to
|
||||
* change the way binding occurs for views supported by
|
||||
* SimpleCursorAdapter.
|
||||
*
|
||||
* @see SimpleCursorAdapter#bindView(android.view.View, android.content.Context, android.database.Cursor)
|
||||
* @see SimpleCursorAdapter#setViewImage(ImageView, String)
|
||||
* @see SimpleCursorAdapter#setViewText(TextView, String)
|
||||
*/
|
||||
public static interface ViewBinder {
|
||||
/**
|
||||
* Binds the Cursor column defined by the specified index to the specified view.
|
||||
*
|
||||
* When binding is handled by this ViewBinder, this method must return true.
|
||||
* If this method returns false, SimpleCursorAdapter will attempts to handle
|
||||
* the binding on its own.
|
||||
*
|
||||
* @param view the view to bind the data to
|
||||
* @param cursor the cursor to get the data from
|
||||
* @param columnIndex the column at which the data can be found in the cursor
|
||||
*
|
||||
* @return true if the data was bound to the view, false otherwise
|
||||
*/
|
||||
boolean setViewValue(View view, Cursor cursor, int columnIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class can be used by external clients of SimpleCursorAdapter
|
||||
* to define how the Cursor should be converted to a String.
|
||||
*
|
||||
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
|
||||
*/
|
||||
public static interface CursorToStringConverter {
|
||||
/**
|
||||
* Returns a CharSequence representing the specified Cursor.
|
||||
*
|
||||
* @param cursor the cursor for which a CharSequence representation
|
||||
* is requested
|
||||
*
|
||||
* @return a non-null CharSequence representing the cursor
|
||||
*/
|
||||
CharSequence convertToString(Cursor cursor);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
package com.mobeta.android.dslv;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Color;
|
||||
import android.widget.ListView;
|
||||
import android.widget.ImageView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Simple implementation of the FloatViewManager class. Uses list
|
||||
* items as they appear in the ListView to create the floating View.
|
||||
*/
|
||||
public class SimpleFloatViewManager implements DragSortListView.FloatViewManager {
|
||||
|
||||
private Bitmap mFloatBitmap;
|
||||
|
||||
private ImageView mImageView;
|
||||
|
||||
private int mFloatBGColor = Color.BLACK;
|
||||
|
||||
private ListView mListView;
|
||||
|
||||
public SimpleFloatViewManager(ListView lv) {
|
||||
mListView = lv;
|
||||
}
|
||||
|
||||
public void setBackgroundColor(int color) {
|
||||
mFloatBGColor = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* This simple implementation creates a Bitmap copy of the
|
||||
* list item currently shown at ListView <code>position</code>.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateFloatView(int position) {
|
||||
// Guaranteed that this will not be null? I think so. Nope, got
|
||||
// a NullPointerException once...
|
||||
View v = mListView.getChildAt(position + mListView.getHeaderViewsCount() - mListView.getFirstVisiblePosition());
|
||||
|
||||
if (v == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
v.setPressed(false);
|
||||
|
||||
// Create a copy of the drawing cache so that it does not get
|
||||
// recycled by the framework when the list tries to clean up memory
|
||||
//v.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
|
||||
v.setDrawingCacheEnabled(true);
|
||||
mFloatBitmap = Bitmap.createBitmap(v.getDrawingCache());
|
||||
v.setDrawingCacheEnabled(false);
|
||||
|
||||
if (mImageView == null) {
|
||||
mImageView = new ImageView(mListView.getContext());
|
||||
}
|
||||
mImageView.setBackgroundColor(mFloatBGColor);
|
||||
mImageView.setPadding(0, 0, 0, 0);
|
||||
mImageView.setImageBitmap(mFloatBitmap);
|
||||
mImageView.setLayoutParams(new ViewGroup.LayoutParams(v.getWidth(), v.getHeight()));
|
||||
|
||||
return mImageView;
|
||||
}
|
||||
|
||||
/**
|
||||
* This does nothing
|
||||
*/
|
||||
@Override
|
||||
public void onDragFloatView(View floatView, Point position, Point touch) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the Bitmap from the ImageView created in
|
||||
* onCreateFloatView() and tells the system to recycle it.
|
||||
*/
|
||||
@Override
|
||||
public void onDestroyFloatView(View floatView) {
|
||||
((ImageView) floatView).setImageDrawable(null);
|
||||
|
||||
mFloatBitmap.recycle();
|
||||
mFloatBitmap = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="DragSortListView">
|
||||
<attr name="collapsed_height" format="dimension" />
|
||||
<attr name="drag_scroll_start" format="float" />
|
||||
<attr name="max_drag_scroll_speed" format="float" />
|
||||
<attr name="float_background_color" format="color" />
|
||||
<attr name="remove_mode">
|
||||
<enum name="clickRemove" value="0" />
|
||||
<enum name="flingRemove" value="1" />
|
||||
</attr>
|
||||
<attr name="track_drag_sort" format="boolean"/>
|
||||
<attr name="float_alpha" format="float"/>
|
||||
<attr name="slide_shuffle_speed" format="float"/>
|
||||
<attr name="remove_animation_duration" format="integer"/>
|
||||
<attr name="drop_animation_duration" format="integer"/>
|
||||
<attr name="drag_enabled" format="boolean" />
|
||||
<attr name="sort_enabled" format="boolean" />
|
||||
<attr name="remove_enabled" format="boolean" />
|
||||
<attr name="drag_start_mode">
|
||||
<enum name="onDown" value="0" />
|
||||
<enum name="onMove" value="1" />
|
||||
<enum name="onLongPress" value="2"/>
|
||||
</attr>
|
||||
<attr name="drag_handle_id" format="integer" />
|
||||
<attr name="fling_handle_id" format="integer" />
|
||||
<attr name="click_remove_id" format="integer" />
|
||||
<attr name="use_default_controller" format="boolean" />
|
||||
</declare-styleable>
|
||||
</resources>
|
|
@ -1,8 +0,0 @@
|
|||
apply from: bootstrap.androidModule
|
||||
|
||||
android {
|
||||
lintOptions {
|
||||
baselineFile file("lint-baseline.xml")
|
||||
abortOnError true
|
||||
}
|
||||
}
|
|
@ -1,246 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<issues format="4" by="lint 2.3.3">
|
||||
|
||||
<issue
|
||||
id="InlinedApi"
|
||||
message="Field requires API level 17 (current min is 14): `android.view.View#LAYOUT_DIRECTION_RTL`"
|
||||
errorLine1=" if (mSlideDrawable != null) mSlideDrawable.setIsRtl(layoutDirection == LAYOUT_DIRECTION_RTL);"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/MenuDrawer.java"
|
||||
line="882"
|
||||
column="80"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="InlinedApi"
|
||||
message="Field requires API level 17 (current min is 14): `android.view.View#LAYOUT_DIRECTION_RTL`"
|
||||
errorLine1=" mSlideDrawable.setIsRtl(ViewHelper.getLayoutDirection(this) == LAYOUT_DIRECTION_RTL);"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/MenuDrawer.java"
|
||||
line="1325"
|
||||
column="72"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="OldTargetApi"
|
||||
message="Not targeting the latest versions of Android; compatibility modes apply. Consider testing and updating this version. Consult the `android.os.Build.VERSION_CODES` javadoc for details."
|
||||
errorLine1=" <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="16" />"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/AndroidManifest.xml"
|
||||
line="7"
|
||||
column="41"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="GradleOverrides"
|
||||
message="This `minSdkVersion` value (`7`) is not used; it is always overridden by the value specified in the Gradle build script (`14`)"
|
||||
errorLine1=" <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="16" />"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/AndroidManifest.xml"
|
||||
line="7"
|
||||
column="15"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="GradleOverrides"
|
||||
message="This `targetSdkVersion` value (`16`) is not used; it is always overridden by the value specified in the Gradle build script (`22`)"
|
||||
errorLine1=" <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="16" />"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/AndroidManifest.xml"
|
||||
line="7"
|
||||
column="41"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ParcelClassLoader"
|
||||
message="Using the default class loader will not work if you are restoring your own classes. Consider using for example `readBundle(getClass().getClassLoader())` instead."
|
||||
errorLine1=" mState = in.readBundle();"
|
||||
errorLine2=" ~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/MenuDrawer.java"
|
||||
line="1630"
|
||||
column="25"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is never < 14"
|
||||
errorLine1=" if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="41"
|
||||
column="28"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="43"
|
||||
column="20"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is never < 14"
|
||||
errorLine1=" if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="51"
|
||||
column="28"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="53"
|
||||
column="20"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is never < 14"
|
||||
errorLine1=" if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="59"
|
||||
column="28"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="61"
|
||||
column="20"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is never < 14"
|
||||
errorLine1=" if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="67"
|
||||
column="28"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="69"
|
||||
column="20"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is never < 14"
|
||||
errorLine1=" if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="77"
|
||||
column="28"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="79"
|
||||
column="20"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/DraggableDrawer.java"
|
||||
line="572"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/DraggableDrawer.java"
|
||||
line="580"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/DraggableDrawer.java"
|
||||
line="588"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/DraggableDrawer.java"
|
||||
line="596"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="FloatMath"
|
||||
message="Use `java.lang.Math#sqrt` instead of `android.util.FloatMath#sqrt()` since it is faster as of API 8"
|
||||
errorLine1=" float hyp = FloatMath.sqrt(dx * dx + dy * dy);"
|
||||
errorLine2=" ~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/Scroller.java"
|
||||
line="374"
|
||||
column="25"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="FloatMath"
|
||||
message="Use `java.lang.Math#sqrt` instead of `android.util.FloatMath#sqrt()` since it is faster as of API 8"
|
||||
errorLine1=" float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);"
|
||||
errorLine2=" ~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/Scroller.java"
|
||||
line="391"
|
||||
column="26"/>
|
||||
</issue>
|
||||
|
||||
</issues>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="net.simonvt.menudrawer"
|
||||
android:versionCode="6"
|
||||
android:versionName="3.0.2">
|
||||
</manifest>
|
|
@ -1,99 +0,0 @@
|
|||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
/**
|
||||
* FrameLayout which caches the hardware layer if available.
|
||||
* <p/>
|
||||
* If it's not posted twice the layer either wont be built on start, or it'll be built twice.
|
||||
*/
|
||||
class BuildLayerFrameLayout extends FrameLayout {
|
||||
|
||||
private boolean mChanged;
|
||||
|
||||
private boolean mHardwareLayersEnabled = true;
|
||||
|
||||
private boolean mAttached;
|
||||
|
||||
private boolean mFirst = true;
|
||||
|
||||
public BuildLayerFrameLayout(Context context) {
|
||||
super(context);
|
||||
if (MenuDrawer.USE_TRANSLATIONS) {
|
||||
setLayerType(LAYER_TYPE_HARDWARE, null);
|
||||
}
|
||||
}
|
||||
|
||||
public BuildLayerFrameLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
if (MenuDrawer.USE_TRANSLATIONS) {
|
||||
setLayerType(LAYER_TYPE_HARDWARE, null);
|
||||
}
|
||||
}
|
||||
|
||||
public BuildLayerFrameLayout(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
if (MenuDrawer.USE_TRANSLATIONS) {
|
||||
setLayerType(LAYER_TYPE_HARDWARE, null);
|
||||
}
|
||||
}
|
||||
|
||||
void setHardwareLayersEnabled(boolean enabled) {
|
||||
mHardwareLayersEnabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
mAttached = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
mAttached = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
|
||||
if (MenuDrawer.USE_TRANSLATIONS && mHardwareLayersEnabled) {
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mChanged = true;
|
||||
invalidate();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
super.dispatchDraw(canvas);
|
||||
|
||||
if (mChanged && MenuDrawer.USE_TRANSLATIONS) {
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mAttached) {
|
||||
final int layerType = getLayerType();
|
||||
// If it's already a hardware layer, it'll be built anyway.
|
||||
if (layerType != LAYER_TYPE_HARDWARE || mFirst) {
|
||||
mFirst = false;
|
||||
setLayerType(LAYER_TYPE_HARDWARE, null);
|
||||
buildLayer();
|
||||
setLayerType(LAYER_TYPE_NONE, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mChanged = false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
/**
|
||||
* A specialized Drawable that fills the Canvas with a specified color.
|
||||
* Note that a ColorDrawable ignores the ColorFilter.
|
||||
* <p/>
|
||||
* <p>It can be defined in an XML file with the <code><color></code> element.</p>
|
||||
*
|
||||
* @attr ref android.R.styleable#ColorDrawable_color
|
||||
*/
|
||||
class ColorDrawable extends Drawable {
|
||||
|
||||
private ColorState mState;
|
||||
private final Paint mPaint = new Paint();
|
||||
|
||||
/** Creates a new black ColorDrawable. */
|
||||
public ColorDrawable() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ColorDrawable with the specified color.
|
||||
*
|
||||
* @param color The color to draw.
|
||||
*/
|
||||
public ColorDrawable(int color) {
|
||||
this(null);
|
||||
setColor(color);
|
||||
}
|
||||
|
||||
private ColorDrawable(ColorState state) {
|
||||
mState = new ColorState(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChangingConfigurations() {
|
||||
return super.getChangingConfigurations() | mState.mChangingConfigurations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
if ((mState.mUseColor >>> 24) != 0) {
|
||||
mPaint.setColor(mState.mUseColor);
|
||||
canvas.drawRect(getBounds(), mPaint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the drawable's color value.
|
||||
*
|
||||
* @return int The color to draw.
|
||||
*/
|
||||
public int getColor() {
|
||||
return mState.mUseColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the drawable's color value. This action will clobber the results of prior calls to
|
||||
* {@link #setAlpha(int)} on this object, which side-affected the underlying color.
|
||||
*
|
||||
* @param color The color to draw.
|
||||
*/
|
||||
public void setColor(int color) {
|
||||
if (mState.mBaseColor != color || mState.mUseColor != color) {
|
||||
invalidateSelf();
|
||||
mState.mBaseColor = mState.mUseColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the alpha value of this drawable's color.
|
||||
*
|
||||
* @return A value between 0 and 255.
|
||||
*/
|
||||
public int getAlpha() {
|
||||
return mState.mUseColor >>> 24;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color's alpha value.
|
||||
*
|
||||
* @param alpha The alpha value to set, between 0 and 255.
|
||||
*/
|
||||
public void setAlpha(int alpha) {
|
||||
alpha += alpha >> 7; // make it 0..256
|
||||
int baseAlpha = mState.mBaseColor >>> 24;
|
||||
int useAlpha = baseAlpha * alpha >> 8;
|
||||
int oldUseColor = mState.mUseColor;
|
||||
mState.mUseColor = (mState.mBaseColor << 8 >>> 8) | (useAlpha << 24);
|
||||
if (oldUseColor != mState.mUseColor) {
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting a color filter on a ColorDrawable has no effect.
|
||||
*
|
||||
* @param colorFilter Ignore.
|
||||
*/
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
}
|
||||
|
||||
public int getOpacity() {
|
||||
switch (mState.mUseColor >>> 24) {
|
||||
case 255:
|
||||
return PixelFormat.OPAQUE;
|
||||
case 0:
|
||||
return PixelFormat.TRANSPARENT;
|
||||
}
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConstantState getConstantState() {
|
||||
mState.mChangingConfigurations = getChangingConfigurations();
|
||||
return mState;
|
||||
}
|
||||
|
||||
static final class ColorState extends ConstantState {
|
||||
|
||||
int mBaseColor; // base color, independent of setAlpha()
|
||||
int mUseColor; // basecolor modulated by setAlpha()
|
||||
int mChangingConfigurations;
|
||||
|
||||
ColorState(ColorState state) {
|
||||
if (state != null) {
|
||||
mBaseColor = state.mBaseColor;
|
||||
mUseColor = state.mUseColor;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable newDrawable() {
|
||||
return new ColorDrawable(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable newDrawable(Resources res) {
|
||||
return new ColorDrawable(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChangingConfigurations() {
|
||||
return mChangingConfigurations;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,619 +0,0 @@
|
|||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.os.SystemClock;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
public abstract class DraggableDrawer extends MenuDrawer {
|
||||
|
||||
/**
|
||||
* Key used when saving menu visibility state.
|
||||
*/
|
||||
private static final String STATE_MENU_VISIBLE = "net.simonvt.menudrawer.MenuDrawer.menuVisible";
|
||||
|
||||
/**
|
||||
* Interpolator used for peeking at the drawer.
|
||||
*/
|
||||
private static final Interpolator PEEK_INTERPOLATOR = new PeekInterpolator();
|
||||
|
||||
/**
|
||||
* The maximum alpha of the dark menu overlay used for dimming the menu.
|
||||
*/
|
||||
protected static final int MAX_MENU_OVERLAY_ALPHA = 185;
|
||||
|
||||
/**
|
||||
* Default delay from {@link #peekDrawer()} is called until first animation is run.
|
||||
*/
|
||||
private static final long DEFAULT_PEEK_START_DELAY = 5000;
|
||||
|
||||
/**
|
||||
* Default delay between each subsequent animation, after {@link #peekDrawer()} has been called.
|
||||
*/
|
||||
private static final long DEFAULT_PEEK_DELAY = 10000;
|
||||
|
||||
/**
|
||||
* The duration of the peek animation.
|
||||
*/
|
||||
protected static final int PEEK_DURATION = 5000;
|
||||
|
||||
/**
|
||||
* Distance in dp from closed position from where the drawer is considered closed with regards to touch events.
|
||||
*/
|
||||
private static final int CLOSE_ENOUGH = 3;
|
||||
|
||||
protected static final int INVALID_POINTER = -1;
|
||||
|
||||
/**
|
||||
* Slop before starting a drag.
|
||||
*/
|
||||
protected int mTouchSlop;
|
||||
|
||||
/**
|
||||
* Runnable used when the peek animation is running.
|
||||
*/
|
||||
protected final Runnable mPeekRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
peekDrawerInvalidate();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Runnable used when animating the drawer open/closed.
|
||||
*/
|
||||
private final Runnable mDragRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
postAnimationInvalidate();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates whether the drawer is currently being dragged.
|
||||
*/
|
||||
protected boolean mIsDragging;
|
||||
|
||||
/**
|
||||
* The current pointer id.
|
||||
*/
|
||||
protected int mActivePointerId = INVALID_POINTER;
|
||||
|
||||
/**
|
||||
* The initial X position of a drag.
|
||||
*/
|
||||
protected float mInitialMotionX;
|
||||
|
||||
/**
|
||||
* The initial Y position of a drag.
|
||||
*/
|
||||
protected float mInitialMotionY;
|
||||
|
||||
/**
|
||||
* The last X position of a drag.
|
||||
*/
|
||||
protected float mLastMotionX = -1;
|
||||
|
||||
/**
|
||||
* The last Y position of a drag.
|
||||
*/
|
||||
protected float mLastMotionY = -1;
|
||||
|
||||
/**
|
||||
* Default delay between each subsequent animation, after {@link #peekDrawer()} has been called.
|
||||
*/
|
||||
protected long mPeekDelay;
|
||||
|
||||
/**
|
||||
* Scroller used for the peek drawer animation.
|
||||
*/
|
||||
protected Scroller mPeekScroller;
|
||||
|
||||
/**
|
||||
* Velocity tracker used when animating the drawer open/closed after a drag.
|
||||
*/
|
||||
protected VelocityTracker mVelocityTracker;
|
||||
|
||||
/**
|
||||
* Maximum velocity allowed when animating the drawer open/closed.
|
||||
*/
|
||||
protected int mMaxVelocity;
|
||||
|
||||
/**
|
||||
* Indicates whether the menu should be offset when dragging the drawer.
|
||||
*/
|
||||
protected boolean mOffsetMenu = true;
|
||||
|
||||
/**
|
||||
* Distance in px from closed position from where the drawer is considered closed with regards to touch events.
|
||||
*/
|
||||
protected int mCloseEnough;
|
||||
|
||||
/**
|
||||
* Runnable used for first call to {@link #startPeek()} after {@link #peekDrawer()} has been called.
|
||||
*/
|
||||
private Runnable mPeekStartRunnable;
|
||||
|
||||
/**
|
||||
* Scroller used when animating the drawer open/closed.
|
||||
*/
|
||||
private Scroller mScroller;
|
||||
|
||||
/**
|
||||
* Indicates whether the current layer type is {@link android.view.View#LAYER_TYPE_HARDWARE}.
|
||||
*/
|
||||
protected boolean mLayerTypeHardware;
|
||||
|
||||
DraggableDrawer(Activity activity, int dragMode) {
|
||||
super(activity, dragMode);
|
||||
}
|
||||
|
||||
public DraggableDrawer(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public DraggableDrawer(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public DraggableDrawer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super.initDrawer(context, attrs, defStyle);
|
||||
|
||||
final ViewConfiguration configuration = ViewConfiguration.get(context);
|
||||
mTouchSlop = configuration.getScaledTouchSlop();
|
||||
mMaxVelocity = configuration.getScaledMaximumFlingVelocity();
|
||||
|
||||
mScroller = new Scroller(context, MenuDrawer.SMOOTH_INTERPOLATOR);
|
||||
mPeekScroller = new Scroller(context, DraggableDrawer.PEEK_INTERPOLATOR);
|
||||
|
||||
mCloseEnough = dpToPx(DraggableDrawer.CLOSE_ENOUGH);
|
||||
}
|
||||
|
||||
public void toggleMenu(boolean animate) {
|
||||
if (mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING) {
|
||||
closeMenu(animate);
|
||||
} else if (mDrawerState == STATE_CLOSED || mDrawerState == STATE_CLOSING) {
|
||||
openMenu(animate);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMenuVisible() {
|
||||
return mMenuVisible;
|
||||
}
|
||||
|
||||
public void setMenuSize(final int size) {
|
||||
mMenuSize = size;
|
||||
if (mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING) {
|
||||
setOffsetPixels(mMenuSize);
|
||||
}
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setOffsetMenuEnabled(boolean offsetMenu) {
|
||||
if (offsetMenu != mOffsetMenu) {
|
||||
mOffsetMenu = offsetMenu;
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getOffsetMenuEnabled() {
|
||||
return mOffsetMenu;
|
||||
}
|
||||
|
||||
public void peekDrawer() {
|
||||
peekDrawer(DEFAULT_PEEK_START_DELAY, DEFAULT_PEEK_DELAY);
|
||||
}
|
||||
|
||||
public void peekDrawer(long delay) {
|
||||
peekDrawer(DEFAULT_PEEK_START_DELAY, delay);
|
||||
}
|
||||
|
||||
public void peekDrawer(final long startDelay, final long delay) {
|
||||
if (startDelay < 0) {
|
||||
throw new IllegalArgumentException("startDelay must be zero or larger.");
|
||||
}
|
||||
if (delay < 0) {
|
||||
throw new IllegalArgumentException("delay must be zero or larger");
|
||||
}
|
||||
|
||||
removeCallbacks(mPeekRunnable);
|
||||
removeCallbacks(mPeekStartRunnable);
|
||||
|
||||
mPeekDelay = delay;
|
||||
mPeekStartRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
startPeek();
|
||||
}
|
||||
};
|
||||
postDelayed(mPeekStartRunnable, startDelay);
|
||||
}
|
||||
|
||||
public void setHardwareLayerEnabled(boolean enabled) {
|
||||
if (enabled != mHardwareLayersEnabled) {
|
||||
mHardwareLayersEnabled = enabled;
|
||||
mMenuContainer.setHardwareLayersEnabled(enabled);
|
||||
mContentContainer.setHardwareLayersEnabled(enabled);
|
||||
stopLayerTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
public int getTouchMode() {
|
||||
return mTouchMode;
|
||||
}
|
||||
|
||||
public void setTouchMode(int mode) {
|
||||
if (mTouchMode != mode) {
|
||||
mTouchMode = mode;
|
||||
updateTouchAreaSize();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTouchBezelSize(int size) {
|
||||
mTouchBezelSize = size;
|
||||
}
|
||||
|
||||
public int getTouchBezelSize() {
|
||||
return mTouchBezelSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* If possible, set the layer type to {@link android.view.View#LAYER_TYPE_HARDWARE}.
|
||||
*/
|
||||
protected void startLayerTranslation() {
|
||||
if (USE_TRANSLATIONS && mHardwareLayersEnabled && !mLayerTypeHardware) {
|
||||
mLayerTypeHardware = true;
|
||||
mContentContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
mMenuContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the current layer type is {@link android.view.View#LAYER_TYPE_HARDWARE}, this will set it to
|
||||
* {@link View#LAYER_TYPE_NONE}.
|
||||
*/
|
||||
protected void stopLayerTranslation() {
|
||||
if (mLayerTypeHardware) {
|
||||
mLayerTypeHardware = false;
|
||||
mContentContainer.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
mMenuContainer.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a drag has been ended.
|
||||
*/
|
||||
protected void endDrag() {
|
||||
mIsDragging = false;
|
||||
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops ongoing animation of the drawer.
|
||||
*/
|
||||
protected void stopAnimation() {
|
||||
removeCallbacks(mDragRunnable);
|
||||
mScroller.abortAnimation();
|
||||
stopLayerTranslation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a drawer animation has successfully completed.
|
||||
*/
|
||||
private void completeAnimation() {
|
||||
mScroller.abortAnimation();
|
||||
final int finalX = mScroller.getFinalX();
|
||||
setOffsetPixels(finalX);
|
||||
setDrawerState(finalX == 0 ? STATE_CLOSED : STATE_OPEN);
|
||||
stopLayerTranslation();
|
||||
}
|
||||
|
||||
protected void cancelContentTouch() {
|
||||
final long now = SystemClock.uptimeMillis();
|
||||
final MotionEvent cancelEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
|
||||
final int childCount = getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
getChildAt(i).dispatchTouchEvent(cancelEvent);
|
||||
}
|
||||
mContentContainer.dispatchTouchEvent(cancelEvent);
|
||||
cancelEvent.recycle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the drawer to the position passed.
|
||||
*
|
||||
* @param position The position the content is moved to.
|
||||
* @param velocity Optional velocity if called by releasing a drag event.
|
||||
* @param animate Whether the move is animated.
|
||||
*/
|
||||
protected void animateOffsetTo(int position, int velocity, boolean animate) {
|
||||
endDrag();
|
||||
endPeek();
|
||||
|
||||
final int startX = (int) mOffsetPixels;
|
||||
final int dx = position - startX;
|
||||
if (dx == 0 || !animate) {
|
||||
setOffsetPixels(position);
|
||||
setDrawerState(position == 0 ? STATE_CLOSED : STATE_OPEN);
|
||||
stopLayerTranslation();
|
||||
return;
|
||||
}
|
||||
|
||||
int duration;
|
||||
|
||||
velocity = Math.abs(velocity);
|
||||
if (velocity > 0) {
|
||||
duration = 4 * Math.round(1000.f * Math.abs((float) dx / velocity));
|
||||
} else {
|
||||
duration = (int) (600.f * Math.abs((float) dx / mMenuSize));
|
||||
}
|
||||
|
||||
duration = Math.min(duration, mMaxAnimationDuration);
|
||||
animateOffsetTo(position, duration);
|
||||
}
|
||||
|
||||
protected void animateOffsetTo(int position, int duration) {
|
||||
final int startX = (int) mOffsetPixels;
|
||||
final int dx = position - startX;
|
||||
|
||||
if (dx > 0) {
|
||||
setDrawerState(STATE_OPENING);
|
||||
mScroller.startScroll(startX, 0, dx, 0, duration);
|
||||
} else {
|
||||
setDrawerState(STATE_CLOSING);
|
||||
mScroller.startScroll(startX, 0, dx, 0, duration);
|
||||
}
|
||||
|
||||
startLayerTranslation();
|
||||
|
||||
postAnimationInvalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback when each frame in the drawer animation should be drawn.
|
||||
*/
|
||||
private void postAnimationInvalidate() {
|
||||
if (mScroller.computeScrollOffset()) {
|
||||
final int oldX = (int) mOffsetPixels;
|
||||
final int x = mScroller.getCurrX();
|
||||
|
||||
if (x != oldX) setOffsetPixels(x);
|
||||
if (x != mScroller.getFinalX()) {
|
||||
postOnAnimation(mDragRunnable);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
completeAnimation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts peek drawer animation.
|
||||
*/
|
||||
protected void startPeek() {
|
||||
initPeekScroller();
|
||||
|
||||
startLayerTranslation();
|
||||
peekDrawerInvalidate();
|
||||
}
|
||||
|
||||
protected abstract void initPeekScroller();
|
||||
|
||||
/**
|
||||
* Callback when each frame in the peek drawer animation should be drawn.
|
||||
*/
|
||||
private void peekDrawerInvalidate() {
|
||||
if (mPeekScroller.computeScrollOffset()) {
|
||||
final int oldX = (int) mOffsetPixels;
|
||||
final int x = mPeekScroller.getCurrX();
|
||||
if (x != oldX) setOffsetPixels(x);
|
||||
|
||||
if (!mPeekScroller.isFinished()) {
|
||||
postOnAnimation(mPeekRunnable);
|
||||
return;
|
||||
|
||||
} else if (mPeekDelay > 0) {
|
||||
mPeekStartRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
startPeek();
|
||||
}
|
||||
};
|
||||
postDelayed(mPeekStartRunnable, mPeekDelay);
|
||||
}
|
||||
}
|
||||
|
||||
completePeek();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the peek drawer animation has successfully completed.
|
||||
*/
|
||||
private void completePeek() {
|
||||
mPeekScroller.abortAnimation();
|
||||
|
||||
setOffsetPixels(0);
|
||||
|
||||
setDrawerState(STATE_CLOSED);
|
||||
stopLayerTranslation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops ongoing peek drawer animation.
|
||||
*/
|
||||
protected void endPeek() {
|
||||
removeCallbacks(mPeekStartRunnable);
|
||||
removeCallbacks(mPeekRunnable);
|
||||
stopLayerTranslation();
|
||||
}
|
||||
|
||||
protected boolean isCloseEnough() {
|
||||
return Math.abs(mOffsetPixels) <= mCloseEnough;
|
||||
}
|
||||
|
||||
protected boolean canChildrenScroll(int dx, int dy, int x, int y) {
|
||||
boolean canScroll = false;
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
case RIGHT:
|
||||
if (!mMenuVisible) {
|
||||
canScroll = canChildScrollHorizontally(mContentContainer, false, dx,
|
||||
x - ViewHelper.getLeft(mContentContainer), y - ViewHelper.getTop(mContentContainer));
|
||||
} else {
|
||||
canScroll = canChildScrollHorizontally(mMenuContainer, false, dx,
|
||||
x - ViewHelper.getLeft(mMenuContainer), y - ViewHelper.getTop(mContentContainer));
|
||||
}
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
case BOTTOM:
|
||||
if (!mMenuVisible) {
|
||||
canScroll = canChildScrollVertically(mContentContainer, false, dy,
|
||||
x - ViewHelper.getLeft(mContentContainer), y - ViewHelper.getTop(mContentContainer));
|
||||
} else {
|
||||
canScroll = canChildScrollVertically(mMenuContainer, false, dy,
|
||||
x - ViewHelper.getLeft(mMenuContainer), y - ViewHelper.getTop(mContentContainer));
|
||||
}
|
||||
}
|
||||
|
||||
return canScroll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests scrollability within child views of v given a delta of dx.
|
||||
*
|
||||
* @param v View to test for horizontal scrollability
|
||||
* @param checkV Whether the view should be checked for draggability
|
||||
* @param dx Delta scrolled in pixels
|
||||
* @param x X coordinate of the active touch point
|
||||
* @param y Y coordinate of the active touch point
|
||||
* @return true if child views of v can be scrolled by delta of dx.
|
||||
*/
|
||||
protected boolean canChildScrollHorizontally(View v, boolean checkV, int dx, int x, int y) {
|
||||
if (v instanceof ViewGroup) {
|
||||
final ViewGroup group = (ViewGroup) v;
|
||||
|
||||
final int count = group.getChildCount();
|
||||
// Count backwards - let topmost views consume scroll distance first.
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
final View child = group.getChildAt(i);
|
||||
|
||||
final int childLeft = child.getLeft() + supportGetTranslationX(child);
|
||||
final int childRight = child.getRight() + supportGetTranslationX(child);
|
||||
final int childTop = child.getTop() + supportGetTranslationY(child);
|
||||
final int childBottom = child.getBottom() + supportGetTranslationY(child);
|
||||
|
||||
if (x >= childLeft && x < childRight && y >= childTop && y < childBottom
|
||||
&& canChildScrollHorizontally(child, true, dx, x - childLeft, y - childTop)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return checkV && mOnInterceptMoveEventListener.isViewDraggable(v, dx, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests scrollability within child views of v given a delta of dx.
|
||||
*
|
||||
* @param v View to test for horizontal scrollability
|
||||
* @param checkV Whether the view should be checked for draggability
|
||||
* @param dx Delta scrolled in pixels
|
||||
* @param x X coordinate of the active touch point
|
||||
* @param y Y coordinate of the active touch point
|
||||
* @return true if child views of v can be scrolled by delta of dx.
|
||||
*/
|
||||
protected boolean canChildScrollVertically(View v, boolean checkV, int dx, int x, int y) {
|
||||
if (v instanceof ViewGroup) {
|
||||
final ViewGroup group = (ViewGroup) v;
|
||||
|
||||
final int count = group.getChildCount();
|
||||
// Count backwards - let topmost views consume scroll distance first.
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
final View child = group.getChildAt(i);
|
||||
|
||||
final int childLeft = child.getLeft() + supportGetTranslationX(child);
|
||||
final int childRight = child.getRight() + supportGetTranslationX(child);
|
||||
final int childTop = child.getTop() + supportGetTranslationY(child);
|
||||
final int childBottom = child.getBottom() + supportGetTranslationY(child);
|
||||
|
||||
if (x >= childLeft && x < childRight && y >= childTop && y < childBottom
|
||||
&& canChildScrollVertically(child, true, dx, x - childLeft, y - childTop)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return checkV && mOnInterceptMoveEventListener.isViewDraggable(v, dx, x, y);
|
||||
}
|
||||
|
||||
protected float getXVelocity(VelocityTracker velocityTracker) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
|
||||
return velocityTracker.getXVelocity(mActivePointerId);
|
||||
}
|
||||
|
||||
return velocityTracker.getXVelocity();
|
||||
}
|
||||
|
||||
protected float getYVelocity(VelocityTracker velocityTracker) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
|
||||
return velocityTracker.getYVelocity(mActivePointerId);
|
||||
}
|
||||
|
||||
return velocityTracker.getYVelocity();
|
||||
}
|
||||
|
||||
private int supportGetTranslationY(View v) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
return (int) v.getTranslationY();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int supportGetTranslationX(View v) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
return (int) v.getTranslationX();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void saveState(Bundle state) {
|
||||
final boolean menuVisible = mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING;
|
||||
state.putBoolean(STATE_MENU_VISIBLE, menuVisible);
|
||||
}
|
||||
|
||||
public void restoreState(Parcelable in) {
|
||||
super.restoreState(in);
|
||||
Bundle state = (Bundle) in;
|
||||
final boolean menuOpen = state.getBoolean(STATE_MENU_VISIBLE);
|
||||
if (menuOpen) {
|
||||
openMenu(false);
|
||||
} else {
|
||||
setOffsetPixels(0);
|
||||
}
|
||||
mDrawerState = menuOpen ? STATE_OPEN : STATE_CLOSED;
|
||||
}
|
||||
}
|
|
@ -1,175 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2006 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
/**
|
||||
* This class encapsulates scrolling. The duration of the scroll
|
||||
* can be passed in the constructor and specifies the maximum time that
|
||||
* the scrolling animation should take. Past this time, the scrolling is
|
||||
* automatically moved to its final stage and computeScrollOffset()
|
||||
* will always return false to indicate that scrolling is over.
|
||||
*/
|
||||
class FloatScroller {
|
||||
|
||||
private float mStart;
|
||||
private float mFinal;
|
||||
|
||||
private float mCurr;
|
||||
private long mStartTime;
|
||||
private int mDuration;
|
||||
private float mDurationReciprocal;
|
||||
private float mDeltaX;
|
||||
private boolean mFinished;
|
||||
private Interpolator mInterpolator;
|
||||
|
||||
/**
|
||||
* Create a Scroller with the specified interpolator. If the interpolator is
|
||||
* null, the default (viscous) interpolator will be used. Specify whether or
|
||||
* not to support progressive "flywheel" behavior in flinging.
|
||||
*/
|
||||
public FloatScroller(Interpolator interpolator) {
|
||||
mFinished = true;
|
||||
mInterpolator = interpolator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the scroller has finished scrolling.
|
||||
*
|
||||
* @return True if the scroller has finished scrolling, false otherwise.
|
||||
*/
|
||||
public final boolean isFinished() {
|
||||
return mFinished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the finished field to a particular value.
|
||||
*
|
||||
* @param finished The new finished value.
|
||||
*/
|
||||
public final void forceFinished(boolean finished) {
|
||||
mFinished = finished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how long the scroll event will take, in milliseconds.
|
||||
*
|
||||
* @return The duration of the scroll in milliseconds.
|
||||
*/
|
||||
public final int getDuration() {
|
||||
return mDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current offset in the scroll.
|
||||
*
|
||||
* @return The new offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final float getCurr() {
|
||||
return mCurr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start offset in the scroll.
|
||||
*
|
||||
* @return The start offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final float getStart() {
|
||||
return mStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns where the scroll will end. Valid only for "fling" scrolls.
|
||||
*
|
||||
* @return The final offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final float getFinal() {
|
||||
return mFinal;
|
||||
}
|
||||
|
||||
public boolean computeScrollOffset() {
|
||||
if (mFinished) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
|
||||
|
||||
if (timePassed < mDuration) {
|
||||
float x = timePassed * mDurationReciprocal;
|
||||
x = mInterpolator.getInterpolation(x);
|
||||
mCurr = mStart + x * mDeltaX;
|
||||
|
||||
} else {
|
||||
mCurr = mFinal;
|
||||
mFinished = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void startScroll(float start, float delta, int duration) {
|
||||
mFinished = false;
|
||||
mDuration = duration;
|
||||
mStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
mStart = start;
|
||||
mFinal = start + delta;
|
||||
mDeltaX = delta;
|
||||
mDurationReciprocal = 1.0f / (float) mDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the animation. Contrary to {@link #forceFinished(boolean)},
|
||||
* aborting the animating cause the scroller to move to the final x and y
|
||||
* position
|
||||
*
|
||||
* @see #forceFinished(boolean)
|
||||
*/
|
||||
public void abortAnimation() {
|
||||
mCurr = mFinal;
|
||||
mFinished = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the scroll animation. This allows a running animation to scroll
|
||||
* further and longer, when used with {@link #setFinal(float)}.
|
||||
*
|
||||
* @param extend Additional time to scroll in milliseconds.
|
||||
* @see #setFinal(float)
|
||||
*/
|
||||
public void extendDuration(int extend) {
|
||||
int passed = timePassed();
|
||||
mDuration = passed + extend;
|
||||
mDurationReciprocal = 1.0f / mDuration;
|
||||
mFinished = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time elapsed since the beginning of the scrolling.
|
||||
*
|
||||
* @return The elapsed time in milliseconds.
|
||||
*/
|
||||
public int timePassed() {
|
||||
return (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
|
||||
}
|
||||
|
||||
public void setFinal(float newVal) {
|
||||
mFinal = newVal;
|
||||
mDeltaX = mFinal - mStart;
|
||||
mFinished = false;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,28 +0,0 @@
|
|||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
/**
|
||||
* FrameLayout which doesn't let touch events propagate to views positioned behind it in the view hierarchy.
|
||||
*/
|
||||
class NoClickThroughFrameLayout extends BuildLayerFrameLayout {
|
||||
|
||||
public NoClickThroughFrameLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public NoClickThroughFrameLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public NoClickThroughFrameLayout(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,781 +0,0 @@
|
|||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
|
||||
public class OverlayDrawer extends DraggableDrawer {
|
||||
|
||||
private static final String TAG = "OverlayDrawer";
|
||||
|
||||
private int mPeekSize;
|
||||
|
||||
private Runnable mRevealRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cancelContentTouch();
|
||||
int animateTo = 0;
|
||||
switch (getPosition()) {
|
||||
case RIGHT:
|
||||
case BOTTOM:
|
||||
animateTo = -mPeekSize;
|
||||
break;
|
||||
|
||||
default:
|
||||
animateTo = mPeekSize;
|
||||
break;
|
||||
}
|
||||
animateOffsetTo(animateTo, 250);
|
||||
}
|
||||
};
|
||||
|
||||
OverlayDrawer(Activity activity, int dragMode) {
|
||||
super(activity, dragMode);
|
||||
}
|
||||
|
||||
public OverlayDrawer(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public OverlayDrawer(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public OverlayDrawer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super.initDrawer(context, attrs, defStyle);
|
||||
super.addView(mContentContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||||
if (USE_TRANSLATIONS) {
|
||||
mContentContainer.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
}
|
||||
mContentContainer.setHardwareLayersEnabled(false);
|
||||
super.addView(mMenuContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||||
mPeekSize = dpToPx(20);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawOverlay(Canvas canvas) {
|
||||
final int width = getWidth();
|
||||
final int height = getHeight();
|
||||
final int offsetPixels = (int) mOffsetPixels;
|
||||
final float openRatio = Math.abs(mOffsetPixels) / mMenuSize;
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
mMenuOverlay.setBounds(offsetPixels, 0, width, height);
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
mMenuOverlay.setBounds(0, 0, width + offsetPixels, height);
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
mMenuOverlay.setBounds(0, offsetPixels, width, height);
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
mMenuOverlay.setBounds(0, 0, width, height + offsetPixels);
|
||||
break;
|
||||
}
|
||||
|
||||
mMenuOverlay.setAlpha((int) (MAX_MENU_OVERLAY_ALPHA * openRatio));
|
||||
mMenuOverlay.draw(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openMenu(boolean animate) {
|
||||
int animateTo = 0;
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
case TOP:
|
||||
animateTo = mMenuSize;
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
case BOTTOM:
|
||||
animateTo = -mMenuSize;
|
||||
break;
|
||||
}
|
||||
|
||||
animateOffsetTo(animateTo, 0, animate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeMenu(boolean animate) {
|
||||
animateOffsetTo(0, 0, animate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onOffsetPixelsChanged(int offsetPixels) {
|
||||
if (USE_TRANSLATIONS) {
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
mMenuContainer.setTranslationX(offsetPixels - mMenuSize);
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
mMenuContainer.setTranslationY(offsetPixels - mMenuSize);
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
mMenuContainer.setTranslationX(offsetPixels + mMenuSize);
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
mMenuContainer.setTranslationY(offsetPixels + mMenuSize);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (getPosition()) {
|
||||
case TOP:
|
||||
mMenuContainer.offsetTopAndBottom(offsetPixels - mMenuContainer.getBottom());
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
mMenuContainer.offsetTopAndBottom(offsetPixels - (mMenuContainer.getTop() - getHeight()));
|
||||
break;
|
||||
|
||||
case LEFT:
|
||||
mMenuContainer.offsetLeftAndRight(offsetPixels - mMenuContainer.getRight());
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
mMenuContainer.offsetLeftAndRight(offsetPixels - (mMenuContainer.getLeft() - getWidth()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initPeekScroller() {
|
||||
switch (getPosition()) {
|
||||
case RIGHT:
|
||||
case BOTTOM: {
|
||||
final int dx = -mPeekSize;
|
||||
mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
final int dx = mPeekSize;
|
||||
mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
onOffsetPixelsChanged((int) mOffsetPixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GradientDrawable.Orientation getDropShadowOrientation() {
|
||||
switch (getPosition()) {
|
||||
case TOP:
|
||||
return GradientDrawable.Orientation.TOP_BOTTOM;
|
||||
|
||||
case RIGHT:
|
||||
return GradientDrawable.Orientation.RIGHT_LEFT;
|
||||
|
||||
case BOTTOM:
|
||||
return GradientDrawable.Orientation.BOTTOM_TOP;
|
||||
|
||||
default:
|
||||
return GradientDrawable.Orientation.LEFT_RIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateDropShadowRect() {
|
||||
final float openRatio = Math.abs(mOffsetPixels) / mMenuSize;
|
||||
final int dropShadowSize = (int) (mDropShadowSize * openRatio);
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
mDropShadowRect.top = 0;
|
||||
mDropShadowRect.bottom = getHeight();
|
||||
mDropShadowRect.left = ViewHelper.getRight(mMenuContainer);
|
||||
mDropShadowRect.right = mDropShadowRect.left + dropShadowSize;
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
mDropShadowRect.left = 0;
|
||||
mDropShadowRect.right = getWidth();
|
||||
mDropShadowRect.top = ViewHelper.getBottom(mMenuContainer);
|
||||
mDropShadowRect.bottom = mDropShadowRect.top + dropShadowSize;
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
mDropShadowRect.top = 0;
|
||||
mDropShadowRect.bottom = getHeight();
|
||||
mDropShadowRect.right = ViewHelper.getLeft(mMenuContainer);
|
||||
mDropShadowRect.left = mDropShadowRect.right - dropShadowSize;
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
mDropShadowRect.left = 0;
|
||||
mDropShadowRect.right = getWidth();
|
||||
mDropShadowRect.bottom = ViewHelper.getTop(mMenuContainer);
|
||||
mDropShadowRect.top = mDropShadowRect.bottom - dropShadowSize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startLayerTranslation() {
|
||||
if (USE_TRANSLATIONS && mHardwareLayersEnabled && !mLayerTypeHardware) {
|
||||
mLayerTypeHardware = true;
|
||||
mMenuContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopLayerTranslation() {
|
||||
if (mLayerTypeHardware) {
|
||||
mLayerTypeHardware = false;
|
||||
mMenuContainer.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
final int width = r - l;
|
||||
final int height = b - t;
|
||||
|
||||
mContentContainer.layout(0, 0, width, height);
|
||||
|
||||
if (USE_TRANSLATIONS) {
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
mMenuContainer.layout(0, 0, mMenuSize, height);
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
mMenuContainer.layout(width - mMenuSize, 0, width, height);
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
mMenuContainer.layout(0, 0, width, mMenuSize);
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
mMenuContainer.layout(0, height - mMenuSize, width, height);
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
final int offsetPixels = (int) mOffsetPixels;
|
||||
final int menuSize = mMenuSize;
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
mMenuContainer.layout(-menuSize + offsetPixels, 0, offsetPixels, height);
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
mMenuContainer.layout(width + offsetPixels, 0, width + menuSize + offsetPixels, height);
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
mMenuContainer.layout(0, -menuSize + offsetPixels, width, offsetPixels);
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
mMenuContainer.layout(0, height + offsetPixels, width, height + menuSize + offsetPixels);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
|
||||
if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
|
||||
throw new IllegalStateException("Must measure with an exact size");
|
||||
}
|
||||
|
||||
final int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
final int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
if (mOffsetPixels == -1) openMenu(false);
|
||||
|
||||
int menuWidthMeasureSpec;
|
||||
int menuHeightMeasureSpec;
|
||||
switch (getPosition()) {
|
||||
case TOP:
|
||||
case BOTTOM:
|
||||
menuWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width);
|
||||
menuHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, mMenuSize);
|
||||
break;
|
||||
|
||||
default:
|
||||
// LEFT/RIGHT
|
||||
menuWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, mMenuSize);
|
||||
menuHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height);
|
||||
}
|
||||
mMenuContainer.measure(menuWidthMeasureSpec, menuHeightMeasureSpec);
|
||||
|
||||
final int contentWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width);
|
||||
final int contentHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height);
|
||||
mContentContainer.measure(contentWidthMeasureSpec, contentHeightMeasureSpec);
|
||||
|
||||
setMeasuredDimension(width, height);
|
||||
|
||||
updateTouchAreaSize();
|
||||
}
|
||||
|
||||
private boolean isContentTouch(int x, int y) {
|
||||
boolean contentTouch = false;
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
contentTouch = ViewHelper.getRight(mMenuContainer) < x;
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
contentTouch = ViewHelper.getLeft(mMenuContainer) > x;
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
contentTouch = ViewHelper.getBottom(mMenuContainer) < y;
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
contentTouch = ViewHelper.getTop(mMenuContainer) > y;
|
||||
break;
|
||||
}
|
||||
|
||||
return contentTouch;
|
||||
}
|
||||
|
||||
protected boolean onDownAllowDrag(int x, int y) {
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
return (!mMenuVisible && mInitialMotionX <= mTouchSize)
|
||||
|| (mMenuVisible && mInitialMotionX <= mOffsetPixels);
|
||||
|
||||
case RIGHT:
|
||||
final int width = getWidth();
|
||||
final int initialMotionX = (int) mInitialMotionX;
|
||||
|
||||
return (!mMenuVisible && initialMotionX >= width - mTouchSize)
|
||||
|| (mMenuVisible && initialMotionX >= width + mOffsetPixels);
|
||||
|
||||
case TOP:
|
||||
return (!mMenuVisible && mInitialMotionY <= mTouchSize)
|
||||
|| (mMenuVisible && mInitialMotionY <= mOffsetPixels);
|
||||
|
||||
case BOTTOM:
|
||||
final int height = getHeight();
|
||||
return (!mMenuVisible && mInitialMotionY >= height - mTouchSize)
|
||||
|| (mMenuVisible && mInitialMotionY >= height + mOffsetPixels);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean onMoveAllowDrag(int x, int y, float dx, float dy) {
|
||||
if (mMenuVisible && mTouchMode == TOUCH_MODE_FULLSCREEN) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
return (!mMenuVisible && mInitialMotionX <= mTouchSize && (dx > 0)) // Drawer closed
|
||||
|| (mMenuVisible && x <= mOffsetPixels) // Drawer open
|
||||
|| (Math.abs(mOffsetPixels) <= mPeekSize && mMenuVisible); // Drawer revealed
|
||||
|
||||
case RIGHT:
|
||||
final int width = getWidth();
|
||||
return (!mMenuVisible && mInitialMotionX >= width - mTouchSize && (dx < 0))
|
||||
|| (mMenuVisible && x >= width - mOffsetPixels)
|
||||
|| (Math.abs(mOffsetPixels) <= mPeekSize && mMenuVisible);
|
||||
|
||||
case TOP:
|
||||
return (!mMenuVisible && mInitialMotionY <= mTouchSize && (dy > 0))
|
||||
|| (mMenuVisible && x <= mOffsetPixels)
|
||||
|| (Math.abs(mOffsetPixels) <= mPeekSize && mMenuVisible);
|
||||
|
||||
case BOTTOM:
|
||||
final int height = getHeight();
|
||||
return (!mMenuVisible && mInitialMotionY >= height - mTouchSize && (dy < 0))
|
||||
|| (mMenuVisible && x >= height - mOffsetPixels)
|
||||
|| (Math.abs(mOffsetPixels) <= mPeekSize && mMenuVisible);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void onMoveEvent(float dx, float dy) {
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
setOffsetPixels(Math.min(Math.max(mOffsetPixels + dx, 0), mMenuSize));
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
setOffsetPixels(Math.max(Math.min(mOffsetPixels + dx, 0), -mMenuSize));
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
setOffsetPixels(Math.min(Math.max(mOffsetPixels + dy, 0), mMenuSize));
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
setOffsetPixels(Math.max(Math.min(mOffsetPixels + dy, 0), -mMenuSize));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void onUpEvent(int x, int y) {
|
||||
final int offsetPixels = (int) mOffsetPixels;
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT: {
|
||||
if (mIsDragging) {
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
|
||||
final int initialVelocity = (int) getXVelocity(mVelocityTracker);
|
||||
mLastMotionX = x;
|
||||
animateOffsetTo(initialVelocity > 0 ? mMenuSize : 0, initialVelocity, true);
|
||||
|
||||
// Close the menu when content is clicked while the menu is visible.
|
||||
} else if (mMenuVisible) {
|
||||
closeMenu();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TOP: {
|
||||
if (mIsDragging) {
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
|
||||
final int initialVelocity = (int) getYVelocity(mVelocityTracker);
|
||||
mLastMotionY = y;
|
||||
animateOffsetTo(initialVelocity > 0 ? mMenuSize : 0, initialVelocity, true);
|
||||
|
||||
// Close the menu when content is clicked while the menu is visible.
|
||||
} else if (mMenuVisible) {
|
||||
closeMenu();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RIGHT: {
|
||||
final int width = getWidth();
|
||||
|
||||
if (mIsDragging) {
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
|
||||
final int initialVelocity = (int) getXVelocity(mVelocityTracker);
|
||||
mLastMotionX = x;
|
||||
animateOffsetTo(initialVelocity > 0 ? 0 : -mMenuSize, initialVelocity, true);
|
||||
|
||||
// Close the menu when content is clicked while the menu is visible.
|
||||
} else if (mMenuVisible) {
|
||||
closeMenu();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case BOTTOM: {
|
||||
if (mIsDragging) {
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
|
||||
final int initialVelocity = (int) getYVelocity(mVelocityTracker);
|
||||
mLastMotionY = y;
|
||||
animateOffsetTo(initialVelocity < 0 ? -mMenuSize : 0, initialVelocity, true);
|
||||
|
||||
// Close the menu when content is clicked while the menu is visible.
|
||||
} else if (mMenuVisible) {
|
||||
closeMenu();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean checkTouchSlop(float dx, float dy) {
|
||||
switch (getPosition()) {
|
||||
case TOP:
|
||||
case BOTTOM:
|
||||
return Math.abs(dy) > mTouchSlop && Math.abs(dy) > Math.abs(dx);
|
||||
|
||||
default:
|
||||
return Math.abs(dx) > mTouchSlop && Math.abs(dx) > Math.abs(dy);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopAnimation() {
|
||||
super.stopAnimation();
|
||||
removeCallbacks(mRevealRunnable);
|
||||
}
|
||||
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
final int action = ev.getAction() & MotionEvent.ACTION_MASK;
|
||||
|
||||
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
||||
removeCallbacks(mRevealRunnable);
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
mIsDragging = false;
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
|
||||
if (Math.abs(mOffsetPixels) > mMenuSize / 2) {
|
||||
openMenu();
|
||||
} else {
|
||||
closeMenu();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (action == MotionEvent.ACTION_DOWN && mMenuVisible && isCloseEnough()) {
|
||||
setOffsetPixels(0);
|
||||
stopAnimation();
|
||||
endPeek();
|
||||
setDrawerState(STATE_CLOSED);
|
||||
mIsDragging = false;
|
||||
}
|
||||
|
||||
// Always intercept events over the content while menu is visible.
|
||||
if (mMenuVisible) {
|
||||
int index = 0;
|
||||
if (mActivePointerId != INVALID_POINTER) {
|
||||
index = ev.findPointerIndex(mActivePointerId);
|
||||
index = index == -1 ? 0 : index;
|
||||
}
|
||||
|
||||
final int x = (int) ev.getX(index);
|
||||
final int y = (int) ev.getY(index);
|
||||
if (isContentTouch(x, y)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mMenuVisible && !mIsDragging && mTouchMode == TOUCH_MODE_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (action != MotionEvent.ACTION_DOWN && mIsDragging) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mLastMotionX = mInitialMotionX = ev.getX();
|
||||
mLastMotionY = mInitialMotionY = ev.getY();
|
||||
final boolean allowDrag = onDownAllowDrag((int) mLastMotionX, (int) mLastMotionY);
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
|
||||
if (allowDrag) {
|
||||
setDrawerState(mMenuVisible ? STATE_OPEN : STATE_CLOSED);
|
||||
stopAnimation();
|
||||
endPeek();
|
||||
|
||||
if (!mMenuVisible && mInitialMotionX <= mPeekSize) {
|
||||
postDelayed(mRevealRunnable, 160);
|
||||
}
|
||||
|
||||
mIsDragging = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final int activePointerId = mActivePointerId;
|
||||
if (activePointerId == INVALID_POINTER) {
|
||||
// If we don't have a valid id, the touch down wasn't on content.
|
||||
break;
|
||||
}
|
||||
|
||||
final int pointerIndex = ev.findPointerIndex(activePointerId);
|
||||
if (pointerIndex == -1) {
|
||||
mIsDragging = false;
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
endDrag();
|
||||
closeMenu(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
final float x = ev.getX(pointerIndex);
|
||||
final float dx = x - mLastMotionX;
|
||||
final float y = ev.getY(pointerIndex);
|
||||
final float dy = y - mLastMotionY;
|
||||
|
||||
if (Math.abs(dx) >= mTouchSlop || Math.abs(dy) >= mTouchSlop) {
|
||||
removeCallbacks(mRevealRunnable);
|
||||
endPeek();
|
||||
}
|
||||
|
||||
if (checkTouchSlop(dx, dy)) {
|
||||
if (mOnInterceptMoveEventListener != null && (mTouchMode == TOUCH_MODE_FULLSCREEN || mMenuVisible)
|
||||
&& canChildrenScroll((int) dx, (int) dy, (int) x, (int) y)) {
|
||||
endDrag(); // Release the velocity tracker
|
||||
requestDisallowInterceptTouchEvent(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean allowDrag = onMoveAllowDrag((int) x, (int) y, dx, dy);
|
||||
|
||||
if (allowDrag) {
|
||||
endPeek();
|
||||
stopAnimation();
|
||||
setDrawerState(STATE_DRAGGING);
|
||||
mIsDragging = true;
|
||||
mLastMotionX = x;
|
||||
mLastMotionY = y;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
onPointerUp(ev);
|
||||
mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
|
||||
mLastMotionY = ev.getY(ev.findPointerIndex(mActivePointerId));
|
||||
break;
|
||||
}
|
||||
|
||||
if (mVelocityTracker == null) mVelocityTracker = VelocityTracker.obtain();
|
||||
mVelocityTracker.addMovement(ev);
|
||||
|
||||
return mIsDragging;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
if (!mMenuVisible && !mIsDragging && mTouchMode == TOUCH_MODE_NONE) {
|
||||
return false;
|
||||
}
|
||||
final int action = ev.getAction() & MotionEvent.ACTION_MASK;
|
||||
|
||||
if (mVelocityTracker == null) mVelocityTracker = VelocityTracker.obtain();
|
||||
mVelocityTracker.addMovement(ev);
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mLastMotionX = mInitialMotionX = ev.getX();
|
||||
mLastMotionY = mInitialMotionY = ev.getY();
|
||||
final boolean allowDrag = onDownAllowDrag((int) mLastMotionX, (int) mLastMotionY);
|
||||
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
|
||||
if (allowDrag) {
|
||||
stopAnimation();
|
||||
endPeek();
|
||||
|
||||
if (!mMenuVisible && mLastMotionX <= mPeekSize) {
|
||||
postDelayed(mRevealRunnable, 160);
|
||||
}
|
||||
|
||||
startLayerTranslation();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
if (pointerIndex == -1) {
|
||||
mIsDragging = false;
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
endDrag();
|
||||
closeMenu(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mIsDragging) {
|
||||
final float x = ev.getX(pointerIndex);
|
||||
final float dx = x - mLastMotionX;
|
||||
final float y = ev.getY(pointerIndex);
|
||||
final float dy = y - mLastMotionY;
|
||||
|
||||
if (checkTouchSlop(dx, dy)) {
|
||||
final boolean allowDrag = onMoveAllowDrag((int) x, (int) y, dx, dy);
|
||||
|
||||
if (allowDrag) {
|
||||
endPeek();
|
||||
stopAnimation();
|
||||
setDrawerState(STATE_DRAGGING);
|
||||
mIsDragging = true;
|
||||
mLastMotionX = x;
|
||||
mLastMotionY = y;
|
||||
} else {
|
||||
mInitialMotionX = x;
|
||||
mInitialMotionY = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mIsDragging) {
|
||||
startLayerTranslation();
|
||||
|
||||
final float x = ev.getX(pointerIndex);
|
||||
final float dx = x - mLastMotionX;
|
||||
final float y = ev.getY(pointerIndex);
|
||||
final float dy = y - mLastMotionY;
|
||||
|
||||
mLastMotionX = x;
|
||||
mLastMotionY = y;
|
||||
onMoveEvent(dx, dy);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP: {
|
||||
removeCallbacks(mRevealRunnable);
|
||||
int index = ev.findPointerIndex(mActivePointerId);
|
||||
index = index == -1 ? 0 : index;
|
||||
final int x = (int) ev.getX(index);
|
||||
final int y = (int) ev.getY(index);
|
||||
onUpEvent(x, y);
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
mIsDragging = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
final int index = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
|
||||
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
||||
mLastMotionX = ev.getX(index);
|
||||
mLastMotionY = ev.getY(index);
|
||||
mActivePointerId = ev.getPointerId(index);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
onPointerUp(ev);
|
||||
mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
|
||||
mLastMotionY = ev.getY(ev.findPointerIndex(mActivePointerId));
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onPointerUp(MotionEvent ev) {
|
||||
final int pointerIndex = ev.getActionIndex();
|
||||
final int pointerId = ev.getPointerId(pointerIndex);
|
||||
if (pointerId == mActivePointerId) {
|
||||
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
|
||||
mLastMotionX = ev.getX(newPointerIndex);
|
||||
mActivePointerId = ev.getPointerId(newPointerIndex);
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
class PeekInterpolator implements Interpolator {
|
||||
|
||||
private static final String TAG = "PeekInterpolator";
|
||||
|
||||
private static final SinusoidalInterpolator SINUSOIDAL_INTERPOLATOR = new SinusoidalInterpolator();
|
||||
|
||||
@Override
|
||||
public float getInterpolation(float input) {
|
||||
float result;
|
||||
|
||||
if (input < 1.f / 3.f) {
|
||||
result = SINUSOIDAL_INTERPOLATOR.getInterpolation(input * 3);
|
||||
|
||||
} else if (input > 2.f / 3.f) {
|
||||
final float val = ((input + 1.f / 3.f) - 1.f) * 3.f;
|
||||
result = 1.f - SINUSOIDAL_INTERPOLATOR.getInterpolation(val);
|
||||
|
||||
} else {
|
||||
result = 1.f;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.util.SparseArray;
|
||||
|
||||
/**
|
||||
* Enums used for positioning the drawer.
|
||||
*/
|
||||
public enum Position {
|
||||
// Positions the drawer to the left of the content.
|
||||
LEFT(0),
|
||||
|
||||
// Positions the drawer above the content.
|
||||
TOP(1),
|
||||
|
||||
// Positions the drawer to the right of the content.
|
||||
RIGHT(2),
|
||||
|
||||
// Positions the drawer below the content.
|
||||
BOTTOM(3),
|
||||
|
||||
/**
|
||||
* Position the drawer at the start edge. This will position the drawer to the {@link #LEFT} with LTR languages and
|
||||
* {@link #RIGHT} with RTL languages.
|
||||
*/
|
||||
START(4),
|
||||
|
||||
/**
|
||||
* Position the drawer at the end edge. This will position the drawer to the {@link #RIGHT} with LTR languages and
|
||||
* {@link #LEFT} with RTL languages.
|
||||
*/
|
||||
END(5);
|
||||
|
||||
final int mValue;
|
||||
|
||||
Position(int value) {
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
private static final SparseArray<Position> STRING_MAPPING = new SparseArray<Position>();
|
||||
|
||||
static {
|
||||
for (Position via : Position.values()) {
|
||||
STRING_MAPPING.put(via.mValue, via);
|
||||
}
|
||||
}
|
||||
|
||||
public static Position fromValue(int value) {
|
||||
return STRING_MAPPING.get(value);
|
||||
}
|
||||
}
|
|
@ -1,504 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2006 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Build;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
|
||||
/**
|
||||
* This class encapsulates scrolling. The duration of the scroll
|
||||
* can be passed in the constructor and specifies the maximum time that
|
||||
* the scrolling animation should take. Past this time, the scrolling is
|
||||
* automatically moved to its final stage and computeScrollOffset()
|
||||
* will always return false to indicate that scrolling is over.
|
||||
*/
|
||||
class Scroller {
|
||||
private int mMode;
|
||||
|
||||
private int mStartX;
|
||||
private int mStartY;
|
||||
private int mFinalX;
|
||||
private int mFinalY;
|
||||
|
||||
private int mMinX;
|
||||
private int mMaxX;
|
||||
private int mMinY;
|
||||
private int mMaxY;
|
||||
|
||||
private int mCurrX;
|
||||
private int mCurrY;
|
||||
private long mStartTime;
|
||||
private int mDuration;
|
||||
private float mDurationReciprocal;
|
||||
private float mDeltaX;
|
||||
private float mDeltaY;
|
||||
private boolean mFinished;
|
||||
private Interpolator mInterpolator;
|
||||
private boolean mFlywheel;
|
||||
|
||||
private float mVelocity;
|
||||
|
||||
private static final int DEFAULT_DURATION = 250;
|
||||
private static final int SCROLL_MODE = 0;
|
||||
private static final int FLING_MODE = 1;
|
||||
|
||||
private static final float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9));
|
||||
private static final float ALPHA = 800; // pixels / seconds
|
||||
private static final float START_TENSION = 0.4f; // Tension at start: (0.4 * total T, 1.0 * Distance)
|
||||
private static final float END_TENSION = 1.0f - START_TENSION;
|
||||
private static final int NB_SAMPLES = 100;
|
||||
private static final float[] SPLINE = new float[NB_SAMPLES + 1];
|
||||
|
||||
private float mDeceleration;
|
||||
private final float mPpi;
|
||||
|
||||
static {
|
||||
float xMin = 0.0f;
|
||||
for (int i = 0; i <= NB_SAMPLES; i++) {
|
||||
final float t = (float) i / NB_SAMPLES;
|
||||
float xMax = 1.0f;
|
||||
float x, tx, coef;
|
||||
while (true) {
|
||||
x = xMin + (xMax - xMin) / 2.0f;
|
||||
coef = 3.0f * x * (1.0f - x);
|
||||
tx = coef * ((1.0f - x) * START_TENSION + x * END_TENSION) + x * x * x;
|
||||
if (Math.abs(tx - t) < 1E-5) break;
|
||||
if (tx > t) xMax = x;
|
||||
else xMin = x;
|
||||
}
|
||||
final float d = coef + x * x * x;
|
||||
SPLINE[i] = d;
|
||||
}
|
||||
SPLINE[NB_SAMPLES] = 1.0f;
|
||||
|
||||
// This controls the viscous fluid effect (how much of it)
|
||||
sViscousFluidScale = 8.0f;
|
||||
// must be set to 1.0 (used in viscousFluid())
|
||||
sViscousFluidNormalize = 1.0f;
|
||||
sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
|
||||
}
|
||||
|
||||
private static float sViscousFluidScale;
|
||||
private static float sViscousFluidNormalize;
|
||||
|
||||
/**
|
||||
* Create a Scroller with the default duration and interpolator.
|
||||
*/
|
||||
public Scroller(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Scroller with the specified interpolator. If the interpolator is
|
||||
* null, the default (viscous) interpolator will be used. "Flywheel" behavior will
|
||||
* be in effect for apps targeting Honeycomb or newer.
|
||||
*/
|
||||
public Scroller(Context context, Interpolator interpolator) {
|
||||
this(context, interpolator,
|
||||
context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Scroller with the specified interpolator. If the interpolator is
|
||||
* null, the default (viscous) interpolator will be used. Specify whether or
|
||||
* not to support progressive "flywheel" behavior in flinging.
|
||||
*/
|
||||
public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
|
||||
mFinished = true;
|
||||
mInterpolator = interpolator;
|
||||
mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
|
||||
mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
|
||||
mFlywheel = flywheel;
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount of friction applied to flings. The default value
|
||||
* is {@link android.view.ViewConfiguration#getScrollFriction}.
|
||||
*
|
||||
* @param friction A scalar dimension-less value representing the coefficient of
|
||||
* friction.
|
||||
*/
|
||||
public final void setFriction(float friction) {
|
||||
mDeceleration = computeDeceleration(friction);
|
||||
}
|
||||
|
||||
private float computeDeceleration(float friction) {
|
||||
return SensorManager.GRAVITY_EARTH // g (m/s^2)
|
||||
* 39.37f // inch/meter
|
||||
* mPpi // pixels per inch
|
||||
* friction;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns whether the scroller has finished scrolling.
|
||||
*
|
||||
* @return True if the scroller has finished scrolling, false otherwise.
|
||||
*/
|
||||
public final boolean isFinished() {
|
||||
return mFinished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the finished field to a particular value.
|
||||
*
|
||||
* @param finished The new finished value.
|
||||
*/
|
||||
public final void forceFinished(boolean finished) {
|
||||
mFinished = finished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how long the scroll event will take, in milliseconds.
|
||||
*
|
||||
* @return The duration of the scroll in milliseconds.
|
||||
*/
|
||||
public final int getDuration() {
|
||||
return mDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current X offset in the scroll.
|
||||
*
|
||||
* @return The new X offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final int getCurrX() {
|
||||
return mCurrX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Y offset in the scroll.
|
||||
*
|
||||
* @return The new Y offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final int getCurrY() {
|
||||
return mCurrY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current velocity.
|
||||
*
|
||||
* @return The original velocity less the deceleration. Result may be
|
||||
* negative.
|
||||
*/
|
||||
public float getCurrVelocity() {
|
||||
return mVelocity - mDeceleration * timePassed() / 2000.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start X offset in the scroll.
|
||||
*
|
||||
* @return The start X offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final int getStartX() {
|
||||
return mStartX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start Y offset in the scroll.
|
||||
*
|
||||
* @return The start Y offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final int getStartY() {
|
||||
return mStartY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns where the scroll will end. Valid only for "fling" scrolls.
|
||||
*
|
||||
* @return The final X offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final int getFinalX() {
|
||||
return mFinalX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns where the scroll will end. Valid only for "fling" scrolls.
|
||||
*
|
||||
* @return The final Y offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final int getFinalY() {
|
||||
return mFinalY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this when you want to know the new location. If it returns true,
|
||||
* the animation is not yet finished. loc will be altered to provide the
|
||||
* new location.
|
||||
*/
|
||||
public boolean computeScrollOffset() {
|
||||
if (mFinished) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
|
||||
|
||||
if (timePassed < mDuration) {
|
||||
switch (mMode) {
|
||||
case SCROLL_MODE:
|
||||
float x = timePassed * mDurationReciprocal;
|
||||
|
||||
if (mInterpolator == null)
|
||||
x = viscousFluid(x);
|
||||
else
|
||||
x = mInterpolator.getInterpolation(x);
|
||||
|
||||
mCurrX = mStartX + Math.round(x * mDeltaX);
|
||||
mCurrY = mStartY + Math.round(x * mDeltaY);
|
||||
break;
|
||||
case FLING_MODE:
|
||||
final float t = (float) timePassed / mDuration;
|
||||
final int index = (int) (NB_SAMPLES * t);
|
||||
final float tInf = (float) index / NB_SAMPLES;
|
||||
final float tSup = (float) (index + 1) / NB_SAMPLES;
|
||||
final float dInf = SPLINE[index];
|
||||
final float dSup = SPLINE[index + 1];
|
||||
final float distanceCoef = dInf + (t - tInf) / (tSup - tInf) * (dSup - dInf);
|
||||
|
||||
mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
|
||||
// Pin to mMinX <= mCurrX <= mMaxX
|
||||
mCurrX = Math.min(mCurrX, mMaxX);
|
||||
mCurrX = Math.max(mCurrX, mMinX);
|
||||
|
||||
mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
|
||||
// Pin to mMinY <= mCurrY <= mMaxY
|
||||
mCurrY = Math.min(mCurrY, mMaxY);
|
||||
mCurrY = Math.max(mCurrY, mMinY);
|
||||
|
||||
if (mCurrX == mFinalX && mCurrY == mFinalY) {
|
||||
mFinished = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
mCurrX = mFinalX;
|
||||
mCurrY = mFinalY;
|
||||
mFinished = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start scrolling by providing a starting point and the distance to travel.
|
||||
* The scroll will use the default value of 250 milliseconds for the
|
||||
* duration.
|
||||
*
|
||||
* @param startX Starting horizontal scroll offset in pixels. Positive
|
||||
* numbers will scroll the content to the left.
|
||||
* @param startY Starting vertical scroll offset in pixels. Positive numbers
|
||||
* will scroll the content up.
|
||||
* @param dx Horizontal distance to travel. Positive numbers will scroll the
|
||||
* content to the left.
|
||||
* @param dy Vertical distance to travel. Positive numbers will scroll the
|
||||
* content up.
|
||||
*/
|
||||
public void startScroll(int startX, int startY, int dx, int dy) {
|
||||
startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start scrolling by providing a starting point and the distance to travel.
|
||||
*
|
||||
* @param startX Starting horizontal scroll offset in pixels. Positive
|
||||
* numbers will scroll the content to the left.
|
||||
* @param startY Starting vertical scroll offset in pixels. Positive numbers
|
||||
* will scroll the content up.
|
||||
* @param dx Horizontal distance to travel. Positive numbers will scroll the
|
||||
* content to the left.
|
||||
* @param dy Vertical distance to travel. Positive numbers will scroll the
|
||||
* content up.
|
||||
* @param duration Duration of the scroll in milliseconds.
|
||||
*/
|
||||
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
|
||||
mMode = SCROLL_MODE;
|
||||
mFinished = false;
|
||||
mDuration = duration;
|
||||
mStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
mStartX = startX;
|
||||
mStartY = startY;
|
||||
mFinalX = startX + dx;
|
||||
mFinalY = startY + dy;
|
||||
mDeltaX = dx;
|
||||
mDeltaY = dy;
|
||||
mDurationReciprocal = 1.0f / (float) mDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start scrolling based on a fling gesture. The distance travelled will
|
||||
* depend on the initial velocity of the fling.
|
||||
*
|
||||
* @param startX Starting point of the scroll (X)
|
||||
* @param startY Starting point of the scroll (Y)
|
||||
* @param velocityX Initial velocity of the fling (X) measured in pixels per
|
||||
* second.
|
||||
* @param velocityY Initial velocity of the fling (Y) measured in pixels per
|
||||
* second
|
||||
* @param minX Minimum X value. The scroller will not scroll past this
|
||||
* point.
|
||||
* @param maxX Maximum X value. The scroller will not scroll past this
|
||||
* point.
|
||||
* @param minY Minimum Y value. The scroller will not scroll past this
|
||||
* point.
|
||||
* @param maxY Maximum Y value. The scroller will not scroll past this
|
||||
* point.
|
||||
*/
|
||||
public void fling(int startX, int startY, int velocityX, int velocityY,
|
||||
int minX, int maxX, int minY, int maxY) {
|
||||
// Continue a scroll or fling in progress
|
||||
if (mFlywheel && !mFinished) {
|
||||
float oldVel = getCurrVelocity();
|
||||
|
||||
float dx = (float) (mFinalX - mStartX);
|
||||
float dy = (float) (mFinalY - mStartY);
|
||||
float hyp = (float) Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
float ndx = dx / hyp;
|
||||
float ndy = dy / hyp;
|
||||
|
||||
float oldVelocityX = ndx * oldVel;
|
||||
float oldVelocityY = ndy * oldVel;
|
||||
if (Math.signum(velocityX) == Math.signum(oldVelocityX)
|
||||
&& Math.signum(velocityY) == Math.signum(oldVelocityY)) {
|
||||
velocityX += oldVelocityX;
|
||||
velocityY += oldVelocityY;
|
||||
}
|
||||
}
|
||||
|
||||
mMode = FLING_MODE;
|
||||
mFinished = false;
|
||||
|
||||
float velocity = (float) Math.sqrt(velocityX * velocityX + velocityY * velocityY);
|
||||
|
||||
mVelocity = velocity;
|
||||
final double l = Math.log(START_TENSION * velocity / ALPHA);
|
||||
mDuration = (int) (1000.0 * Math.exp(l / (DECELERATION_RATE - 1.0)));
|
||||
mStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
mStartX = startX;
|
||||
mStartY = startY;
|
||||
|
||||
float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
|
||||
float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
|
||||
|
||||
int totalDistance =
|
||||
(int) (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
|
||||
|
||||
mMinX = minX;
|
||||
mMaxX = maxX;
|
||||
mMinY = minY;
|
||||
mMaxY = maxY;
|
||||
|
||||
mFinalX = startX + Math.round(totalDistance * coeffX);
|
||||
// Pin to mMinX <= mFinalX <= mMaxX
|
||||
mFinalX = Math.min(mFinalX, mMaxX);
|
||||
mFinalX = Math.max(mFinalX, mMinX);
|
||||
|
||||
mFinalY = startY + Math.round(totalDistance * coeffY);
|
||||
// Pin to mMinY <= mFinalY <= mMaxY
|
||||
mFinalY = Math.min(mFinalY, mMaxY);
|
||||
mFinalY = Math.max(mFinalY, mMinY);
|
||||
}
|
||||
|
||||
static float viscousFluid(float x) {
|
||||
x *= sViscousFluidScale;
|
||||
if (x < 1.0f) {
|
||||
x -= (1.0f - (float) Math.exp(-x));
|
||||
} else {
|
||||
float start = 0.36787944117f; // 1/e == exp(-1)
|
||||
x = 1.0f - (float) Math.exp(1.0f - x);
|
||||
x = start + x * (1.0f - start);
|
||||
}
|
||||
x *= sViscousFluidNormalize;
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the animation. Contrary to {@link #forceFinished(boolean)},
|
||||
* aborting the animating cause the scroller to move to the final x and y
|
||||
* position
|
||||
*
|
||||
* @see #forceFinished(boolean)
|
||||
*/
|
||||
public void abortAnimation() {
|
||||
mCurrX = mFinalX;
|
||||
mCurrY = mFinalY;
|
||||
mFinished = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the scroll animation. This allows a running animation to scroll
|
||||
* further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
|
||||
*
|
||||
* @param extend Additional time to scroll in milliseconds.
|
||||
* @see #setFinalX(int)
|
||||
* @see #setFinalY(int)
|
||||
*/
|
||||
public void extendDuration(int extend) {
|
||||
int passed = timePassed();
|
||||
mDuration = passed + extend;
|
||||
mDurationReciprocal = 1.0f / mDuration;
|
||||
mFinished = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time elapsed since the beginning of the scrolling.
|
||||
*
|
||||
* @return The elapsed time in milliseconds.
|
||||
*/
|
||||
public int timePassed() {
|
||||
return (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the final position (X) for this scroller.
|
||||
*
|
||||
* @param newX The new X offset as an absolute distance from the origin.
|
||||
* @see #extendDuration(int)
|
||||
* @see #setFinalY(int)
|
||||
*/
|
||||
public void setFinalX(int newX) {
|
||||
mFinalX = newX;
|
||||
mDeltaX = mFinalX - mStartX;
|
||||
mFinished = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the final position (Y) for this scroller.
|
||||
*
|
||||
* @param newY The new Y offset as an absolute distance from the origin.
|
||||
* @see #extendDuration(int)
|
||||
* @see #setFinalX(int)
|
||||
*/
|
||||
public void setFinalY(int newY) {
|
||||
mFinalY = newY;
|
||||
mDeltaY = mFinalY - mStartY;
|
||||
mFinished = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public boolean isScrollingInDirection(float xvel, float yvel) {
|
||||
return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX)
|
||||
&& Math.signum(yvel) == Math.signum(mFinalY - mStartY);
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
/**
|
||||
* Interpolator which, when drawn from 0 to 1, looks like half a sine-wave. Used for smoother opening/closing when
|
||||
* peeking at the drawer.
|
||||
*/
|
||||
class SinusoidalInterpolator implements Interpolator {
|
||||
|
||||
@Override
|
||||
public float getInterpolation(float input) {
|
||||
return (float) (0.5f + 0.5f * Math.sin(input * Math.PI - Math.PI / 2.f));
|
||||
}
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
public class SlideDrawable extends Drawable implements Drawable.Callback {
|
||||
|
||||
private Drawable mWrapped;
|
||||
private float mOffset;
|
||||
|
||||
private final Rect mTmpRect = new Rect();
|
||||
|
||||
private boolean mIsRtl;
|
||||
|
||||
public SlideDrawable(Drawable wrapped) {
|
||||
mWrapped = wrapped;
|
||||
}
|
||||
|
||||
public void setOffset(float offset) {
|
||||
mOffset = offset;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public float getOffset() {
|
||||
return mOffset;
|
||||
}
|
||||
|
||||
void setIsRtl(boolean isRtl) {
|
||||
mIsRtl = isRtl;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
mWrapped.copyBounds(mTmpRect);
|
||||
canvas.save();
|
||||
if (mIsRtl) {
|
||||
canvas.translate(1.f / 3 * mTmpRect.width() * mOffset, 0);
|
||||
} else {
|
||||
canvas.translate(1.f / 3 * mTmpRect.width() * -mOffset, 0);
|
||||
}
|
||||
mWrapped.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChangingConfigurations(int configs) {
|
||||
mWrapped.setChangingConfigurations(configs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChangingConfigurations() {
|
||||
return mWrapped.getChangingConfigurations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDither(boolean dither) {
|
||||
mWrapped.setDither(dither);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFilterBitmap(boolean filter) {
|
||||
mWrapped.setFilterBitmap(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mWrapped.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
mWrapped.setColorFilter(cf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(int color, PorterDuff.Mode mode) {
|
||||
mWrapped.setColorFilter(color, mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearColorFilter() {
|
||||
mWrapped.clearColorFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStateful() {
|
||||
return mWrapped.isStateful();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setState(int[] stateSet) {
|
||||
return mWrapped.setState(stateSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getState() {
|
||||
return mWrapped.getState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable getCurrent() {
|
||||
return mWrapped.getCurrent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setVisible(boolean visible, boolean restart) {
|
||||
return super.setVisible(visible, restart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return mWrapped.getOpacity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Region getTransparentRegion() {
|
||||
return mWrapped.getTransparentRegion();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onStateChange(int[] state) {
|
||||
mWrapped.setState(state);
|
||||
return super.onStateChange(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
mWrapped.setBounds(bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mWrapped.getIntrinsicWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mWrapped.getIntrinsicHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinimumWidth() {
|
||||
return mWrapped.getMinimumWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinimumHeight() {
|
||||
return mWrapped.getMinimumHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getPadding(Rect padding) {
|
||||
return mWrapped.getPadding(padding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConstantState getConstantState() {
|
||||
return super.getConstantState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateDrawable(Drawable who) {
|
||||
if (who == mWrapped) {
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleDrawable(Drawable who, Runnable what, long when) {
|
||||
if (who == mWrapped) {
|
||||
scheduleSelf(what, when);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unscheduleDrawable(Drawable who, Runnable what) {
|
||||
if (who == mWrapped) {
|
||||
unscheduleSelf(what);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,707 +0,0 @@
|
|||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
|
||||
public class SlidingDrawer extends DraggableDrawer {
|
||||
|
||||
private static final String TAG = "OverlayDrawer";
|
||||
|
||||
SlidingDrawer(Activity activity, int dragMode) {
|
||||
super(activity, dragMode);
|
||||
}
|
||||
|
||||
public SlidingDrawer(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SlidingDrawer(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SlidingDrawer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super.initDrawer(context, attrs, defStyle);
|
||||
super.addView(mMenuContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||||
super.addView(mContentContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openMenu(boolean animate) {
|
||||
int animateTo = 0;
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
case TOP:
|
||||
animateTo = mMenuSize;
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
case BOTTOM:
|
||||
animateTo = -mMenuSize;
|
||||
break;
|
||||
}
|
||||
|
||||
animateOffsetTo(animateTo, 0, animate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeMenu(boolean animate) {
|
||||
animateOffsetTo(0, 0, animate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onOffsetPixelsChanged(int offsetPixels) {
|
||||
if (USE_TRANSLATIONS) {
|
||||
switch (getPosition()) {
|
||||
case TOP:
|
||||
case BOTTOM:
|
||||
mContentContainer.setTranslationY(offsetPixels);
|
||||
break;
|
||||
|
||||
default:
|
||||
mContentContainer.setTranslationX(offsetPixels);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (getPosition()) {
|
||||
case TOP:
|
||||
case BOTTOM:
|
||||
mContentContainer.offsetTopAndBottom(offsetPixels - mContentContainer.getTop());
|
||||
break;
|
||||
|
||||
default:
|
||||
mContentContainer.offsetLeftAndRight(offsetPixels - mContentContainer.getLeft());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
offsetMenu(offsetPixels);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initPeekScroller() {
|
||||
switch (getPosition()) {
|
||||
case RIGHT:
|
||||
case BOTTOM: {
|
||||
final int dx = -mMenuSize / 3;
|
||||
mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
final int dx = mMenuSize / 3;
|
||||
mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
onOffsetPixelsChanged((int) mOffsetPixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawOverlay(Canvas canvas) {
|
||||
final int width = getWidth();
|
||||
final int height = getHeight();
|
||||
final int offsetPixels = (int) mOffsetPixels;
|
||||
final float openRatio = Math.abs(mOffsetPixels) / mMenuSize;
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
mMenuOverlay.setBounds(0, 0, offsetPixels, height);
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
mMenuOverlay.setBounds(width + offsetPixels, 0, width, height);
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
mMenuOverlay.setBounds(0, 0, width, offsetPixels);
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
mMenuOverlay.setBounds(0, height + offsetPixels, width, height);
|
||||
break;
|
||||
}
|
||||
|
||||
mMenuOverlay.setAlpha((int) (MAX_MENU_OVERLAY_ALPHA * (1.f - openRatio)));
|
||||
mMenuOverlay.draw(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
final int width = r - l;
|
||||
final int height = b - t;
|
||||
|
||||
if (USE_TRANSLATIONS) {
|
||||
mContentContainer.layout(0, 0, width, height);
|
||||
} else {
|
||||
final int offsetPixels = (int) mOffsetPixels;
|
||||
if (getPosition() == Position.LEFT || getPosition() == Position.RIGHT) {
|
||||
mContentContainer.layout(offsetPixels, 0, width + offsetPixels, height);
|
||||
} else {
|
||||
mContentContainer.layout(0, offsetPixels, width, height + offsetPixels);
|
||||
}
|
||||
}
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
mMenuContainer.layout(0, 0, mMenuSize, height);
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
mMenuContainer.layout(width - mMenuSize, 0, width, height);
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
mMenuContainer.layout(0, 0, width, mMenuSize);
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
mMenuContainer.layout(0, height - mMenuSize, width, height);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Offsets the menu relative to its original position based on the position of the content.
|
||||
*
|
||||
* @param offsetPixels The number of pixels the content if offset.
|
||||
*/
|
||||
private void offsetMenu(int offsetPixels) {
|
||||
if (!mOffsetMenu || mMenuSize == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int width = getWidth();
|
||||
final int height = getHeight();
|
||||
|
||||
final int menuSize = mMenuSize;
|
||||
final int sign = (int) (mOffsetPixels / Math.abs(mOffsetPixels));
|
||||
final float openRatio = Math.abs(mOffsetPixels) / menuSize;
|
||||
final int offset = (int) (-0.25f * ((1.0f - openRatio) * menuSize) * sign);
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT: {
|
||||
if (USE_TRANSLATIONS) {
|
||||
if (offsetPixels > 0) {
|
||||
mMenuContainer.setTranslationX(offset);
|
||||
} else {
|
||||
mMenuContainer.setTranslationX(-menuSize);
|
||||
}
|
||||
|
||||
} else {
|
||||
mMenuContainer.offsetLeftAndRight(offset - mMenuContainer.getLeft());
|
||||
mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RIGHT: {
|
||||
if (USE_TRANSLATIONS) {
|
||||
if (offsetPixels != 0) {
|
||||
mMenuContainer.setTranslationX(offset);
|
||||
} else {
|
||||
mMenuContainer.setTranslationX(menuSize);
|
||||
}
|
||||
|
||||
} else {
|
||||
final int oldOffset = mMenuContainer.getRight() - width;
|
||||
final int offsetBy = offset - oldOffset;
|
||||
mMenuContainer.offsetLeftAndRight(offsetBy);
|
||||
mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TOP: {
|
||||
if (USE_TRANSLATIONS) {
|
||||
if (offsetPixels > 0) {
|
||||
mMenuContainer.setTranslationY(offset);
|
||||
} else {
|
||||
mMenuContainer.setTranslationY(-menuSize);
|
||||
}
|
||||
|
||||
} else {
|
||||
mMenuContainer.offsetTopAndBottom(offset - mMenuContainer.getTop());
|
||||
mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case BOTTOM: {
|
||||
if (USE_TRANSLATIONS) {
|
||||
if (offsetPixels != 0) {
|
||||
mMenuContainer.setTranslationY(offset);
|
||||
} else {
|
||||
mMenuContainer.setTranslationY(menuSize);
|
||||
}
|
||||
|
||||
} else {
|
||||
final int oldOffset = mMenuContainer.getBottom() - height;
|
||||
final int offsetBy = offset - oldOffset;
|
||||
mMenuContainer.offsetTopAndBottom(offsetBy);
|
||||
mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
|
||||
if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
|
||||
throw new IllegalStateException("Must measure with an exact size");
|
||||
}
|
||||
|
||||
final int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
final int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
if (mOffsetPixels == -1) openMenu(false);
|
||||
|
||||
int menuWidthMeasureSpec;
|
||||
int menuHeightMeasureSpec;
|
||||
switch (getPosition()) {
|
||||
case TOP:
|
||||
case BOTTOM:
|
||||
menuWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width);
|
||||
menuHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, mMenuSize);
|
||||
break;
|
||||
|
||||
default:
|
||||
// LEFT/RIGHT
|
||||
menuWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, mMenuSize);
|
||||
menuHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height);
|
||||
}
|
||||
mMenuContainer.measure(menuWidthMeasureSpec, menuHeightMeasureSpec);
|
||||
|
||||
final int contentWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width);
|
||||
final int contentHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height);
|
||||
mContentContainer.measure(contentWidthMeasureSpec, contentHeightMeasureSpec);
|
||||
|
||||
setMeasuredDimension(width, height);
|
||||
|
||||
updateTouchAreaSize();
|
||||
}
|
||||
|
||||
private boolean isContentTouch(int x, int y) {
|
||||
boolean contentTouch = false;
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
contentTouch = ViewHelper.getLeft(mContentContainer) < x;
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
contentTouch = ViewHelper.getRight(mContentContainer) > x;
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
contentTouch = ViewHelper.getTop(mContentContainer) < y;
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
contentTouch = ViewHelper.getBottom(mContentContainer) > y;
|
||||
break;
|
||||
}
|
||||
|
||||
return contentTouch;
|
||||
}
|
||||
|
||||
protected boolean onDownAllowDrag(int x, int y) {
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
return (!mMenuVisible && mInitialMotionX <= mTouchSize)
|
||||
|| (mMenuVisible && mInitialMotionX >= mOffsetPixels);
|
||||
|
||||
case RIGHT:
|
||||
final int width = getWidth();
|
||||
final int initialMotionX = (int) mInitialMotionX;
|
||||
|
||||
return (!mMenuVisible && initialMotionX >= width - mTouchSize)
|
||||
|| (mMenuVisible && initialMotionX <= width + mOffsetPixels);
|
||||
|
||||
case TOP:
|
||||
return (!mMenuVisible && mInitialMotionY <= mTouchSize)
|
||||
|| (mMenuVisible && mInitialMotionY >= mOffsetPixels);
|
||||
|
||||
case BOTTOM:
|
||||
final int height = getHeight();
|
||||
return (!mMenuVisible && mInitialMotionY >= height - mTouchSize)
|
||||
|| (mMenuVisible && mInitialMotionY <= height + mOffsetPixels);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean onMoveAllowDrag(int x, int y, float dx, float dy) {
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
return (!mMenuVisible && mInitialMotionX <= mTouchSize && (dx > 0))
|
||||
|| (mMenuVisible && x >= mOffsetPixels);
|
||||
|
||||
case RIGHT:
|
||||
final int width = getWidth();
|
||||
return (!mMenuVisible && mInitialMotionX >= width - mTouchSize && (dx < 0))
|
||||
|| (mMenuVisible && x <= width + mOffsetPixels);
|
||||
|
||||
case TOP:
|
||||
return (!mMenuVisible && mInitialMotionY <= mTouchSize && (dy > 0))
|
||||
|| (mMenuVisible && y >= mOffsetPixels);
|
||||
|
||||
case BOTTOM:
|
||||
final int height = getHeight();
|
||||
return (!mMenuVisible && mInitialMotionY >= height - mTouchSize && (dy < 0))
|
||||
|| (mMenuVisible && y <= height + mOffsetPixels);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void onMoveEvent(float dx, float dy) {
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
setOffsetPixels(Math.min(Math.max(mOffsetPixels + dx, 0), mMenuSize));
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
setOffsetPixels(Math.max(Math.min(mOffsetPixels + dx, 0), -mMenuSize));
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
setOffsetPixels(Math.min(Math.max(mOffsetPixels + dy, 0), mMenuSize));
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
setOffsetPixels(Math.max(Math.min(mOffsetPixels + dy, 0), -mMenuSize));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void onUpEvent(int x, int y) {
|
||||
final int offsetPixels = (int) mOffsetPixels;
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT: {
|
||||
if (mIsDragging) {
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
|
||||
final int initialVelocity = (int) getXVelocity(mVelocityTracker);
|
||||
mLastMotionX = x;
|
||||
animateOffsetTo(initialVelocity > 0 ? mMenuSize : 0, initialVelocity, true);
|
||||
|
||||
// Close the menu when content is clicked while the menu is visible.
|
||||
} else if (mMenuVisible && x > offsetPixels) {
|
||||
closeMenu();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TOP: {
|
||||
if (mIsDragging) {
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
|
||||
final int initialVelocity = (int) getYVelocity(mVelocityTracker);
|
||||
mLastMotionY = y;
|
||||
animateOffsetTo(initialVelocity > 0 ? mMenuSize : 0, initialVelocity, true);
|
||||
|
||||
// Close the menu when content is clicked while the menu is visible.
|
||||
} else if (mMenuVisible && y > offsetPixels) {
|
||||
closeMenu();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RIGHT: {
|
||||
final int width = getWidth();
|
||||
|
||||
if (mIsDragging) {
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
|
||||
final int initialVelocity = (int) getXVelocity(mVelocityTracker);
|
||||
mLastMotionX = x;
|
||||
animateOffsetTo(initialVelocity > 0 ? 0 : -mMenuSize, initialVelocity, true);
|
||||
|
||||
// Close the menu when content is clicked while the menu is visible.
|
||||
} else if (mMenuVisible && x < width + offsetPixels) {
|
||||
closeMenu();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case BOTTOM: {
|
||||
if (mIsDragging) {
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
|
||||
final int initialVelocity = (int) getYVelocity(mVelocityTracker);
|
||||
mLastMotionY = y;
|
||||
animateOffsetTo(initialVelocity < 0 ? -mMenuSize : 0, initialVelocity, true);
|
||||
|
||||
// Close the menu when content is clicked while the menu is visible.
|
||||
} else if (mMenuVisible && y < getHeight() + offsetPixels) {
|
||||
closeMenu();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean checkTouchSlop(float dx, float dy) {
|
||||
switch (getPosition()) {
|
||||
case TOP:
|
||||
case BOTTOM:
|
||||
return Math.abs(dy) > mTouchSlop && Math.abs(dy) > Math.abs(dx);
|
||||
|
||||
default:
|
||||
return Math.abs(dx) > mTouchSlop && Math.abs(dx) > Math.abs(dy);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
final int action = ev.getAction() & MotionEvent.ACTION_MASK;
|
||||
|
||||
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
mIsDragging = false;
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
|
||||
if (Math.abs(mOffsetPixels) > mMenuSize / 2) {
|
||||
openMenu();
|
||||
} else {
|
||||
closeMenu();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (action == MotionEvent.ACTION_DOWN && mMenuVisible && isCloseEnough()) {
|
||||
setOffsetPixels(0);
|
||||
stopAnimation();
|
||||
endPeek();
|
||||
setDrawerState(STATE_CLOSED);
|
||||
mIsDragging = false;
|
||||
}
|
||||
|
||||
// Always intercept events over the content while menu is visible.
|
||||
if (mMenuVisible) {
|
||||
int index = 0;
|
||||
if (mActivePointerId != INVALID_POINTER) {
|
||||
index = ev.findPointerIndex(mActivePointerId);
|
||||
index = index == -1 ? 0 : index;
|
||||
}
|
||||
|
||||
final int x = (int) ev.getX(index);
|
||||
final int y = (int) ev.getY(index);
|
||||
if (isContentTouch(x, y)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mMenuVisible && !mIsDragging && mTouchMode == TOUCH_MODE_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (action != MotionEvent.ACTION_DOWN && mIsDragging) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mLastMotionX = mInitialMotionX = ev.getX();
|
||||
mLastMotionY = mInitialMotionY = ev.getY();
|
||||
final boolean allowDrag = onDownAllowDrag((int) mLastMotionX, (int) mLastMotionY);
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
|
||||
if (allowDrag) {
|
||||
setDrawerState(mMenuVisible ? STATE_OPEN : STATE_CLOSED);
|
||||
stopAnimation();
|
||||
endPeek();
|
||||
mIsDragging = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final int activePointerId = mActivePointerId;
|
||||
if (activePointerId == INVALID_POINTER) {
|
||||
// If we don't have a valid id, the touch down wasn't on content.
|
||||
break;
|
||||
}
|
||||
|
||||
final int pointerIndex = ev.findPointerIndex(activePointerId);
|
||||
if (pointerIndex == -1) {
|
||||
mIsDragging = false;
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
endDrag();
|
||||
closeMenu(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
final float x = ev.getX(pointerIndex);
|
||||
final float dx = x - mLastMotionX;
|
||||
final float y = ev.getY(pointerIndex);
|
||||
final float dy = y - mLastMotionY;
|
||||
|
||||
if (checkTouchSlop(dx, dy)) {
|
||||
if (mOnInterceptMoveEventListener != null && (mTouchMode == TOUCH_MODE_FULLSCREEN || mMenuVisible)
|
||||
&& canChildrenScroll((int) dx, (int) dy, (int) x, (int) y)) {
|
||||
endDrag(); // Release the velocity tracker
|
||||
requestDisallowInterceptTouchEvent(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean allowDrag = onMoveAllowDrag((int) x, (int) y, dx, dy);
|
||||
|
||||
if (allowDrag) {
|
||||
setDrawerState(STATE_DRAGGING);
|
||||
mIsDragging = true;
|
||||
mLastMotionX = x;
|
||||
mLastMotionY = y;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
onPointerUp(ev);
|
||||
mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
|
||||
mLastMotionY = ev.getY(ev.findPointerIndex(mActivePointerId));
|
||||
break;
|
||||
}
|
||||
|
||||
if (mVelocityTracker == null) mVelocityTracker = VelocityTracker.obtain();
|
||||
mVelocityTracker.addMovement(ev);
|
||||
|
||||
return mIsDragging;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
if (!mMenuVisible && !mIsDragging && mTouchMode == TOUCH_MODE_NONE) {
|
||||
return false;
|
||||
}
|
||||
final int action = ev.getAction() & MotionEvent.ACTION_MASK;
|
||||
|
||||
if (mVelocityTracker == null) mVelocityTracker = VelocityTracker.obtain();
|
||||
mVelocityTracker.addMovement(ev);
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mLastMotionX = mInitialMotionX = ev.getX();
|
||||
mLastMotionY = mInitialMotionY = ev.getY();
|
||||
final boolean allowDrag = onDownAllowDrag((int) mLastMotionX, (int) mLastMotionY);
|
||||
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
|
||||
if (allowDrag) {
|
||||
stopAnimation();
|
||||
endPeek();
|
||||
startLayerTranslation();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
if (pointerIndex == -1) {
|
||||
mIsDragging = false;
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
endDrag();
|
||||
closeMenu(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mIsDragging) {
|
||||
final float x = ev.getX(pointerIndex);
|
||||
final float dx = x - mLastMotionX;
|
||||
final float y = ev.getY(pointerIndex);
|
||||
final float dy = y - mLastMotionY;
|
||||
|
||||
if (checkTouchSlop(dx, dy)) {
|
||||
final boolean allowDrag = onMoveAllowDrag((int) x, (int) y, dx, dy);
|
||||
|
||||
if (allowDrag) {
|
||||
setDrawerState(STATE_DRAGGING);
|
||||
mIsDragging = true;
|
||||
mLastMotionX = x;
|
||||
mLastMotionY = y;
|
||||
} else {
|
||||
mInitialMotionX = x;
|
||||
mInitialMotionY = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mIsDragging) {
|
||||
startLayerTranslation();
|
||||
|
||||
final float x = ev.getX(pointerIndex);
|
||||
final float dx = x - mLastMotionX;
|
||||
final float y = ev.getY(pointerIndex);
|
||||
final float dy = y - mLastMotionY;
|
||||
|
||||
mLastMotionX = x;
|
||||
mLastMotionY = y;
|
||||
onMoveEvent(dx, dy);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP: {
|
||||
int index = ev.findPointerIndex(mActivePointerId);
|
||||
index = index == -1 ? 0 : index;
|
||||
final int x = (int) ev.getX(index);
|
||||
final int y = (int) ev.getY(index);
|
||||
onUpEvent(x, y);
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
mIsDragging = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
final int index = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
|
||||
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
||||
mLastMotionX = ev.getX(index);
|
||||
mLastMotionY = ev.getY(index);
|
||||
mActivePointerId = ev.getPointerId(index);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
onPointerUp(ev);
|
||||
mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
|
||||
mLastMotionY = ev.getY(ev.findPointerIndex(mActivePointerId));
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onPointerUp(MotionEvent ev) {
|
||||
final int pointerIndex = ev.getActionIndex();
|
||||
final int pointerId = ev.getPointerId(pointerIndex);
|
||||
if (pointerId == mActivePointerId) {
|
||||
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
|
||||
mLastMotionX = ev.getX(newPointerIndex);
|
||||
mActivePointerId = ev.getPointerId(newPointerIndex);
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
class SmoothInterpolator implements Interpolator {
|
||||
|
||||
@Override
|
||||
public float getInterpolation(float t) {
|
||||
t -= 1.0f;
|
||||
return t * t * t * t * t + 1.0f;
|
||||
}
|
||||
}
|
|
@ -1,218 +0,0 @@
|
|||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class StaticDrawer extends MenuDrawer {
|
||||
|
||||
public StaticDrawer(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public StaticDrawer(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public StaticDrawer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super.initDrawer(context, attrs, defStyle);
|
||||
super.addView(mMenuContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||||
super.addView(mContentContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||||
|
||||
mIsStatic = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawOverlay(Canvas canvas) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onOffsetPixelsChanged(int offsetPixels) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
final int width = r - l;
|
||||
final int height = b - t;
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
mMenuContainer.layout(0, 0, mMenuSize, height);
|
||||
mContentContainer.layout(mMenuSize, 0, width, height);
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
mMenuContainer.layout(width - mMenuSize, 0, width, height);
|
||||
mContentContainer.layout(0, 0, width - mMenuSize, height);
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
mMenuContainer.layout(0, 0, width, mMenuSize);
|
||||
mContentContainer.layout(0, mMenuSize, width, height);
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
mMenuContainer.layout(0, height - mMenuSize, width, height);
|
||||
mContentContainer.layout(0, 0, width, height - mMenuSize);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
|
||||
if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
|
||||
throw new IllegalStateException("Must measure with an exact size");
|
||||
}
|
||||
|
||||
final int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
final int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
case RIGHT: {
|
||||
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
|
||||
|
||||
final int menuWidth = mMenuSize;
|
||||
final int menuWidthMeasureSpec = MeasureSpec.makeMeasureSpec(menuWidth, MeasureSpec.EXACTLY);
|
||||
|
||||
final int contentWidth = width - menuWidth;
|
||||
final int contentWidthMeasureSpec = MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY);
|
||||
|
||||
mContentContainer.measure(contentWidthMeasureSpec, childHeightMeasureSpec);
|
||||
mMenuContainer.measure(menuWidthMeasureSpec, childHeightMeasureSpec);
|
||||
break;
|
||||
}
|
||||
|
||||
case TOP:
|
||||
case BOTTOM: {
|
||||
final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
|
||||
|
||||
final int menuHeight = mMenuSize;
|
||||
final int menuHeightMeasureSpec = MeasureSpec.makeMeasureSpec(menuHeight, MeasureSpec.EXACTLY);
|
||||
|
||||
final int contentHeight = height - menuHeight;
|
||||
final int contentHeightMeasureSpec = MeasureSpec.makeMeasureSpec(contentHeight, MeasureSpec.EXACTLY);
|
||||
|
||||
mContentContainer.measure(childWidthMeasureSpec, contentHeightMeasureSpec);
|
||||
mMenuContainer.measure(childWidthMeasureSpec, menuHeightMeasureSpec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toggleMenu(boolean animate) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openMenu(boolean animate) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeMenu(boolean animate) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMenuVisible() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMenuSize(int size) {
|
||||
mMenuSize = size;
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOffsetMenuEnabled(boolean offsetMenu) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getOffsetMenuEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void peekDrawer() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void peekDrawer(long delay) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void peekDrawer(long startDelay, long delay) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHardwareLayerEnabled(boolean enabled) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTouchMode() {
|
||||
return TOUCH_MODE_NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTouchMode(int mode) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTouchBezelSize(int size) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTouchBezelSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSlideDrawable(int drawableRes) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSlideDrawable(Drawable drawable) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupUpIndicator(Activity activity) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDrawerIndicatorEnabled(boolean enabled) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDrawerIndicatorEnabled() {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
|
||||
final class ViewHelper {
|
||||
|
||||
private ViewHelper() {
|
||||
}
|
||||
|
||||
public static int getLeft(View v) {
|
||||
if (MenuDrawer.USE_TRANSLATIONS) {
|
||||
return (int) (v.getLeft() + v.getTranslationX());
|
||||
}
|
||||
|
||||
return v.getLeft();
|
||||
}
|
||||
|
||||
public static int getTop(View v) {
|
||||
if (MenuDrawer.USE_TRANSLATIONS) {
|
||||
return (int) (v.getTop() + v.getTranslationY());
|
||||
}
|
||||
|
||||
return v.getTop();
|
||||
}
|
||||
|
||||
public static int getRight(View v) {
|
||||
if (MenuDrawer.USE_TRANSLATIONS) {
|
||||
return (int) (v.getRight() + v.getTranslationX());
|
||||
}
|
||||
|
||||
return v.getRight();
|
||||
}
|
||||
|
||||
public static int getBottom(View v) {
|
||||
if (MenuDrawer.USE_TRANSLATIONS) {
|
||||
return (int) (v.getBottom() + v.getTranslationY());
|
||||
}
|
||||
|
||||
return v.getBottom();
|
||||
}
|
||||
|
||||
public static int getLayoutDirection(View v) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
return v.getLayoutDirection();
|
||||
}
|
||||
|
||||
return View.LAYOUT_DIRECTION_LTR;
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
package net.simonvt.menudrawer.compat;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public final class ActionBarHelper {
|
||||
|
||||
private static final String TAG = "ActionBarHelper";
|
||||
|
||||
static final boolean DEBUG = false;
|
||||
|
||||
private Activity mActivity;
|
||||
|
||||
private Object mIndicatorInfo;
|
||||
|
||||
private boolean mUsesCompat;
|
||||
|
||||
public ActionBarHelper(Activity activity) {
|
||||
mActivity = activity;
|
||||
|
||||
try {
|
||||
Class clazz = activity.getClass();
|
||||
Method m = clazz.getMethod("getSupportActionBar");
|
||||
mUsesCompat = true;
|
||||
} catch (NoSuchMethodException e) {
|
||||
if (DEBUG) {
|
||||
Log.e(TAG,
|
||||
"Activity " + activity.getClass().getSimpleName() + " does not use a compatibility action bar",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
mIndicatorInfo = getIndicatorInfo();
|
||||
}
|
||||
|
||||
private Object getIndicatorInfo() {
|
||||
if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
return ActionBarHelperCompat.getIndicatorInfo(mActivity);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
return ActionBarHelperNative.getIndicatorInfo(mActivity);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setActionBarUpIndicator(Drawable drawable, int contentDesc) {
|
||||
if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
ActionBarHelperCompat.setActionBarUpIndicator(mIndicatorInfo, mActivity, drawable, contentDesc);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
ActionBarHelperNative.setActionBarUpIndicator(mIndicatorInfo, mActivity, drawable, contentDesc);
|
||||
}
|
||||
}
|
||||
|
||||
public void setActionBarDescription(int contentDesc) {
|
||||
if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
ActionBarHelperCompat.setActionBarDescription(mIndicatorInfo, mActivity, contentDesc);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
ActionBarHelperNative.setActionBarDescription(mIndicatorInfo, mActivity, contentDesc);
|
||||
}
|
||||
}
|
||||
|
||||
public Drawable getThemeUpIndicator() {
|
||||
if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
return ActionBarHelperCompat.getThemeUpIndicator(mIndicatorInfo);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
return ActionBarHelperNative.getThemeUpIndicator(mIndicatorInfo, mActivity);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setDisplayShowHomeAsUpEnabled(boolean enabled) {
|
||||
if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
ActionBarHelperCompat.setDisplayHomeAsUpEnabled(mIndicatorInfo, enabled);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
ActionBarHelperNative.setDisplayHomeAsUpEnabled(mActivity, enabled);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
package net.simonvt.menudrawer.compat;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
final class ActionBarHelperCompat {
|
||||
|
||||
private static final String TAG = "ActionBarHelperCompat";
|
||||
|
||||
private ActionBarHelperCompat() {
|
||||
}
|
||||
|
||||
public static void setActionBarUpIndicator(Object info, Activity activity, Drawable drawable, int contentDescRes) {
|
||||
final SetIndicatorInfo sii = (SetIndicatorInfo) info;
|
||||
if (sii.mUpIndicatorView != null) {
|
||||
sii.mUpIndicatorView.setImageDrawable(drawable);
|
||||
final String contentDescription = contentDescRes == 0 ? null : activity.getString(contentDescRes);
|
||||
sii.mUpIndicatorView.setContentDescription(contentDescription);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setActionBarDescription(Object info, Activity activity, int contentDescRes) {
|
||||
final SetIndicatorInfo sii = (SetIndicatorInfo) info;
|
||||
if (sii.mUpIndicatorView != null) {
|
||||
final String contentDescription = contentDescRes == 0 ? null : activity.getString(contentDescRes);
|
||||
sii.mUpIndicatorView.setContentDescription(contentDescription);
|
||||
}
|
||||
}
|
||||
|
||||
public static Drawable getThemeUpIndicator(Object info) {
|
||||
final SetIndicatorInfo sii = (SetIndicatorInfo) info;
|
||||
if (sii.mUpIndicatorView != null) {
|
||||
return sii.mUpIndicatorView.getDrawable();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Object getIndicatorInfo(Activity activity) {
|
||||
return new SetIndicatorInfo(activity);
|
||||
}
|
||||
|
||||
public static void setDisplayHomeAsUpEnabled(Object info, boolean enabled) {
|
||||
final SetIndicatorInfo sii = (SetIndicatorInfo) info;
|
||||
if (sii.mHomeAsUpEnabled != null) {
|
||||
try {
|
||||
sii.mHomeAsUpEnabled.invoke(sii.mActionBar, enabled);
|
||||
} catch (Throwable t) {
|
||||
if (ActionBarHelper.DEBUG) {
|
||||
Log.e(TAG, "Unable to call setHomeAsUpEnabled", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class SetIndicatorInfo {
|
||||
|
||||
public ImageView mUpIndicatorView;
|
||||
public Object mActionBar;
|
||||
public Method mHomeAsUpEnabled;
|
||||
|
||||
SetIndicatorInfo(Activity activity) {
|
||||
try {
|
||||
String appPackage = activity.getPackageName();
|
||||
|
||||
try {
|
||||
// Attempt to find ActionBarSherlock up indicator
|
||||
final int homeId = activity.getResources().getIdentifier("abs__home", "id", appPackage);
|
||||
View v = activity.findViewById(homeId);
|
||||
ViewGroup parent = (ViewGroup) v.getParent();
|
||||
final int upId = activity.getResources().getIdentifier("abs__up", "id", appPackage);
|
||||
mUpIndicatorView = (ImageView) parent.findViewById(upId);
|
||||
} catch (Throwable t) {
|
||||
if (ActionBarHelper.DEBUG) {
|
||||
Log.e(TAG, "ABS action bar not found", t);
|
||||
}
|
||||
}
|
||||
|
||||
if (mUpIndicatorView == null) {
|
||||
// Attempt to find AppCompat up indicator
|
||||
final int homeId = activity.getResources().getIdentifier("home", "id", appPackage);
|
||||
View v = activity.findViewById(homeId);
|
||||
ViewGroup parent = (ViewGroup) v.getParent();
|
||||
final int upId = activity.getResources().getIdentifier("up", "id", appPackage);
|
||||
mUpIndicatorView = (ImageView) parent.findViewById(upId);
|
||||
}
|
||||
|
||||
Class supportActivity = activity.getClass();
|
||||
Method getActionBar = supportActivity.getMethod("getSupportActionBar");
|
||||
|
||||
mActionBar = getActionBar.invoke(activity, (Object)null);
|
||||
Class supportActionBar = mActionBar.getClass();
|
||||
mHomeAsUpEnabled = supportActionBar.getMethod("setDisplayHomeAsUpEnabled", Boolean.TYPE);
|
||||
|
||||
} catch (Throwable t) {
|
||||
if (ActionBarHelper.DEBUG) {
|
||||
Log.e(TAG, "Unable to init SetIndicatorInfo for ABS", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
package net.simonvt.menudrawer.compat;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.app.Activity;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
final class ActionBarHelperNative {
|
||||
|
||||
private static final String TAG = "ActionBarHelperNative";
|
||||
|
||||
private ActionBarHelperNative() {
|
||||
}
|
||||
|
||||
private static final int[] THEME_ATTRS = new int[] {
|
||||
android.R.attr.homeAsUpIndicator
|
||||
};
|
||||
|
||||
public static void setActionBarUpIndicator(Object info, Activity activity, Drawable drawable, int contentDescRes) {
|
||||
|
||||
final SetIndicatorInfo sii = (SetIndicatorInfo) info;
|
||||
if (sii.setHomeAsUpIndicator != null) {
|
||||
try {
|
||||
final ActionBar actionBar = activity.getActionBar();
|
||||
sii.setHomeAsUpIndicator.invoke(actionBar, drawable);
|
||||
sii.setHomeActionContentDescription.invoke(actionBar, contentDescRes);
|
||||
} catch (Throwable t) {
|
||||
if (ActionBarHelper.DEBUG) Log.e(TAG, "Couldn't set home-as-up indicator via JB-MR2 API", t);
|
||||
}
|
||||
} else if (sii.upIndicatorView != null) {
|
||||
sii.upIndicatorView.setImageDrawable(drawable);
|
||||
} else {
|
||||
if (ActionBarHelper.DEBUG) Log.e(TAG, "Couldn't set home-as-up indicator");
|
||||
}
|
||||
}
|
||||
|
||||
public static void setActionBarDescription(Object info, Activity activity, int contentDescRes) {
|
||||
final SetIndicatorInfo sii = (SetIndicatorInfo) info;
|
||||
if (sii.setHomeAsUpIndicator != null) {
|
||||
try {
|
||||
final ActionBar actionBar = activity.getActionBar();
|
||||
sii.setHomeActionContentDescription.invoke(actionBar, contentDescRes);
|
||||
} catch (Throwable t) {
|
||||
if (ActionBarHelper.DEBUG) Log.e(TAG, "Couldn't set content description via JB-MR2 API", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Drawable getThemeUpIndicator(Object info, Activity activity) {
|
||||
final TypedArray a = activity.obtainStyledAttributes(THEME_ATTRS);
|
||||
final Drawable result = a.getDrawable(0);
|
||||
a.recycle();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Object getIndicatorInfo(Activity activity) {
|
||||
return new SetIndicatorInfo(activity);
|
||||
}
|
||||
|
||||
public static void setDisplayHomeAsUpEnabled(Activity activity, boolean b) {
|
||||
ActionBar actionBar = activity.getActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(b);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SetIndicatorInfo {
|
||||
|
||||
public Method setHomeAsUpIndicator;
|
||||
public Method setHomeActionContentDescription;
|
||||
public ImageView upIndicatorView;
|
||||
|
||||
SetIndicatorInfo(Activity activity) {
|
||||
try {
|
||||
setHomeAsUpIndicator = ActionBar.class.getDeclaredMethod("setHomeAsUpIndicator", Drawable.class);
|
||||
setHomeActionContentDescription = ActionBar.class.getDeclaredMethod(
|
||||
"setHomeActionContentDescription", Integer.TYPE);
|
||||
|
||||
// If we got the method we won't need the stuff below.
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
// Oh well. We'll use the other mechanism below instead.
|
||||
}
|
||||
|
||||
final View home = activity.findViewById(android.R.id.home);
|
||||
if (home == null) {
|
||||
// Action bar doesn't have a known configuration, an OEM messed with things.
|
||||
return;
|
||||
}
|
||||
|
||||
final ViewGroup parent = (ViewGroup) home.getParent();
|
||||
final int childCount = parent.getChildCount();
|
||||
if (childCount != 2) {
|
||||
// No idea which one will be the right one, an OEM messed with things.
|
||||
return;
|
||||
}
|
||||
|
||||
final View first = parent.getChildAt(0);
|
||||
final View second = parent.getChildAt(1);
|
||||
final View up = first.getId() == android.R.id.home ? second : first;
|
||||
|
||||
if (up instanceof ImageView) {
|
||||
// Jackpot! (Probably...)
|
||||
upIndicatorView = (ImageView) up;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
<resources>
|
||||
|
||||
<!-- Reference to a style for the menu drawer. -->
|
||||
<attr name="menuDrawerStyle" format="reference" />
|
||||
|
||||
<!-- Styleables used for styling the menu drawer. -->
|
||||
<declare-styleable name="MenuDrawer">
|
||||
|
||||
<!-- Drawable to use for the background of the content. -->
|
||||
<attr name="mdContentBackground" format="reference" />
|
||||
|
||||
<!-- Drawable to use for the background of the menu. -->
|
||||
<attr name="mdMenuBackground" format="reference" />
|
||||
|
||||
<!-- The size of the menu. -->
|
||||
<attr name="mdMenuSize" format="dimension" />
|
||||
|
||||
<!-- Drawable used as indicator for the active view. -->
|
||||
<attr name="mdActiveIndicator" format="reference" />
|
||||
|
||||
<!-- Defines whether the content will have a dropshadow onto the menu. Default is true. -->
|
||||
<attr name="mdDropShadowEnabled" format="boolean" />
|
||||
|
||||
<!-- The size of the drop shadow. Default is 6dp -->
|
||||
<attr name="mdDropShadowSize" format="dimension" />
|
||||
|
||||
<!-- The color of the drop shadow. Default is #FF000000. -->
|
||||
<attr name="mdDropShadowColor" format="color" />
|
||||
|
||||
<!-- Drawable used for the drop shadow. -->
|
||||
<attr name="mdDropShadow" format="reference" />
|
||||
|
||||
<!-- The touch bezel size. -->
|
||||
<attr name="mdTouchBezelSize" format="dimension" />
|
||||
|
||||
<!-- Whether the indicator should be animated between active views. -->
|
||||
<attr name="mdAllowIndicatorAnimation" format="boolean" />
|
||||
|
||||
<!-- The maximum animation duration -->
|
||||
<attr name="mdMaxAnimationDuration" format="integer" />
|
||||
|
||||
<!-- Drawable that replaces the up indicator -->
|
||||
<attr name="mdSlideDrawable" format="reference" />
|
||||
|
||||
<!-- String to use as the up indicators content description when the drawer is open -->
|
||||
<attr name="mdDrawerOpenUpContentDescription" format="string" />
|
||||
|
||||
<!-- String to use as the up indicators content description when the drawer is closed -->
|
||||
<attr name="mdDrawerClosedUpContentDescription" format="string" />
|
||||
|
||||
<!-- Whether an overlay should be drawn as the drawer is opened and closed -->
|
||||
<attr name="mdDrawOverlay" format="boolean" />
|
||||
|
||||
<!-- The position of the drawer -->
|
||||
<attr name="mdPosition" format="enum">
|
||||
<enum name="left" value="0" />
|
||||
<enum name="top" value="1" />
|
||||
<enum name="right" value="2" />
|
||||
<enum name="bottom" value="3" />
|
||||
<enum name="start" value="4" />
|
||||
<enum name="end" value="5" />
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
|
@ -1,6 +0,0 @@
|
|||
<resources>
|
||||
|
||||
<!-- The default background of the menu. -->
|
||||
<color name="md__defaultBackground">#FF555555</color>
|
||||
|
||||
</resources>
|
|
@ -1,24 +0,0 @@
|
|||
<resources>
|
||||
|
||||
<!-- ID used when defining the content layout in XML. -->
|
||||
<item name="mdContent" type="id" />
|
||||
|
||||
<!-- ID used when defining the menu layout in XML. -->
|
||||
<item name="mdMenu" type="id" />
|
||||
|
||||
<!-- The ID of the content container. -->
|
||||
<item name="md__content" type="id" />
|
||||
|
||||
<!-- The ID of the menu container. -->
|
||||
<item name="md__menu" type="id" />
|
||||
|
||||
<!-- The ID of the drawer. -->
|
||||
<item name="md__drawer" type="id" />
|
||||
|
||||
<!-- Used with View#setTag(int) to specify a position for the active view. -->
|
||||
<item name="mdActiveViewPosition" type="id" />
|
||||
|
||||
<item name="md__translationX" type="id" />
|
||||
|
||||
<item name="md__translationY" type="id" />
|
||||
</resources>
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="md__drawerOpenIndicatorDesc" tools:ignore="MissingTranslation">Close drawer</string>
|
||||
|
||||
<string name="md__drawerClosedIndicatorDesc" tools:ignore="MissingTranslation">Open drawer</string>
|
||||
|
||||
</resources>
|
|
@ -1,13 +0,0 @@
|
|||
<resources>
|
||||
|
||||
<style name="Widget" />
|
||||
|
||||
<!-- Base theme for the menu drawer. -->
|
||||
<style name="Widget.MenuDrawer">
|
||||
<item name="mdMenuBackground">@color/md__defaultBackground</item>
|
||||
<item name="mdContentBackground">?android:attr/windowBackground</item>
|
||||
<item name="mdDrawerOpenUpContentDescription">@string/md__drawerOpenIndicatorDesc</item>
|
||||
<item name="mdDrawerClosedUpContentDescription">@string/md__drawerClosedIndicatorDesc</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -1,8 +0,0 @@
|
|||
apply from: bootstrap.androidModule
|
||||
|
||||
android {
|
||||
lintOptions {
|
||||
baselineFile file("lint-baseline.xml")
|
||||
abortOnError true
|
||||
}
|
||||
}
|
|
@ -1,326 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<issues format="4" by="lint 2.3.3">
|
||||
|
||||
<issue
|
||||
id="LocaleFolder"
|
||||
message="The locale folder "`he`" should be called "`iw`" instead; see the `java.util.Locale` documentation">
|
||||
<location
|
||||
file="src/main/res/values-he"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="OldTargetApi"
|
||||
message="Not targeting the latest versions of Android; compatibility modes apply. Consider testing and updating this version. Consult the `android.os.Build.VERSION_CODES` javadoc for details."
|
||||
errorLine1=" <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="16" />"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/AndroidManifest.xml"
|
||||
line="7"
|
||||
column="41"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="GradleOverrides"
|
||||
message="This `minSdkVersion` value (`4`) is not used; it is always overridden by the value specified in the Gradle build script (`14`)"
|
||||
errorLine1=" <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="16" />"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/AndroidManifest.xml"
|
||||
line="7"
|
||||
column="15"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="GradleOverrides"
|
||||
message="This `targetSdkVersion` value (`16`) is not used; it is always overridden by the value specified in the Gradle build script (`22`)"
|
||||
errorLine1=" <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="16" />"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/AndroidManifest.xml"
|
||||
line="7"
|
||||
column="41"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="Deprecated"
|
||||
message="`android:singleLine` is deprecated: Use `maxLines="1"` instead"
|
||||
errorLine1=" android:singleLine="true""
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/layout/pull_to_refresh_header_vertical.xml"
|
||||
line="45"
|
||||
column="17"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="Deprecated"
|
||||
message="`android:singleLine` is deprecated: Use `maxLines="1"` instead"
|
||||
errorLine1=" android:singleLine="true""
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/layout/pull_to_refresh_header_vertical.xml"
|
||||
line="53"
|
||||
column="17"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="MissingTranslation"
|
||||
message=""`pull_to_refresh_from_bottom_pull_label`" is not translated in "es" (Spanish), "fr" (French), "pt" (Portuguese), "pt-BR" (Portuguese: Brazil)"
|
||||
errorLine1=" <string name="pull_to_refresh_from_bottom_pull_label">@string/pull_to_refresh_pull_label</string>"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/values/pull_refresh_strings.xml"
|
||||
line="9"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="MissingTranslation"
|
||||
message=""`pull_to_refresh_from_bottom_release_label`" is not translated in "es" (Spanish), "fr" (French), "pt" (Portuguese), "pt-BR" (Portuguese: Brazil)"
|
||||
errorLine1=" <string name="pull_to_refresh_from_bottom_release_label">@string/pull_to_refresh_release_label</string>"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/values/pull_refresh_strings.xml"
|
||||
line="10"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="MissingTranslation"
|
||||
message=""`pull_to_refresh_from_bottom_refreshing_label`" is not translated in "es" (Spanish), "fr" (French), "pt" (Portuguese), "pt-BR" (Portuguese: Brazil)"
|
||||
errorLine1=" <string name="pull_to_refresh_from_bottom_refreshing_label">@string/pull_to_refresh_refreshing_label</string>"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/values/pull_refresh_strings.xml"
|
||||
line="11"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="AddJavascriptInterface"
|
||||
message="`WebView.addJavascriptInterface` should not be called with minSdkVersion < 17 for security reasons: JavaScript can use reflection to manipulate application"
|
||||
errorLine1=" webView.addJavascriptInterface(mJsCallback, JS_INTERFACE_PKG);"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/handmark/pulltorefresh/library/extras/PullToRefreshWebView2.java"
|
||||
line="90"
|
||||
column="11"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="JavascriptInterface"
|
||||
message="None of the methods in the added interface (JsValueCallback) have been annotated with `@android.webkit.JavascriptInterface`; they will not be visible in API 17"
|
||||
errorLine1=" webView.addJavascriptInterface(mJsCallback, JS_INTERFACE_PKG);"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/handmark/pulltorefresh/library/extras/PullToRefreshWebView2.java"
|
||||
line="90"
|
||||
column="11"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" return VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD && mOverScrollEnabled"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/handmark/pulltorefresh/library/PullToRefreshBase.java"
|
||||
line="211"
|
||||
column="10"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/handmark/pulltorefresh/library/PullToRefreshExpandableListView.java"
|
||||
line="54"
|
||||
column="7"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/handmark/pulltorefresh/library/PullToRefreshGridView.java"
|
||||
line="54"
|
||||
column="7"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/handmark/pulltorefresh/library/PullToRefreshHorizontalScrollView.java"
|
||||
line="53"
|
||||
column="7"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/handmark/pulltorefresh/library/PullToRefreshListView.java"
|
||||
line="207"
|
||||
column="7"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/handmark/pulltorefresh/library/PullToRefreshScrollView.java"
|
||||
line="52"
|
||||
column="7"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/handmark/pulltorefresh/library/PullToRefreshWebView.java"
|
||||
line="98"
|
||||
column="7"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/handmark/pulltorefresh/library/internal/ViewCompat.java"
|
||||
line="44"
|
||||
column="7"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="FloatMath"
|
||||
message="Use `java.lang.Math#floor` instead of `android.util.FloatMath#floor()` since it is faster as of API 8"
|
||||
errorLine1=" float exactContentHeight = FloatMath.floor(mRefreshableView.getContentHeight() * mRefreshableView.getScale());"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/handmark/pulltorefresh/library/PullToRefreshWebView.java"
|
||||
line="115"
|
||||
column="30"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="FloatMath"
|
||||
message="Use `java.lang.Math#floor` instead of `android.util.FloatMath#floor()` since it is faster as of API 8"
|
||||
errorLine1=" return (int) Math.max(0, FloatMath.floor(mRefreshableView.getContentHeight() * mRefreshableView.getScale())"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/handmark/pulltorefresh/library/PullToRefreshWebView.java"
|
||||
line="161"
|
||||
column="29"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="IconMissingDensityFolder"
|
||||
message="Missing density variation folders in `src/main/res`: drawable-xxhdpi">
|
||||
<location
|
||||
file="src/main/res"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ViewConstructor"
|
||||
message="Custom view `RotateLoadingLayout` is missing constructor used by tools: `(Context)` or `(Context,AttributeSet)` or `(Context,AttributeSet,int)`"
|
||||
errorLine1="public class RotateLoadingLayout extends LoadingLayout {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/handmark/pulltorefresh/library/internal/RotateLoadingLayout.java"
|
||||
line="30"
|
||||
column="14"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ContentDescription"
|
||||
message="[Accessibility] Missing `contentDescription` attribute on image"
|
||||
errorLine1=" <ImageView"
|
||||
errorLine2=" ^">
|
||||
<location
|
||||
file="src/main/res/layout/pull_to_refresh_header_horizontal.xml"
|
||||
line="13"
|
||||
column="9"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ContentDescription"
|
||||
message="[Accessibility] Missing `contentDescription` attribute on image"
|
||||
errorLine1=" <ImageView"
|
||||
errorLine2=" ^">
|
||||
<location
|
||||
file="src/main/res/layout/pull_to_refresh_header_vertical.xml"
|
||||
line="18"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="RtlHardcoded"
|
||||
message="Use "`Gravity.START`" instead of "`Gravity.LEFT`" to ensure correct behavior in right-to-left locales"
|
||||
errorLine1=" lp.gravity = scrollDirection == Orientation.VERTICAL ? Gravity.TOP : Gravity.LEFT;"
|
||||
errorLine2=" ~~~~">
|
||||
<location
|
||||
file="src/main/java/com/handmark/pulltorefresh/library/internal/LoadingLayout.java"
|
||||
line="92"
|
||||
column="82"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="RtlHardcoded"
|
||||
message="Use "`Gravity.END`" instead of "`Gravity.RIGHT`" to ensure correct behavior in right-to-left locales"
|
||||
errorLine1=" lp.gravity = scrollDirection == Orientation.VERTICAL ? Gravity.BOTTOM : Gravity.RIGHT;"
|
||||
errorLine2=" ~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/handmark/pulltorefresh/library/internal/LoadingLayout.java"
|
||||
line="102"
|
||||
column="85"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="RtlHardcoded"
|
||||
message="Use "`Gravity.END`" instead of "`Gravity.RIGHT`" to ensure correct behavior in right-to-left locales"
|
||||
errorLine1=" params.gravity = Gravity.TOP | Gravity.RIGHT;"
|
||||
errorLine2=" ~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/handmark/pulltorefresh/library/PullToRefreshAdapterViewBase.java"
|
||||
line="344"
|
||||
column="43"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="RtlHardcoded"
|
||||
message="Use "`Gravity.END`" instead of "`Gravity.RIGHT`" to ensure correct behavior in right-to-left locales"
|
||||
errorLine1=" params.gravity = Gravity.BOTTOM | Gravity.RIGHT;"
|
||||
errorLine2=" ~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/handmark/pulltorefresh/library/PullToRefreshAdapterViewBase.java"
|
||||
line="359"
|
||||
column="46"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="RtlHardcoded"
|
||||
message="Use "`start`" instead of "`left`" to ensure correct behavior in right-to-left locales"
|
||||
errorLine1=" android:layout_gravity="left|center_vertical" >"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/layout/pull_to_refresh_header_vertical.xml"
|
||||
line="16"
|
||||
column="37"/>
|
||||
</issue>
|
||||
|
||||
</issues>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.handmark.pulltorefresh.library"
|
||||
android:versionCode="2110"
|
||||
android:versionName="2.1.1">
|
||||
</manifest>
|
|
@ -1,57 +0,0 @@
|
|||
package com.handmark.pulltorefresh.library;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
public interface ILoadingLayout {
|
||||
|
||||
/**
|
||||
* Set the Last Updated Text. This displayed under the main label when
|
||||
* Pulling
|
||||
*
|
||||
* @param label - Label to set
|
||||
*/
|
||||
public void setLastUpdatedLabel(CharSequence label);
|
||||
|
||||
/**
|
||||
* Set the drawable used in the loading layout. This is the same as calling
|
||||
* <code>setLoadingDrawable(drawable, Mode.BOTH)</code>
|
||||
*
|
||||
* @param drawable - Drawable to display
|
||||
*/
|
||||
public void setLoadingDrawable(Drawable drawable);
|
||||
|
||||
/**
|
||||
* Set Text to show when the Widget is being Pulled
|
||||
* <code>setPullLabel(releaseLabel, Mode.BOTH)</code>
|
||||
*
|
||||
* @param pullLabel - CharSequence to display
|
||||
*/
|
||||
public void setPullLabel(CharSequence pullLabel);
|
||||
|
||||
/**
|
||||
* Set Text to show when the Widget is refreshing
|
||||
* <code>setRefreshingLabel(releaseLabel, Mode.BOTH)</code>
|
||||
*
|
||||
* @param refreshingLabel - CharSequence to display
|
||||
*/
|
||||
public void setRefreshingLabel(CharSequence refreshingLabel);
|
||||
|
||||
/**
|
||||
* Set Text to show when the Widget is being pulled, and will refresh when
|
||||
* released. This is the same as calling
|
||||
* <code>setReleaseLabel(releaseLabel, Mode.BOTH)</code>
|
||||
*
|
||||
* @param releaseLabel - CharSequence to display
|
||||
*/
|
||||
public void setReleaseLabel(CharSequence releaseLabel);
|
||||
|
||||
/**
|
||||
* Set's the Sets the typeface and style in which the text should be
|
||||
* displayed. Please see
|
||||
* {@link android.widget.TextView#setTypeface(Typeface)
|
||||
* TextView#setTypeface(Typeface)}.
|
||||
*/
|
||||
public void setTextTypeface(Typeface tf);
|
||||
|
||||
}
|
|
@ -1,246 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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.
|
||||
*******************************************************************************/
|
||||
package com.handmark.pulltorefresh.library;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.handmark.pulltorefresh.library.PullToRefreshBase.Mode;
|
||||
import com.handmark.pulltorefresh.library.PullToRefreshBase.OnPullEventListener;
|
||||
import com.handmark.pulltorefresh.library.PullToRefreshBase.OnRefreshListener;
|
||||
import com.handmark.pulltorefresh.library.PullToRefreshBase.OnRefreshListener2;
|
||||
import com.handmark.pulltorefresh.library.PullToRefreshBase.State;
|
||||
|
||||
public interface IPullToRefresh<T extends View> {
|
||||
|
||||
/**
|
||||
* Demos the Pull-to-Refresh functionality to the user so that they are
|
||||
* aware it is there. This could be useful when the user first opens your
|
||||
* app, etc. The animation will only happen if the Refresh View (ListView,
|
||||
* ScrollView, etc) is in a state where a Pull-to-Refresh could occur by a
|
||||
* user's touch gesture (i.e. scrolled to the top/bottom).
|
||||
*
|
||||
* @return true - if the Demo has been started, false if not.
|
||||
*/
|
||||
public boolean demo();
|
||||
|
||||
/**
|
||||
* Get the mode that this view is currently in. This is only really useful
|
||||
* when using <code>Mode.BOTH</code>.
|
||||
*
|
||||
* @return Mode that the view is currently in
|
||||
*/
|
||||
public Mode getCurrentMode();
|
||||
|
||||
/**
|
||||
* Returns whether the Touch Events are filtered or not. If true is
|
||||
* returned, then the View will only use touch events where the difference
|
||||
* in the Y-axis is greater than the difference in the X-axis. This means
|
||||
* that the View will not interfere when it is used in a horizontal
|
||||
* scrolling View (such as a ViewPager).
|
||||
*
|
||||
* @return boolean - true if the View is filtering Touch Events
|
||||
*/
|
||||
public boolean getFilterTouchEvents();
|
||||
|
||||
/**
|
||||
* Returns a proxy object which allows you to call methods on all of the
|
||||
* LoadingLayouts (the Views which show when Pulling/Refreshing).
|
||||
* <p />
|
||||
* You should not keep the result of this method any longer than you need
|
||||
* it.
|
||||
*
|
||||
* @return Object which will proxy any calls you make on it, to all of the
|
||||
* LoadingLayouts.
|
||||
*/
|
||||
public ILoadingLayout getLoadingLayoutProxy();
|
||||
|
||||
/**
|
||||
* Returns a proxy object which allows you to call methods on the
|
||||
* LoadingLayouts (the Views which show when Pulling/Refreshing). The actual
|
||||
* LoadingLayout(s) which will be affected, are chosen by the parameters you
|
||||
* give.
|
||||
* <p />
|
||||
* You should not keep the result of this method any longer than you need
|
||||
* it.
|
||||
*
|
||||
* @param includeStart - Whether to include the Start/Header Views
|
||||
* @param includeEnd - Whether to include the End/Footer Views
|
||||
* @return Object which will proxy any calls you make on it, to the
|
||||
* LoadingLayouts included.
|
||||
*/
|
||||
public ILoadingLayout getLoadingLayoutProxy(boolean includeStart, boolean includeEnd);
|
||||
|
||||
/**
|
||||
* Get the mode that this view has been set to. If this returns
|
||||
* <code>Mode.BOTH</code>, you can use <code>getCurrentMode()</code> to
|
||||
* check which mode the view is currently in
|
||||
*
|
||||
* @return Mode that the view has been set to
|
||||
*/
|
||||
public Mode getMode();
|
||||
|
||||
/**
|
||||
* Get the Wrapped Refreshable View. Anything returned here has already been
|
||||
* added to the content view.
|
||||
*
|
||||
* @return The View which is currently wrapped
|
||||
*/
|
||||
public T getRefreshableView();
|
||||
|
||||
/**
|
||||
* Get whether the 'Refreshing' View should be automatically shown when
|
||||
* refreshing. Returns true by default.
|
||||
*
|
||||
* @return - true if the Refreshing View will be show
|
||||
*/
|
||||
public boolean getShowViewWhileRefreshing();
|
||||
|
||||
/**
|
||||
* @return - The state that the View is currently in.
|
||||
*/
|
||||
public State getState();
|
||||
|
||||
/**
|
||||
* Whether Pull-to-Refresh is enabled
|
||||
*
|
||||
* @return enabled
|
||||
*/
|
||||
public boolean isPullToRefreshEnabled();
|
||||
|
||||
/**
|
||||
* Gets whether Overscroll support is enabled. This is different to
|
||||
* Android's standard Overscroll support (the edge-glow) which is available
|
||||
* from GINGERBREAD onwards
|
||||
*
|
||||
* @return true - if both PullToRefresh-OverScroll and Android's inbuilt
|
||||
* OverScroll are enabled
|
||||
*/
|
||||
public boolean isPullToRefreshOverScrollEnabled();
|
||||
|
||||
/**
|
||||
* Returns whether the Widget is currently in the Refreshing mState
|
||||
*
|
||||
* @return true if the Widget is currently refreshing
|
||||
*/
|
||||
public boolean isRefreshing();
|
||||
|
||||
/**
|
||||
* Returns whether the widget has enabled scrolling on the Refreshable View
|
||||
* while refreshing.
|
||||
*
|
||||
* @return true if the widget has enabled scrolling while refreshing
|
||||
*/
|
||||
public boolean isScrollingWhileRefreshingEnabled();
|
||||
|
||||
/**
|
||||
* Mark the current Refresh as complete. Will Reset the UI and hide the
|
||||
* Refreshing View
|
||||
*/
|
||||
public void onRefreshComplete();
|
||||
|
||||
/**
|
||||
* Set the Touch Events to be filtered or not. If set to true, then the View
|
||||
* will only use touch events where the difference in the Y-axis is greater
|
||||
* than the difference in the X-axis. This means that the View will not
|
||||
* interfere when it is used in a horizontal scrolling View (such as a
|
||||
* ViewPager), but will restrict which types of finger scrolls will trigger
|
||||
* the View.
|
||||
*
|
||||
* @param filterEvents - true if you want to filter Touch Events. Default is
|
||||
* true.
|
||||
*/
|
||||
public void setFilterTouchEvents(boolean filterEvents);
|
||||
|
||||
/**
|
||||
* Set the mode of Pull-to-Refresh that this view will use.
|
||||
*
|
||||
* @param mode - Mode to set the View to
|
||||
*/
|
||||
public void setMode(Mode mode);
|
||||
|
||||
/**
|
||||
* Set OnPullEventListener for the Widget
|
||||
*
|
||||
* @param listener - Listener to be used when the Widget has a pull event to
|
||||
* propogate.
|
||||
*/
|
||||
public void setOnPullEventListener(OnPullEventListener<T> listener);
|
||||
|
||||
/**
|
||||
* Set OnRefreshListener for the Widget
|
||||
*
|
||||
* @param listener - Listener to be used when the Widget is set to Refresh
|
||||
*/
|
||||
public void setOnRefreshListener(OnRefreshListener<T> listener);
|
||||
|
||||
/**
|
||||
* Set OnRefreshListener for the Widget
|
||||
*
|
||||
* @param listener - Listener to be used when the Widget is set to Refresh
|
||||
*/
|
||||
public void setOnRefreshListener(OnRefreshListener2<T> listener);
|
||||
|
||||
/**
|
||||
* Sets whether Overscroll support is enabled. This is different to
|
||||
* Android's standard Overscroll support (the edge-glow). This setting only
|
||||
* takes effect when running on device with Android v2.3 or greater.
|
||||
*
|
||||
* @param enabled - true if you want Overscroll enabled
|
||||
*/
|
||||
public void setPullToRefreshOverScrollEnabled(boolean enabled);
|
||||
|
||||
/**
|
||||
* Sets the Widget to be in the refresh state. The UI will be updated to
|
||||
* show the 'Refreshing' view, and be scrolled to show such.
|
||||
*/
|
||||
public void setRefreshing();
|
||||
|
||||
/**
|
||||
* Sets the Widget to be in the refresh state. The UI will be updated to
|
||||
* show the 'Refreshing' view.
|
||||
*
|
||||
* @param doScroll - true if you want to force a scroll to the Refreshing
|
||||
* view.
|
||||
*/
|
||||
public void setRefreshing(boolean doScroll);
|
||||
|
||||
/**
|
||||
* Sets the Animation Interpolator that is used for animated scrolling.
|
||||
* Defaults to a DecelerateInterpolator
|
||||
*
|
||||
* @param interpolator - Interpolator to use
|
||||
*/
|
||||
public void setScrollAnimationInterpolator(Interpolator interpolator);
|
||||
|
||||
/**
|
||||
* By default the Widget disables scrolling on the Refreshable View while
|
||||
* refreshing. This method can change this behaviour.
|
||||
*
|
||||
* @param scrollingWhileRefreshingEnabled - true if you want to enable
|
||||
* scrolling while refreshing
|
||||
*/
|
||||
public void setScrollingWhileRefreshingEnabled(boolean scrollingWhileRefreshingEnabled);
|
||||
|
||||
/**
|
||||
* A mutator to enable/disable whether the 'Refreshing' View should be
|
||||
* automatically shown when refreshing.
|
||||
*
|
||||
* @param showView
|
||||
*/
|
||||
public void setShowViewWhileRefreshing(boolean showView);
|
||||
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
package com.handmark.pulltorefresh.library;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.handmark.pulltorefresh.library.internal.LoadingLayout;
|
||||
|
||||
public class LoadingLayoutProxy implements ILoadingLayout {
|
||||
|
||||
private final HashSet<LoadingLayout> mLoadingLayouts;
|
||||
|
||||
LoadingLayoutProxy() {
|
||||
mLoadingLayouts = new HashSet<LoadingLayout>();
|
||||
}
|
||||
|
||||
/**
|
||||
* This allows you to add extra LoadingLayout instances to this proxy. This
|
||||
* is only necessary if you keep your own instances, and want to have them
|
||||
* included in any
|
||||
* {@link PullToRefreshBase#createLoadingLayoutProxy(boolean, boolean)
|
||||
* createLoadingLayoutProxy(...)} calls.
|
||||
*
|
||||
* @param layout - LoadingLayout to have included.
|
||||
*/
|
||||
public void addLayout(LoadingLayout layout) {
|
||||
if (null != layout) {
|
||||
mLoadingLayouts.add(layout);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastUpdatedLabel(CharSequence label) {
|
||||
for (LoadingLayout layout : mLoadingLayouts) {
|
||||
layout.setLastUpdatedLabel(label);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoadingDrawable(Drawable drawable) {
|
||||
for (LoadingLayout layout : mLoadingLayouts) {
|
||||
layout.setLoadingDrawable(drawable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRefreshingLabel(CharSequence refreshingLabel) {
|
||||
for (LoadingLayout layout : mLoadingLayouts) {
|
||||
layout.setRefreshingLabel(refreshingLabel);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPullLabel(CharSequence label) {
|
||||
for (LoadingLayout layout : mLoadingLayouts) {
|
||||
layout.setPullLabel(label);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReleaseLabel(CharSequence label) {
|
||||
for (LoadingLayout layout : mLoadingLayouts) {
|
||||
layout.setReleaseLabel(label);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextTypeface(Typeface tf) {
|
||||
for (LoadingLayout layout : mLoadingLayouts) {
|
||||
layout.setTextTypeface(tf);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,178 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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.
|
||||
*******************************************************************************/
|
||||
package com.handmark.pulltorefresh.library;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.handmark.pulltorefresh.library.PullToRefreshBase.Mode;
|
||||
import com.handmark.pulltorefresh.library.PullToRefreshBase.State;
|
||||
|
||||
@TargetApi(9)
|
||||
public final class OverscrollHelper {
|
||||
|
||||
static final String LOG_TAG = "OverscrollHelper";
|
||||
static final float DEFAULT_OVERSCROLL_SCALE = 1f;
|
||||
|
||||
/**
|
||||
* Helper method for Overscrolling that encapsulates all of the necessary
|
||||
* function.
|
||||
* <p/>
|
||||
* This should only be used on AdapterView's such as ListView as it just
|
||||
* calls through to overScrollBy() with the scrollRange = 0. AdapterView's
|
||||
* do not have a scroll range (i.e. getScrollY() doesn't work).
|
||||
*
|
||||
* @param view - PullToRefreshView that is calling this.
|
||||
* @param deltaX - Change in X in pixels, passed through from from
|
||||
* overScrollBy call
|
||||
* @param scrollX - Current X scroll value in pixels before applying deltaY,
|
||||
* passed through from from overScrollBy call
|
||||
* @param deltaY - Change in Y in pixels, passed through from from
|
||||
* overScrollBy call
|
||||
* @param scrollY - Current Y scroll value in pixels before applying deltaY,
|
||||
* passed through from from overScrollBy call
|
||||
* @param isTouchEvent - true if this scroll operation is the result of a
|
||||
* touch event, passed through from from overScrollBy call
|
||||
*/
|
||||
public static void overScrollBy(final PullToRefreshBase<?> view, final int deltaX, final int scrollX,
|
||||
final int deltaY, final int scrollY, final boolean isTouchEvent) {
|
||||
overScrollBy(view, deltaX, scrollX, deltaY, scrollY, 0, isTouchEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for Overscrolling that encapsulates all of the necessary
|
||||
* function. This version of the call is used for Views that need to specify
|
||||
* a Scroll Range but scroll back to it's edge correctly.
|
||||
*
|
||||
* @param view - PullToRefreshView that is calling this.
|
||||
* @param deltaX - Change in X in pixels, passed through from from
|
||||
* overScrollBy call
|
||||
* @param scrollX - Current X scroll value in pixels before applying deltaY,
|
||||
* passed through from from overScrollBy call
|
||||
* @param deltaY - Change in Y in pixels, passed through from from
|
||||
* overScrollBy call
|
||||
* @param scrollY - Current Y scroll value in pixels before applying deltaY,
|
||||
* passed through from from overScrollBy call
|
||||
* @param scrollRange - Scroll Range of the View, specifically needed for
|
||||
* ScrollView
|
||||
* @param isTouchEvent - true if this scroll operation is the result of a
|
||||
* touch event, passed through from from overScrollBy call
|
||||
*/
|
||||
public static void overScrollBy(final PullToRefreshBase<?> view, final int deltaX, final int scrollX,
|
||||
final int deltaY, final int scrollY, final int scrollRange, final boolean isTouchEvent) {
|
||||
overScrollBy(view, deltaX, scrollX, deltaY, scrollY, scrollRange, 0, DEFAULT_OVERSCROLL_SCALE, isTouchEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for Overscrolling that encapsulates all of the necessary
|
||||
* function. This is the advanced version of the call.
|
||||
*
|
||||
* @param view - PullToRefreshView that is calling this.
|
||||
* @param deltaX - Change in X in pixels, passed through from from
|
||||
* overScrollBy call
|
||||
* @param scrollX - Current X scroll value in pixels before applying deltaY,
|
||||
* passed through from from overScrollBy call
|
||||
* @param deltaY - Change in Y in pixels, passed through from from
|
||||
* overScrollBy call
|
||||
* @param scrollY - Current Y scroll value in pixels before applying deltaY,
|
||||
* passed through from from overScrollBy call
|
||||
* @param scrollRange - Scroll Range of the View, specifically needed for
|
||||
* ScrollView
|
||||
* @param fuzzyThreshold - Threshold for which the values how fuzzy we
|
||||
* should treat the other values. Needed for WebView as it
|
||||
* doesn't always scroll back to it's edge. 0 = no fuzziness.
|
||||
* @param scaleFactor - Scale Factor for overscroll amount
|
||||
* @param isTouchEvent - true if this scroll operation is the result of a
|
||||
* touch event, passed through from from overScrollBy call
|
||||
*/
|
||||
public static void overScrollBy(final PullToRefreshBase<?> view, final int deltaX, final int scrollX,
|
||||
final int deltaY, final int scrollY, final int scrollRange, final int fuzzyThreshold,
|
||||
final float scaleFactor, final boolean isTouchEvent) {
|
||||
|
||||
final int deltaValue, currentScrollValue, scrollValue;
|
||||
switch (view.getPullToRefreshScrollDirection()) {
|
||||
case HORIZONTAL:
|
||||
deltaValue = deltaX;
|
||||
scrollValue = scrollX;
|
||||
currentScrollValue = view.getScrollX();
|
||||
break;
|
||||
case VERTICAL:
|
||||
default:
|
||||
deltaValue = deltaY;
|
||||
scrollValue = scrollY;
|
||||
currentScrollValue = view.getScrollY();
|
||||
break;
|
||||
}
|
||||
|
||||
// Check that OverScroll is enabled and that we're not currently
|
||||
// refreshing.
|
||||
if (view.isPullToRefreshOverScrollEnabled() && !view.isRefreshing()) {
|
||||
final Mode mode = view.getMode();
|
||||
|
||||
// Check that Pull-to-Refresh is enabled, and the event isn't from
|
||||
// touch
|
||||
if (mode.permitsPullToRefresh() && !isTouchEvent && deltaValue != 0) {
|
||||
final int newScrollValue = (deltaValue + scrollValue);
|
||||
|
||||
if (PullToRefreshBase.DEBUG) {
|
||||
Log.d(LOG_TAG, "OverScroll. DeltaX: " + deltaX + ", ScrollX: " + scrollX + ", DeltaY: " + deltaY
|
||||
+ ", ScrollY: " + scrollY + ", NewY: " + newScrollValue + ", ScrollRange: " + scrollRange
|
||||
+ ", CurrentScroll: " + currentScrollValue);
|
||||
}
|
||||
|
||||
if (newScrollValue < (0 - fuzzyThreshold)) {
|
||||
// Check the mode supports the overscroll direction, and
|
||||
// then move scroll
|
||||
if (mode.showHeaderLoadingLayout()) {
|
||||
// If we're currently at zero, we're about to start
|
||||
// overscrolling, so change the state
|
||||
if (currentScrollValue == 0) {
|
||||
view.setState(State.OVERSCROLLING);
|
||||
}
|
||||
|
||||
view.setHeaderScroll((int) (scaleFactor * (currentScrollValue + newScrollValue)));
|
||||
}
|
||||
} else if (newScrollValue > (scrollRange + fuzzyThreshold)) {
|
||||
// Check the mode supports the overscroll direction, and
|
||||
// then move scroll
|
||||
if (mode.showFooterLoadingLayout()) {
|
||||
// If we're currently at zero, we're about to start
|
||||
// overscrolling, so change the state
|
||||
if (currentScrollValue == 0) {
|
||||
view.setState(State.OVERSCROLLING);
|
||||
}
|
||||
|
||||
view.setHeaderScroll((int) (scaleFactor * (currentScrollValue + newScrollValue - scrollRange)));
|
||||
}
|
||||
} else if (Math.abs(newScrollValue) <= fuzzyThreshold
|
||||
|| Math.abs(newScrollValue - scrollRange) <= fuzzyThreshold) {
|
||||
// Means we've stopped overscrolling, so scroll back to 0
|
||||
view.setState(State.RESET);
|
||||
}
|
||||
} else if (isTouchEvent && State.OVERSCROLLING == view.getState()) {
|
||||
// This condition means that we were overscrolling from a fling,
|
||||
// but the user has touched the View and is now overscrolling
|
||||
// from touch instead. We need to just reset.
|
||||
view.setState(State.RESET);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isAndroidOverScrollEnabled(View view) {
|
||||
return view.getOverScrollMode() != View.OVER_SCROLL_NEVER;
|
||||
}
|
||||
}
|
|
@ -1,475 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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.
|
||||
*******************************************************************************/
|
||||
package com.handmark.pulltorefresh.library;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AbsListView.OnScrollListener;
|
||||
import android.widget.Adapter;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListAdapter;
|
||||
|
||||
import com.handmark.pulltorefresh.library.internal.EmptyViewMethodAccessor;
|
||||
import com.handmark.pulltorefresh.library.internal.IndicatorLayout;
|
||||
|
||||
public abstract class PullToRefreshAdapterViewBase<T extends AbsListView> extends PullToRefreshBase<T> implements
|
||||
OnScrollListener {
|
||||
|
||||
private static FrameLayout.LayoutParams convertEmptyViewLayoutParams(ViewGroup.LayoutParams lp) {
|
||||
FrameLayout.LayoutParams newLp = null;
|
||||
|
||||
if (null != lp) {
|
||||
newLp = new FrameLayout.LayoutParams(lp);
|
||||
|
||||
if (lp instanceof LinearLayout.LayoutParams) {
|
||||
newLp.gravity = ((LinearLayout.LayoutParams) lp).gravity;
|
||||
} else {
|
||||
newLp.gravity = Gravity.CENTER;
|
||||
}
|
||||
}
|
||||
|
||||
return newLp;
|
||||
}
|
||||
|
||||
private boolean mLastItemVisible;
|
||||
private OnScrollListener mOnScrollListener;
|
||||
private OnLastItemVisibleListener mOnLastItemVisibleListener;
|
||||
private View mEmptyView;
|
||||
|
||||
private IndicatorLayout mIndicatorIvTop;
|
||||
private IndicatorLayout mIndicatorIvBottom;
|
||||
|
||||
private boolean mShowIndicator;
|
||||
private boolean mScrollEmptyView = true;
|
||||
|
||||
public PullToRefreshAdapterViewBase(Context context) {
|
||||
super(context);
|
||||
mRefreshableView.setOnScrollListener(this);
|
||||
}
|
||||
|
||||
public PullToRefreshAdapterViewBase(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mRefreshableView.setOnScrollListener(this);
|
||||
}
|
||||
|
||||
public PullToRefreshAdapterViewBase(Context context, Mode mode) {
|
||||
super(context, mode);
|
||||
mRefreshableView.setOnScrollListener(this);
|
||||
}
|
||||
|
||||
public PullToRefreshAdapterViewBase(Context context, Mode mode, AnimationStyle animStyle) {
|
||||
super(context, mode, animStyle);
|
||||
mRefreshableView.setOnScrollListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether an indicator graphic should be displayed when the View is in
|
||||
* a state where a Pull-to-Refresh can happen. An example of this state is
|
||||
* when the Adapter View is scrolled to the top and the mode is set to
|
||||
* {@link Mode#PULL_FROM_START}. The default value is <var>true</var> if
|
||||
* {@link PullToRefreshBase#isPullToRefreshOverScrollEnabled()
|
||||
* isPullToRefreshOverScrollEnabled()} returns false.
|
||||
*
|
||||
* @return true if the indicators will be shown
|
||||
*/
|
||||
public boolean getShowIndicator() {
|
||||
return mShowIndicator;
|
||||
}
|
||||
|
||||
public final void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount,
|
||||
final int totalItemCount) {
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "First Visible: " + firstVisibleItem + ". Visible Count: " + visibleItemCount
|
||||
+ ". Total Items:" + totalItemCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the Last Item is Visible. lastVisibleItemIndex is a
|
||||
* zero-based index, so we minus one totalItemCount to check
|
||||
*/
|
||||
if (null != mOnLastItemVisibleListener) {
|
||||
mLastItemVisible = (totalItemCount > 0) && (firstVisibleItem + visibleItemCount >= totalItemCount - 1);
|
||||
}
|
||||
|
||||
// If we're showing the indicator, check positions...
|
||||
if (getShowIndicatorInternal()) {
|
||||
updateIndicatorViewsVisibility();
|
||||
}
|
||||
|
||||
// Finally call OnScrollListener if we have one
|
||||
if (null != mOnScrollListener) {
|
||||
mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
|
||||
}
|
||||
}
|
||||
|
||||
public final void onScrollStateChanged(final AbsListView view, final int state) {
|
||||
/**
|
||||
* Check that the scrolling has stopped, and that the last item is
|
||||
* visible.
|
||||
*/
|
||||
if (state == OnScrollListener.SCROLL_STATE_IDLE && null != mOnLastItemVisibleListener && mLastItemVisible) {
|
||||
mOnLastItemVisibleListener.onLastItemVisible();
|
||||
}
|
||||
|
||||
if (null != mOnScrollListener) {
|
||||
mOnScrollListener.onScrollStateChanged(view, state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass-through method for {@link PullToRefreshBase#getRefreshableView()
|
||||
* getRefreshableView()}.
|
||||
* {@link AdapterView#setAdapter(android.widget.Adapter)}
|
||||
* setAdapter(adapter)}. This is just for convenience!
|
||||
*
|
||||
* @param adapter - Adapter to set
|
||||
*/
|
||||
public void setAdapter(ListAdapter adapter) {
|
||||
((AdapterView<ListAdapter>) mRefreshableView).setAdapter(adapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Empty View to be used by the Adapter View.
|
||||
* <p/>
|
||||
* We need it handle it ourselves so that we can Pull-to-Refresh when the
|
||||
* Empty View is shown.
|
||||
* <p/>
|
||||
* Please note, you do <strong>not</strong> usually need to call this method
|
||||
* yourself. Calling setEmptyView on the AdapterView will automatically call
|
||||
* this method and set everything up. This includes when the Android
|
||||
* Framework automatically sets the Empty View based on it's ID.
|
||||
*
|
||||
* @param newEmptyView - Empty View to be used
|
||||
*/
|
||||
public final void setEmptyView(View newEmptyView) {
|
||||
FrameLayout refreshableViewWrapper = getRefreshableViewWrapper();
|
||||
|
||||
if (null != newEmptyView) {
|
||||
// New view needs to be clickable so that Android recognizes it as a
|
||||
// target for Touch Events
|
||||
newEmptyView.setClickable(true);
|
||||
|
||||
ViewParent newEmptyViewParent = newEmptyView.getParent();
|
||||
if (null != newEmptyViewParent && newEmptyViewParent instanceof ViewGroup) {
|
||||
((ViewGroup) newEmptyViewParent).removeView(newEmptyView);
|
||||
}
|
||||
|
||||
// We need to convert any LayoutParams so that it works in our
|
||||
// FrameLayout
|
||||
FrameLayout.LayoutParams lp = convertEmptyViewLayoutParams(newEmptyView.getLayoutParams());
|
||||
if (null != lp) {
|
||||
refreshableViewWrapper.addView(newEmptyView, lp);
|
||||
} else {
|
||||
refreshableViewWrapper.addView(newEmptyView);
|
||||
}
|
||||
}
|
||||
|
||||
if (mRefreshableView instanceof EmptyViewMethodAccessor) {
|
||||
((EmptyViewMethodAccessor) mRefreshableView).setEmptyViewInternal(newEmptyView);
|
||||
} else {
|
||||
mRefreshableView.setEmptyView(newEmptyView);
|
||||
}
|
||||
mEmptyView = newEmptyView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass-through method for {@link PullToRefreshBase#getRefreshableView()
|
||||
* getRefreshableView()}.
|
||||
* {@link AdapterView#setOnItemClickListener(OnItemClickListener)
|
||||
* setOnItemClickListener(listener)}. This is just for convenience!
|
||||
*
|
||||
* @param listener - OnItemClickListener to use
|
||||
*/
|
||||
public void setOnItemClickListener(OnItemClickListener listener) {
|
||||
mRefreshableView.setOnItemClickListener(listener);
|
||||
}
|
||||
|
||||
public final void setOnLastItemVisibleListener(OnLastItemVisibleListener listener) {
|
||||
mOnLastItemVisibleListener = listener;
|
||||
}
|
||||
|
||||
public final void setOnScrollListener(OnScrollListener listener) {
|
||||
mOnScrollListener = listener;
|
||||
}
|
||||
|
||||
public final void setScrollEmptyView(boolean doScroll) {
|
||||
mScrollEmptyView = doScroll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether an indicator graphic should be displayed when the View is in
|
||||
* a state where a Pull-to-Refresh can happen. An example of this state is
|
||||
* when the Adapter View is scrolled to the top and the mode is set to
|
||||
* {@link Mode#PULL_FROM_START}
|
||||
*
|
||||
* @param showIndicator - true if the indicators should be shown.
|
||||
*/
|
||||
public void setShowIndicator(boolean showIndicator) {
|
||||
mShowIndicator = showIndicator;
|
||||
|
||||
if (getShowIndicatorInternal()) {
|
||||
// If we're set to Show Indicator, add/update them
|
||||
addIndicatorViews();
|
||||
} else {
|
||||
// If not, then remove then
|
||||
removeIndicatorViews();
|
||||
}
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
@Override
|
||||
protected void onPullToRefresh() {
|
||||
super.onPullToRefresh();
|
||||
|
||||
if (getShowIndicatorInternal()) {
|
||||
switch (getCurrentMode()) {
|
||||
case PULL_FROM_END:
|
||||
mIndicatorIvBottom.pullToRefresh();
|
||||
break;
|
||||
case PULL_FROM_START:
|
||||
mIndicatorIvTop.pullToRefresh();
|
||||
break;
|
||||
default:
|
||||
// NO-OP
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onRefreshing(boolean doScroll) {
|
||||
super.onRefreshing(doScroll);
|
||||
|
||||
if (getShowIndicatorInternal()) {
|
||||
updateIndicatorViewsVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReleaseToRefresh() {
|
||||
super.onReleaseToRefresh();
|
||||
|
||||
if (getShowIndicatorInternal()) {
|
||||
switch (getCurrentMode()) {
|
||||
case PULL_FROM_END:
|
||||
mIndicatorIvBottom.releaseToRefresh();
|
||||
break;
|
||||
case PULL_FROM_START:
|
||||
mIndicatorIvTop.releaseToRefresh();
|
||||
break;
|
||||
default:
|
||||
// NO-OP
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReset() {
|
||||
super.onReset();
|
||||
|
||||
if (getShowIndicatorInternal()) {
|
||||
updateIndicatorViewsVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleStyledAttributes(TypedArray a) {
|
||||
// Set Show Indicator to the XML value, or default value
|
||||
mShowIndicator = a.getBoolean(R.styleable.PullToRefresh_ptrShowIndicator, !isPullToRefreshOverScrollEnabled());
|
||||
}
|
||||
|
||||
protected boolean isReadyForPullStart() {
|
||||
return isFirstItemVisible();
|
||||
}
|
||||
|
||||
protected boolean isReadyForPullEnd() {
|
||||
return isLastItemVisible();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
|
||||
super.onScrollChanged(l, t, oldl, oldt);
|
||||
if (null != mEmptyView && !mScrollEmptyView) {
|
||||
mEmptyView.scrollTo(-l, -t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateUIForMode() {
|
||||
super.updateUIForMode();
|
||||
|
||||
// Check Indicator Views consistent with new Mode
|
||||
if (getShowIndicatorInternal()) {
|
||||
addIndicatorViews();
|
||||
} else {
|
||||
removeIndicatorViews();
|
||||
}
|
||||
}
|
||||
|
||||
private void addIndicatorViews() {
|
||||
Mode mode = getMode();
|
||||
FrameLayout refreshableViewWrapper = getRefreshableViewWrapper();
|
||||
|
||||
if (mode.showHeaderLoadingLayout() && null == mIndicatorIvTop) {
|
||||
// If the mode can pull down, and we don't have one set already
|
||||
mIndicatorIvTop = new IndicatorLayout(getContext(), Mode.PULL_FROM_START);
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.rightMargin = getResources().getDimensionPixelSize(R.dimen.indicator_right_padding);
|
||||
params.gravity = Gravity.TOP | Gravity.RIGHT;
|
||||
refreshableViewWrapper.addView(mIndicatorIvTop, params);
|
||||
|
||||
} else if (!mode.showHeaderLoadingLayout() && null != mIndicatorIvTop) {
|
||||
// If we can't pull down, but have a View then remove it
|
||||
refreshableViewWrapper.removeView(mIndicatorIvTop);
|
||||
mIndicatorIvTop = null;
|
||||
}
|
||||
|
||||
if (mode.showFooterLoadingLayout() && null == mIndicatorIvBottom) {
|
||||
// If the mode can pull down, and we don't have one set already
|
||||
mIndicatorIvBottom = new IndicatorLayout(getContext(), Mode.PULL_FROM_END);
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.rightMargin = getResources().getDimensionPixelSize(R.dimen.indicator_right_padding);
|
||||
params.gravity = Gravity.BOTTOM | Gravity.RIGHT;
|
||||
refreshableViewWrapper.addView(mIndicatorIvBottom, params);
|
||||
|
||||
} else if (!mode.showFooterLoadingLayout() && null != mIndicatorIvBottom) {
|
||||
// If we can't pull down, but have a View then remove it
|
||||
refreshableViewWrapper.removeView(mIndicatorIvBottom);
|
||||
mIndicatorIvBottom = null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean getShowIndicatorInternal() {
|
||||
return mShowIndicator && isPullToRefreshEnabled();
|
||||
}
|
||||
|
||||
private boolean isFirstItemVisible() {
|
||||
final Adapter adapter = mRefreshableView.getAdapter();
|
||||
|
||||
if (null == adapter || adapter.isEmpty()) {
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "isFirstItemVisible. Empty View.");
|
||||
}
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
/**
|
||||
* This check should really just be:
|
||||
* mRefreshableView.getFirstVisiblePosition() == 0, but PtRListView
|
||||
* internally use a HeaderView which messes the positions up. For
|
||||
* now we'll just add one to account for it and rely on the inner
|
||||
* condition which checks getTop().
|
||||
*/
|
||||
if (mRefreshableView.getFirstVisiblePosition() <= 1) {
|
||||
final View firstVisibleChild = mRefreshableView.getChildAt(0);
|
||||
if (firstVisibleChild != null) {
|
||||
return firstVisibleChild.getTop() >= mRefreshableView.getTop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isLastItemVisible() {
|
||||
final Adapter adapter = mRefreshableView.getAdapter();
|
||||
|
||||
if (null == adapter || adapter.isEmpty()) {
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "isLastItemVisible. Empty View.");
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
final int lastItemPosition = mRefreshableView.getCount() - 1;
|
||||
final int lastVisiblePosition = mRefreshableView.getLastVisiblePosition();
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "isLastItemVisible. Last Item Position: " + lastItemPosition + " Last Visible Pos: "
|
||||
+ lastVisiblePosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* This check should really just be: lastVisiblePosition ==
|
||||
* lastItemPosition, but PtRListView internally uses a FooterView
|
||||
* which messes the positions up. For me we'll just subtract one to
|
||||
* account for it and rely on the inner condition which checks
|
||||
* getBottom().
|
||||
*/
|
||||
if (lastVisiblePosition >= lastItemPosition - 1) {
|
||||
final int childIndex = lastVisiblePosition - mRefreshableView.getFirstVisiblePosition();
|
||||
final View lastVisibleChild = mRefreshableView.getChildAt(childIndex);
|
||||
if (lastVisibleChild != null) {
|
||||
return lastVisibleChild.getBottom() <= mRefreshableView.getBottom();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void removeIndicatorViews() {
|
||||
if (null != mIndicatorIvTop) {
|
||||
getRefreshableViewWrapper().removeView(mIndicatorIvTop);
|
||||
mIndicatorIvTop = null;
|
||||
}
|
||||
|
||||
if (null != mIndicatorIvBottom) {
|
||||
getRefreshableViewWrapper().removeView(mIndicatorIvBottom);
|
||||
mIndicatorIvBottom = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateIndicatorViewsVisibility() {
|
||||
if (null != mIndicatorIvTop) {
|
||||
if (!isRefreshing() && isReadyForPullStart()) {
|
||||
if (!mIndicatorIvTop.isVisible()) {
|
||||
mIndicatorIvTop.show();
|
||||
}
|
||||
} else {
|
||||
if (mIndicatorIvTop.isVisible()) {
|
||||
mIndicatorIvTop.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null != mIndicatorIvBottom) {
|
||||
if (!isRefreshing() && isReadyForPullEnd()) {
|
||||
if (!mIndicatorIvBottom.isVisible()) {
|
||||
mIndicatorIvBottom.show();
|
||||
}
|
||||
} else {
|
||||
if (mIndicatorIvBottom.isVisible()) {
|
||||
mIndicatorIvBottom.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,103 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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.
|
||||
*******************************************************************************/
|
||||
package com.handmark.pulltorefresh.library;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ExpandableListView;
|
||||
|
||||
import com.handmark.pulltorefresh.library.internal.EmptyViewMethodAccessor;
|
||||
|
||||
public class PullToRefreshExpandableListView extends PullToRefreshAdapterViewBase<ExpandableListView> {
|
||||
|
||||
public PullToRefreshExpandableListView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public PullToRefreshExpandableListView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public PullToRefreshExpandableListView(Context context, Mode mode) {
|
||||
super(context, mode);
|
||||
}
|
||||
|
||||
public PullToRefreshExpandableListView(Context context, Mode mode, AnimationStyle style) {
|
||||
super(context, mode, style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Orientation getPullToRefreshScrollDirection() {
|
||||
return Orientation.VERTICAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExpandableListView createRefreshableView(Context context, AttributeSet attrs) {
|
||||
final ExpandableListView lv;
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
|
||||
lv = new InternalExpandableListViewSDK9(context, attrs);
|
||||
} else {
|
||||
lv = new InternalExpandableListView(context, attrs);
|
||||
}
|
||||
|
||||
// Set it to this so it can be used in ListActivity/ListFragment
|
||||
lv.setId(android.R.id.list);
|
||||
return lv;
|
||||
}
|
||||
|
||||
class InternalExpandableListView extends ExpandableListView implements EmptyViewMethodAccessor {
|
||||
|
||||
public InternalExpandableListView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmptyView(View emptyView) {
|
||||
PullToRefreshExpandableListView.this.setEmptyView(emptyView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmptyViewInternal(View emptyView) {
|
||||
super.setEmptyView(emptyView);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
final class InternalExpandableListViewSDK9 extends InternalExpandableListView {
|
||||
|
||||
public InternalExpandableListViewSDK9(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
|
||||
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
|
||||
|
||||
final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
|
||||
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
|
||||
|
||||
// Does all of the hard work...
|
||||
OverscrollHelper.overScrollBy(PullToRefreshExpandableListView.this, deltaX, scrollX, deltaY, scrollY,
|
||||
isTouchEvent);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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.
|
||||
*******************************************************************************/
|
||||
package com.handmark.pulltorefresh.library;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.GridView;
|
||||
|
||||
import com.handmark.pulltorefresh.library.internal.EmptyViewMethodAccessor;
|
||||
|
||||
public class PullToRefreshGridView extends PullToRefreshAdapterViewBase<GridView> {
|
||||
|
||||
public PullToRefreshGridView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public PullToRefreshGridView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public PullToRefreshGridView(Context context, Mode mode) {
|
||||
super(context, mode);
|
||||
}
|
||||
|
||||
public PullToRefreshGridView(Context context, Mode mode, AnimationStyle style) {
|
||||
super(context, mode, style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Orientation getPullToRefreshScrollDirection() {
|
||||
return Orientation.VERTICAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final GridView createRefreshableView(Context context, AttributeSet attrs) {
|
||||
final GridView gv;
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
|
||||
gv = new InternalGridViewSDK9(context, attrs);
|
||||
} else {
|
||||
gv = new InternalGridView(context, attrs);
|
||||
}
|
||||
|
||||
// Use Generated ID (from res/values/ids.xml)
|
||||
gv.setId(R.id.gridview);
|
||||
return gv;
|
||||
}
|
||||
|
||||
class InternalGridView extends GridView implements EmptyViewMethodAccessor {
|
||||
|
||||
public InternalGridView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmptyView(View emptyView) {
|
||||
PullToRefreshGridView.this.setEmptyView(emptyView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmptyViewInternal(View emptyView) {
|
||||
super.setEmptyView(emptyView);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
final class InternalGridViewSDK9 extends InternalGridView {
|
||||
|
||||
public InternalGridViewSDK9(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
|
||||
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
|
||||
|
||||
final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
|
||||
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
|
||||
|
||||
// Does all of the hard work...
|
||||
OverscrollHelper.overScrollBy(PullToRefreshGridView.this, deltaX, scrollX, deltaY, scrollY, isTouchEvent);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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.
|
||||
*******************************************************************************/
|
||||
package com.handmark.pulltorefresh.library;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.HorizontalScrollView;
|
||||
|
||||
public class PullToRefreshHorizontalScrollView extends PullToRefreshBase<HorizontalScrollView> {
|
||||
|
||||
public PullToRefreshHorizontalScrollView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public PullToRefreshHorizontalScrollView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public PullToRefreshHorizontalScrollView(Context context, Mode mode) {
|
||||
super(context, mode);
|
||||
}
|
||||
|
||||
public PullToRefreshHorizontalScrollView(Context context, Mode mode, AnimationStyle style) {
|
||||
super(context, mode, style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Orientation getPullToRefreshScrollDirection() {
|
||||
return Orientation.HORIZONTAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HorizontalScrollView createRefreshableView(Context context, AttributeSet attrs) {
|
||||
HorizontalScrollView scrollView;
|
||||
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
|
||||
scrollView = new InternalHorizontalScrollViewSDK9(context, attrs);
|
||||
} else {
|
||||
scrollView = new HorizontalScrollView(context, attrs);
|
||||
}
|
||||
|
||||
scrollView.setId(R.id.scrollview);
|
||||
return scrollView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReadyForPullStart() {
|
||||
return mRefreshableView.getScrollX() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReadyForPullEnd() {
|
||||
View scrollViewChild = mRefreshableView.getChildAt(0);
|
||||
if (null != scrollViewChild) {
|
||||
return mRefreshableView.getScrollX() >= (scrollViewChild.getWidth() - getWidth());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
final class InternalHorizontalScrollViewSDK9 extends HorizontalScrollView {
|
||||
|
||||
public InternalHorizontalScrollViewSDK9(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
|
||||
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
|
||||
|
||||
final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
|
||||
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
|
||||
|
||||
// Does all of the hard work...
|
||||
OverscrollHelper.overScrollBy(PullToRefreshHorizontalScrollView.this, deltaX, scrollX, deltaY, scrollY,
|
||||
getScrollRange(), isTouchEvent);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Taken from the AOSP ScrollView source
|
||||
*/
|
||||
private int getScrollRange() {
|
||||
int scrollRange = 0;
|
||||
if (getChildCount() > 0) {
|
||||
View child = getChildAt(0);
|
||||
scrollRange = Math.max(0, child.getWidth() - (getWidth() - getPaddingLeft() - getPaddingRight()));
|
||||
}
|
||||
return scrollRange;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,337 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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.
|
||||
*******************************************************************************/
|
||||
package com.handmark.pulltorefresh.library;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
import com.handmark.pulltorefresh.library.internal.EmptyViewMethodAccessor;
|
||||
import com.handmark.pulltorefresh.library.internal.LoadingLayout;
|
||||
|
||||
public class PullToRefreshListView extends PullToRefreshAdapterViewBase<ListView> {
|
||||
|
||||
private LoadingLayout mHeaderLoadingView;
|
||||
private LoadingLayout mFooterLoadingView;
|
||||
|
||||
private FrameLayout mLvFooterLoadingFrame;
|
||||
|
||||
private boolean mListViewExtrasEnabled;
|
||||
|
||||
public PullToRefreshListView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public PullToRefreshListView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public PullToRefreshListView(Context context, Mode mode) {
|
||||
super(context, mode);
|
||||
}
|
||||
|
||||
public PullToRefreshListView(Context context, Mode mode, AnimationStyle style) {
|
||||
super(context, mode, style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Orientation getPullToRefreshScrollDirection() {
|
||||
return Orientation.VERTICAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRefreshing(final boolean doScroll) {
|
||||
/**
|
||||
* If we're not showing the Refreshing view, or the list is empty, the
|
||||
* the header/footer views won't show so we use the normal method.
|
||||
*/
|
||||
ListAdapter adapter = mRefreshableView.getAdapter();
|
||||
if (!mListViewExtrasEnabled || !getShowViewWhileRefreshing() || null == adapter || adapter.isEmpty()) {
|
||||
super.onRefreshing(doScroll);
|
||||
return;
|
||||
}
|
||||
|
||||
super.onRefreshing(false);
|
||||
|
||||
final LoadingLayout origLoadingView, listViewLoadingView, oppositeListViewLoadingView;
|
||||
final int selection, scrollToY;
|
||||
|
||||
switch (getCurrentMode()) {
|
||||
case MANUAL_REFRESH_ONLY:
|
||||
case PULL_FROM_END:
|
||||
origLoadingView = getFooterLayout();
|
||||
listViewLoadingView = mFooterLoadingView;
|
||||
oppositeListViewLoadingView = mHeaderLoadingView;
|
||||
selection = mRefreshableView.getCount() - 1;
|
||||
scrollToY = getScrollY() - getFooterSize();
|
||||
break;
|
||||
case PULL_FROM_START:
|
||||
default:
|
||||
origLoadingView = getHeaderLayout();
|
||||
listViewLoadingView = mHeaderLoadingView;
|
||||
oppositeListViewLoadingView = mFooterLoadingView;
|
||||
selection = 0;
|
||||
scrollToY = getScrollY() + getHeaderSize();
|
||||
break;
|
||||
}
|
||||
|
||||
// Hide our original Loading View
|
||||
origLoadingView.reset();
|
||||
origLoadingView.hideAllViews();
|
||||
|
||||
// Make sure the opposite end is hidden too
|
||||
oppositeListViewLoadingView.setVisibility(View.GONE);
|
||||
|
||||
// Show the ListView Loading View and set it to refresh.
|
||||
listViewLoadingView.setVisibility(View.VISIBLE);
|
||||
listViewLoadingView.refreshing();
|
||||
|
||||
if (doScroll) {
|
||||
// We need to disable the automatic visibility changes for now
|
||||
disableLoadingLayoutVisibilityChanges();
|
||||
|
||||
// We scroll slightly so that the ListView's header/footer is at the
|
||||
// same Y position as our normal header/footer
|
||||
setHeaderScroll(scrollToY);
|
||||
|
||||
// Make sure the ListView is scrolled to show the loading
|
||||
// header/footer
|
||||
mRefreshableView.setSelection(selection);
|
||||
|
||||
// Smooth scroll as normal
|
||||
smoothScrollTo(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReset() {
|
||||
/**
|
||||
* If the extras are not enabled, just call up to super and return.
|
||||
*/
|
||||
if (!mListViewExtrasEnabled) {
|
||||
super.onReset();
|
||||
return;
|
||||
}
|
||||
|
||||
final LoadingLayout originalLoadingLayout, listViewLoadingLayout;
|
||||
final int scrollToHeight, selection;
|
||||
final boolean scrollLvToEdge;
|
||||
|
||||
switch (getCurrentMode()) {
|
||||
case MANUAL_REFRESH_ONLY:
|
||||
case PULL_FROM_END:
|
||||
originalLoadingLayout = getFooterLayout();
|
||||
listViewLoadingLayout = mFooterLoadingView;
|
||||
selection = mRefreshableView.getCount() - 1;
|
||||
scrollToHeight = getFooterSize();
|
||||
scrollLvToEdge = Math.abs(mRefreshableView.getLastVisiblePosition() - selection) <= 1;
|
||||
break;
|
||||
case PULL_FROM_START:
|
||||
default:
|
||||
originalLoadingLayout = getHeaderLayout();
|
||||
listViewLoadingLayout = mHeaderLoadingView;
|
||||
scrollToHeight = -getHeaderSize();
|
||||
selection = 0;
|
||||
scrollLvToEdge = Math.abs(mRefreshableView.getFirstVisiblePosition() - selection) <= 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the ListView header loading layout is showing, then we need to
|
||||
// flip so that the original one is showing instead
|
||||
if (listViewLoadingLayout.getVisibility() == View.VISIBLE) {
|
||||
|
||||
// Set our Original View to Visible
|
||||
originalLoadingLayout.showInvisibleViews();
|
||||
|
||||
// Hide the ListView Header/Footer
|
||||
listViewLoadingLayout.setVisibility(View.GONE);
|
||||
|
||||
/**
|
||||
* Scroll so the View is at the same Y as the ListView
|
||||
* header/footer, but only scroll if: we've pulled to refresh, it's
|
||||
* positioned correctly
|
||||
*/
|
||||
if (scrollLvToEdge && getState() != State.MANUAL_REFRESHING) {
|
||||
mRefreshableView.setSelection(selection);
|
||||
setHeaderScroll(scrollToHeight);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, call up to super
|
||||
super.onReset();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LoadingLayoutProxy createLoadingLayoutProxy(final boolean includeStart, final boolean includeEnd) {
|
||||
LoadingLayoutProxy proxy = super.createLoadingLayoutProxy(includeStart, includeEnd);
|
||||
|
||||
if (mListViewExtrasEnabled) {
|
||||
final Mode mode = getMode();
|
||||
|
||||
if (includeStart && mode.showHeaderLoadingLayout()) {
|
||||
proxy.addLayout(mHeaderLoadingView);
|
||||
}
|
||||
if (includeEnd && mode.showFooterLoadingLayout()) {
|
||||
proxy.addLayout(mFooterLoadingView);
|
||||
}
|
||||
}
|
||||
|
||||
return proxy;
|
||||
}
|
||||
|
||||
protected ListView createListView(Context context, AttributeSet attrs) {
|
||||
final ListView lv;
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
|
||||
lv = new InternalListViewSDK9(context, attrs);
|
||||
} else {
|
||||
lv = new InternalListView(context, attrs);
|
||||
}
|
||||
return lv;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListView createRefreshableView(Context context, AttributeSet attrs) {
|
||||
ListView lv = createListView(context, attrs);
|
||||
|
||||
// Set it to this so it can be used in ListActivity/ListFragment
|
||||
lv.setId(android.R.id.list);
|
||||
return lv;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleStyledAttributes(TypedArray a) {
|
||||
super.handleStyledAttributes(a);
|
||||
|
||||
mListViewExtrasEnabled = a.getBoolean(R.styleable.PullToRefresh_ptrListViewExtrasEnabled, true);
|
||||
|
||||
if (mListViewExtrasEnabled) {
|
||||
final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL);
|
||||
|
||||
// Create Loading Views ready for use later
|
||||
FrameLayout frame = new FrameLayout(getContext());
|
||||
mHeaderLoadingView = createLoadingLayout(getContext(), Mode.PULL_FROM_START, a);
|
||||
mHeaderLoadingView.setVisibility(View.GONE);
|
||||
frame.addView(mHeaderLoadingView, lp);
|
||||
mRefreshableView.addHeaderView(frame, null, false);
|
||||
|
||||
mLvFooterLoadingFrame = new FrameLayout(getContext());
|
||||
mFooterLoadingView = createLoadingLayout(getContext(), Mode.PULL_FROM_END, a);
|
||||
mFooterLoadingView.setVisibility(View.GONE);
|
||||
mLvFooterLoadingFrame.addView(mFooterLoadingView, lp);
|
||||
|
||||
/**
|
||||
* If the value for Scrolling While Refreshing hasn't been
|
||||
* explicitly set via XML, enable Scrolling While Refreshing.
|
||||
*/
|
||||
if (!a.hasValue(R.styleable.PullToRefresh_ptrScrollingWhileRefreshingEnabled)) {
|
||||
setScrollingWhileRefreshingEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
final class InternalListViewSDK9 extends InternalListView {
|
||||
|
||||
public InternalListViewSDK9(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
|
||||
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
|
||||
|
||||
final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
|
||||
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
|
||||
|
||||
// Does all of the hard work...
|
||||
OverscrollHelper.overScrollBy(PullToRefreshListView.this, deltaX, scrollX, deltaY, scrollY, isTouchEvent);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
protected class InternalListView extends ListView implements EmptyViewMethodAccessor {
|
||||
|
||||
private boolean mAddedLvFooter = false;
|
||||
|
||||
public InternalListView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
/**
|
||||
* This is a bit hacky, but Samsung's ListView has got a bug in it
|
||||
* when using Header/Footer Views and the list is empty. This masks
|
||||
* the issue so that it doesn't cause an FC. See Issue #66.
|
||||
*/
|
||||
try {
|
||||
super.dispatchDraw(canvas);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||
/**
|
||||
* This is a bit hacky, but Samsung's ListView has got a bug in it
|
||||
* when using Header/Footer Views and the list is empty. This masks
|
||||
* the issue so that it doesn't cause an FC. See Issue #66.
|
||||
*/
|
||||
try {
|
||||
return super.dispatchTouchEvent(ev);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAdapter(ListAdapter adapter) {
|
||||
// Add the Footer View at the last possible moment
|
||||
if (null != mLvFooterLoadingFrame && !mAddedLvFooter) {
|
||||
addFooterView(mLvFooterLoadingFrame, null, false);
|
||||
mAddedLvFooter = true;
|
||||
}
|
||||
|
||||
super.setAdapter(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmptyView(View emptyView) {
|
||||
PullToRefreshListView.this.setEmptyView(emptyView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmptyViewInternal(View emptyView) {
|
||||
super.setEmptyView(emptyView);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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.
|
||||
*******************************************************************************/
|
||||
package com.handmark.pulltorefresh.library;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
public class PullToRefreshScrollView extends PullToRefreshBase<ScrollView> {
|
||||
|
||||
public PullToRefreshScrollView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public PullToRefreshScrollView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public PullToRefreshScrollView(Context context, Mode mode) {
|
||||
super(context, mode);
|
||||
}
|
||||
|
||||
public PullToRefreshScrollView(Context context, Mode mode, AnimationStyle style) {
|
||||
super(context, mode, style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Orientation getPullToRefreshScrollDirection() {
|
||||
return Orientation.VERTICAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ScrollView createRefreshableView(Context context, AttributeSet attrs) {
|
||||
ScrollView scrollView;
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
|
||||
scrollView = new InternalScrollViewSDK9(context, attrs);
|
||||
} else {
|
||||
scrollView = new ScrollView(context, attrs);
|
||||
}
|
||||
|
||||
scrollView.setId(R.id.scrollview);
|
||||
return scrollView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReadyForPullStart() {
|
||||
return mRefreshableView.getScrollY() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReadyForPullEnd() {
|
||||
View scrollViewChild = mRefreshableView.getChildAt(0);
|
||||
if (null != scrollViewChild) {
|
||||
return mRefreshableView.getScrollY() >= (scrollViewChild.getHeight() - getHeight());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
final class InternalScrollViewSDK9 extends ScrollView {
|
||||
|
||||
public InternalScrollViewSDK9(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
|
||||
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
|
||||
|
||||
final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
|
||||
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
|
||||
|
||||
// Does all of the hard work...
|
||||
OverscrollHelper.overScrollBy(PullToRefreshScrollView.this, deltaX, scrollX, deltaY, scrollY,
|
||||
getScrollRange(), isTouchEvent);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Taken from the AOSP ScrollView source
|
||||
*/
|
||||
private int getScrollRange() {
|
||||
int scrollRange = 0;
|
||||
if (getChildCount() > 0) {
|
||||
View child = getChildAt(0);
|
||||
scrollRange = Math.max(0, child.getHeight() - (getHeight() - getPaddingBottom() - getPaddingTop()));
|
||||
}
|
||||
return scrollRange;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011, 2012 Chris Banes.
|
||||
*
|
||||
* 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.
|
||||
*******************************************************************************/
|
||||
package com.handmark.pulltorefresh.library;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebView;
|
||||
|
||||
public class PullToRefreshWebView extends PullToRefreshBase<WebView> {
|
||||
|
||||
private static final OnRefreshListener<WebView> defaultOnRefreshListener = new OnRefreshListener<WebView>() {
|
||||
|
||||
@Override
|
||||
public void onRefresh(PullToRefreshBase<WebView> refreshView) {
|
||||
refreshView.getRefreshableView().reload();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private final WebChromeClient defaultWebChromeClient = new WebChromeClient() {
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(WebView view, int newProgress) {
|
||||
if (newProgress == 100) {
|
||||
onRefreshComplete();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
public PullToRefreshWebView(Context context) {
|
||||
super(context);
|
||||
|
||||
/**
|
||||
* Added so that by default, Pull-to-Refresh refreshes the page
|
||||
*/
|
||||
setOnRefreshListener(defaultOnRefreshListener);
|
||||
mRefreshableView.setWebChromeClient(defaultWebChromeClient);
|
||||
}
|
||||
|
||||
public PullToRefreshWebView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
/**
|
||||
* Added so that by default, Pull-to-Refresh refreshes the page
|
||||
*/
|
||||
setOnRefreshListener(defaultOnRefreshListener);
|
||||
mRefreshableView.setWebChromeClient(defaultWebChromeClient);
|
||||
}
|
||||
|
||||
public PullToRefreshWebView(Context context, Mode mode) {
|
||||
super(context, mode);
|
||||
|
||||
/**
|
||||
* Added so that by default, Pull-to-Refresh refreshes the page
|
||||
*/
|
||||
setOnRefreshListener(defaultOnRefreshListener);
|
||||
mRefreshableView.setWebChromeClient(defaultWebChromeClient);
|
||||
}
|
||||
|
||||
public PullToRefreshWebView(Context context, Mode mode, AnimationStyle style) {
|
||||
super(context, mode, style);
|
||||
|
||||
/**
|
||||
* Added so that by default, Pull-to-Refresh refreshes the page
|
||||
*/
|
||||
setOnRefreshListener(defaultOnRefreshListener);
|
||||
mRefreshableView.setWebChromeClient(defaultWebChromeClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Orientation getPullToRefreshScrollDirection() {
|
||||
return Orientation.VERTICAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WebView createRefreshableView(Context context, AttributeSet attrs) {
|
||||
WebView webView;
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
|
||||
webView = new InternalWebViewSDK9(context, attrs);
|
||||
} else {
|
||||
webView = new WebView(context, attrs);
|
||||
}
|
||||
|
||||
webView.setId(R.id.webview);
|
||||
return webView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReadyForPullStart() {
|
||||
return mRefreshableView.getScrollY() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReadyForPullEnd() {
|
||||
float exactContentHeight = (float) Math.floor(mRefreshableView.getContentHeight() * mRefreshableView.getScale());
|
||||
return mRefreshableView.getScrollY() >= (exactContentHeight - mRefreshableView.getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPtrRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onPtrRestoreInstanceState(savedInstanceState);
|
||||
mRefreshableView.restoreState(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPtrSaveInstanceState(Bundle saveState) {
|
||||
super.onPtrSaveInstanceState(saveState);
|
||||
mRefreshableView.saveState(saveState);
|
||||
}
|
||||
|
||||
@TargetApi(9)
|
||||
final class InternalWebViewSDK9 extends WebView {
|
||||
|
||||
// WebView doesn't always scroll back to it's edge so we add some
|
||||
// fuzziness
|
||||
static final int OVERSCROLL_FUZZY_THRESHOLD = 2;
|
||||
|
||||
// WebView seems quite reluctant to overscroll so we use the scale
|
||||
// factor to scale it's value
|
||||
static final float OVERSCROLL_SCALE_FACTOR = 1.5f;
|
||||
|
||||
public InternalWebViewSDK9(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
|
||||
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
|
||||
|
||||
final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
|
||||
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
|
||||
|
||||
// Does all of the hard work...
|
||||
OverscrollHelper.overScrollBy(PullToRefreshWebView.this, deltaX, scrollX, deltaY, scrollY,
|
||||
getScrollRange(), OVERSCROLL_FUZZY_THRESHOLD, OVERSCROLL_SCALE_FACTOR, isTouchEvent);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
private int getScrollRange() {
|
||||
return (int) Math.max(0, Math.floor(mRefreshableView.getContentHeight() * mRefreshableView.getScale())
|
||||
- (getHeight() - getPaddingBottom() - getPaddingTop()));
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue