Compare commits
2345 commits
Author | SHA1 | Date | |
---|---|---|---|
|
32e0a24358 | ||
|
bfa08fc032 | ||
|
eb4876a6d6 | ||
|
feb89bb51a | ||
|
3d766d70f3 | ||
|
b0ee223b9e | ||
|
e27b7b07fe | ||
|
025e908c22 | ||
|
2ea920019c | ||
|
8024700b7d | ||
|
d0b0a03bd9 | ||
|
803c74c513 | ||
|
731f8d56e5 | ||
|
6cd52f7c51 | ||
|
f6002b0c6c | ||
|
80b0ecb490 | ||
|
4a0c83b0a2 | ||
|
bf165c7eff | ||
|
36f15ffe9e | ||
|
4e78813c28 | ||
|
dbaf396f81 | ||
|
e483038b6c | ||
|
4e557fffae | ||
|
25fcb84ea8 | ||
|
256dbc55b2 | ||
|
3d7e89e270 | ||
|
21b142d341 | ||
|
8772bcb0b7 | ||
|
ab16d7e105 | ||
|
2c9547c621 | ||
|
46ad05a832 | ||
|
e1b127d282 | ||
|
8dcb690a9c | ||
|
2845945763 | ||
|
3e93dcfc4d | ||
|
71b2f8bc32 | ||
|
9971c25649 | ||
|
6ec2e6ee9a | ||
|
ccf3b7f6a5 | ||
|
931e59319a | ||
|
23da382b22 | ||
|
dd628ed6f1 | ||
|
74b317e159 | ||
|
ea8d455c05 | ||
|
885d6900a2 | ||
|
836cdf28a9 | ||
|
9fc5893f42 | ||
|
0d205b310e | ||
|
979c26ff40 | ||
|
da8ff7bc64 | ||
|
a2389ca61e | ||
|
3ae38cb1de | ||
|
ea5072945e | ||
|
2fbe025bf9 | ||
|
ec6220a97d | ||
|
35c54829a8 | ||
|
fb1d6990c1 | ||
|
3c84274e2d | ||
|
ba59bf8216 | ||
|
bb99213848 | ||
|
210d9afa1a | ||
|
125681b938 | ||
|
b9f3e7979b | ||
|
c6dd54d200 | ||
|
9f334c09b2 | ||
|
71f112673b | ||
|
de92e1d9a7 | ||
|
c441674943 | ||
|
57b14460dc | ||
|
ae60624744 | ||
|
054c09d999 | ||
|
4236a134ad | ||
|
d6a21ee42b | ||
|
533c5c1489 | ||
|
e7c97e07a5 | ||
|
8926a79cca | ||
|
94bc86b5d4 | ||
|
4ce212a6e6 | ||
|
78faa0ea14 | ||
|
e6267ef6a2 | ||
|
9d88c871d9 | ||
|
644640c14f | ||
|
9dc8d55897 | ||
|
388674b05e | ||
|
b70cd181d6 | ||
|
676c0a444c | ||
|
06e0bdf333 | ||
|
c3a189d64f | ||
|
6c0f1941af | ||
|
0d976516a7 | ||
|
9ad119d3fb | ||
|
2068025cdd | ||
|
50f2719832 | ||
|
41497b5f91 | ||
|
bdd4039785 | ||
|
bd8278343f | ||
|
bc83f0a497 | ||
|
2423822615 | ||
|
0622858d3a | ||
|
14d331c91a | ||
|
8682b1c09f | ||
|
a85b546e1f | ||
|
b8ba0cc8e8 | ||
|
efb047775f | ||
|
781b9ec61f | ||
|
52085af6d1 | ||
|
49a6742eb7 | ||
|
58c98afb48 | ||
|
1f54da6d55 | ||
|
63e48d7ec3 | ||
|
80df8aa037 | ||
|
9eebaf1a1f | ||
|
c907ec2508 | ||
|
7a409b795f | ||
|
383908ec7f | ||
|
7c0547b085 | ||
|
f30578d5fc | ||
|
1de4bbcdec | ||
|
31df20ffe5 | ||
|
f5063de70f | ||
|
b1992e8d54 | ||
|
f7e3b65274 | ||
|
cbd000259a | ||
|
cff50fda3b | ||
|
6e68da67e2 | ||
|
2e5dddafde | ||
|
3493ad40ab | ||
|
c881bbfa98 | ||
|
778025e989 | ||
|
01c5c10def | ||
|
7b15930093 | ||
|
31caedd7ed | ||
|
96ef1416f8 | ||
|
70e28e3e0f | ||
|
9933fcf5fd | ||
|
9a589d5b46 | ||
|
10367f64de | ||
|
6aae0f2d5e | ||
|
26d7012e46 | ||
|
f9bfa48f00 | ||
|
717b8f380b | ||
|
40888c3073 | ||
|
5edcd9b49b | ||
|
48058e9416 | ||
|
e1221c4924 | ||
|
597c787834 | ||
|
059c0324c1 | ||
|
20ab3dad0d | ||
|
ad4467af93 | ||
|
111546bc4c | ||
|
e8f931f213 | ||
|
117a730583 | ||
|
509e41bf76 | ||
|
aa78da75dd | ||
|
70438ee0ed | ||
|
80028c6207 | ||
|
ee6aff3824 | ||
|
d4727072af | ||
|
dce1acbb80 | ||
|
ea1d3b5848 | ||
|
04063a9916 | ||
|
c72f469ab3 | ||
|
08c46605f1 | ||
|
3bc06025d9 | ||
|
db85bde641 | ||
|
459ddf29ba | ||
|
cb5761e116 | ||
|
031b2014f5 | ||
|
5917dffd65 | ||
|
dec0b2d265 | ||
|
edc7e7a8f5 | ||
|
bf799edb76 | ||
|
2914a30cf0 | ||
|
307bddcae7 | ||
|
1467776c6c | ||
|
9489d93943 | ||
|
71ca960987 | ||
|
a98d6c516a | ||
|
31c4dab36a | ||
|
2a846edca4 | ||
|
c5411c1c93 | ||
|
1e560590a2 | ||
|
89107af3c3 | ||
|
e120550513 | ||
|
6bd671126b | ||
|
dea78a0ce8 | ||
|
68a277076f | ||
|
b01f30900a | ||
|
2edb3184b2 | ||
|
faf065ea73 | ||
|
dba4edd8ef | ||
|
88b0299ef9 | ||
|
c3309175a6 | ||
|
0c6917c775 | ||
|
62372ec205 | ||
|
dd59459786 | ||
|
0f66d005c7 | ||
|
5ac1b2bcc6 | ||
|
b0f866c889 | ||
|
e626803e9f | ||
|
91b7eeeb45 | ||
|
7a82883f57 | ||
|
2f818ce65f | ||
|
17eb5bff05 | ||
|
d2ce889962 | ||
|
525c5f8aa4 | ||
|
0c8ed326bf | ||
|
a1e4916ae3 | ||
|
2a5e6d7756 | ||
|
a12d8dd8a6 | ||
|
48efc1fa48 | ||
|
7abd2d56f6 | ||
|
00c6a72671 | ||
|
bc39d3a462 | ||
|
d8317b2979 | ||
|
cac91eb0fe | ||
|
3dc26974b4 | ||
|
df382cb539 | ||
|
cc862669b2 | ||
|
b45e61f756 | ||
|
8e3a7a097a | ||
|
6f12a7b24a | ||
|
8f56663059 | ||
|
e4a6906231 | ||
|
192b5c2ac7 | ||
|
3aea91eb1e | ||
|
cf4c773a67 | ||
|
3ab5e1ddc3 | ||
|
aedabb8b74 | ||
|
ed6ca486d6 | ||
|
aad2f2e6db | ||
|
9966ddad9d | ||
|
3ee49bbaa2 | ||
|
eb7729e09a | ||
|
fe189ba4d4 | ||
|
c5e5643164 | ||
|
6ec0bcc184 | ||
|
54e0c66d8b | ||
|
bca9fb0b5a | ||
|
96dc850ee0 | ||
|
1512dae014 | ||
|
d591955145 | ||
|
4501def29f | ||
|
044d19d46d | ||
|
75a4648e13 | ||
|
ceec034a4f | ||
|
3660a8bd78 | ||
|
8ed5d8a3dc | ||
|
6befc92ee5 | ||
|
6a912f98f5 | ||
|
477948a495 | ||
|
e17aeecf77 | ||
|
8ce316561b | ||
|
b28d5678bc | ||
|
33a0d113b8 | ||
|
2ef5a5903d | ||
|
46f713e688 | ||
|
c25eff4c0d | ||
|
52fa8ebf42 | ||
|
ce5a8292e2 | ||
|
7b5e7ecef8 | ||
|
d2a7588861 | ||
|
7efbbc3f11 | ||
|
7186071772 | ||
|
ad04c4e2ed | ||
|
67d65a1532 | ||
|
aee5bfdf50 | ||
|
931a0345a4 | ||
|
61895b666c | ||
|
45a3899a0d | ||
|
97e37dd7b9 | ||
|
6b071deb96 | ||
|
999cb08aef | ||
|
da0a4d5c13 | ||
|
1edd4acd9c | ||
|
88ccd667ab | ||
|
5551240519 | ||
|
b2b9bac42d | ||
|
1f7a00e999 | ||
|
14f31eae37 | ||
|
be6b48f8e0 | ||
|
054b46560c | ||
|
b17013f5bb | ||
|
5d0b5b91b0 | ||
|
cd8bf4a68d | ||
|
558eaf833a | ||
|
d77ce4e0cc | ||
|
b47029cb57 | ||
|
85cf3738e7 | ||
|
097d646635 | ||
|
87f1172481 | ||
|
f04bf4c3aa | ||
|
2fa5407b49 | ||
|
36ecfb678e | ||
|
5e8dba4d90 | ||
|
12bded682c | ||
|
cee51eb2cc | ||
|
c15bd77b39 | ||
|
ca39b928bb | ||
|
f916a826b5 | ||
|
2786d8abed | ||
|
b9f0adc9c2 | ||
|
d6c4d5e133 | ||
|
fb7f29bdf4 | ||
|
b971dbbe4b | ||
|
bc04472b6f | ||
|
1b544d62c3 | ||
|
51f298be8f | ||
|
01f7a4874d | ||
|
d8295cd677 | ||
|
4f1895bc50 | ||
|
e9340b429a | ||
|
f6a44672be | ||
|
7aab762712 | ||
|
95fd195df8 | ||
|
d0a600e016 | ||
|
6b83421cab | ||
|
cd2dfe1962 | ||
|
0b54044752 | ||
|
077089c94c | ||
|
d65feb30b4 | ||
|
d877bfbdfb | ||
|
17f104b27a | ||
|
fcc31f90ad | ||
|
ed890c3c30 | ||
|
9ce60c896c | ||
|
197e7ff085 | ||
|
6779eb0e2b | ||
|
860fa9d8b4 | ||
|
0d9364e7c8 | ||
|
d65f8f8a4f | ||
|
aa96993584 | ||
|
08df1b2958 | ||
|
28d83d9437 | ||
|
ebd35bf026 | ||
|
dfcc5b6b92 | ||
|
7e2881b5f0 | ||
|
32e6daf36d | ||
|
30dd58af93 | ||
|
3956d84310 | ||
|
b103387d73 | ||
|
2f782debeb | ||
|
c1b341ad51 | ||
|
cde7fd6565 | ||
|
4df292bddf | ||
|
7c6514d010 | ||
|
bd2d5068d7 | ||
|
0fd8ab0ade | ||
|
1033711357 | ||
|
165d180fe6 | ||
|
2791ac49c5 | ||
|
4b4aadcc5b | ||
|
70e323eb0a | ||
|
b58d971457 | ||
|
4a2ba10d45 | ||
|
4c59089c29 | ||
|
29671a374e | ||
|
4b51be2b0c | ||
|
88669ceaef | ||
|
1cfa5aaac5 | ||
|
3219ba0a5d | ||
|
c8dbb0a0fd | ||
|
33bbc0d07e | ||
|
80063ea903 | ||
|
b6294db8cf | ||
|
c3e53977f7 | ||
|
9db961766d | ||
|
0f06f42e08 | ||
|
00581082ed | ||
|
4b08919d7d | ||
|
3c26c39a5a | ||
|
af63d0f28c | ||
|
5f5bddf5e9 | ||
|
8d81424365 | ||
|
fcac420ac3 | ||
|
9c07ca2763 | ||
|
90e113e8ab | ||
|
93bb652dd6 | ||
|
ae6c97a498 | ||
|
7913bf4eff | ||
|
b677d4fa62 | ||
|
7077420646 | ||
|
7debeb13c0 | ||
|
ae13590204 | ||
|
cfe4556aa1 | ||
|
51f772247a | ||
|
bb93b66419 | ||
|
a30303a846 | ||
|
3ca1baf68f | ||
|
0433e464d8 | ||
|
67e9707b10 | ||
|
f86c30c608 | ||
|
50e8d3e2ef | ||
|
8a0a8e6c10 | ||
|
d823c20396 | ||
|
07cfd6cd6e | ||
|
08f4c62b4a | ||
|
ee300722af | ||
|
37aa73879c | ||
|
612b492aee | ||
|
82d4649346 | ||
|
7195ab2afd | ||
|
a4d3ae560b | ||
|
a0c745641a | ||
|
a06ff41ed8 | ||
|
c4f01148ca | ||
|
3f30660998 | ||
|
44dedaddff | ||
|
6e9ec4210d | ||
|
95e39e4f4c | ||
|
e7f700ee9a | ||
|
f55c7ac21c | ||
|
02f076de57 | ||
|
aa4c20bd8c | ||
|
806b3beaa7 | ||
|
608c265390 | ||
|
41bfffc2f4 | ||
|
e0dbe8921f | ||
|
bbed112443 | ||
|
8b5b6d0e64 | ||
|
8cd3fcfd38 | ||
|
5e1637da62 | ||
|
ce3939aeb1 | ||
|
9af3bb43cf | ||
|
30fbb9f05e | ||
|
9e34557ed8 | ||
|
3575394ca7 | ||
|
2699fe9f1f | ||
|
af7997321c | ||
|
e605b8c695 | ||
|
6cc1ba746a | ||
|
55b3ec81b6 | ||
|
b143419520 | ||
|
4b4232ce6b | ||
|
5216a11170 | ||
|
c5a25c200c | ||
|
dabdcc1d31 | ||
|
d34fcab30c | ||
|
576f88ddbc | ||
|
b9c580ca3f | ||
|
c09e39b23f | ||
|
5ee5611fbe | ||
|
3e1657d8eb | ||
|
a3399e7aed | ||
|
bb37692e46 | ||
|
1b91394fe9 | ||
|
e498b5b0ec | ||
|
5d038118f5 | ||
|
48c1cfb6ab | ||
|
098f9f939d | ||
|
4563378505 | ||
|
3105da068d | ||
|
aaee076718 | ||
|
c570be349d | ||
|
0604307b88 | ||
|
9542bed54a | ||
|
08c8228ab6 | ||
|
b6fee3e25d | ||
|
5ff4438a42 | ||
|
904dd748cb | ||
|
6116b4714f | ||
|
b572fe1aee | ||
|
2119e0c754 | ||
|
986216e6ad | ||
|
9a1cf7655b | ||
|
60b740021a | ||
|
4a7c367ab5 | ||
|
e0f345b616 | ||
|
fc4f0396a4 | ||
|
ee8deedd76 | ||
|
fc5f3adcd9 | ||
|
a4d44406d6 | ||
|
ad90694879 | ||
|
818703c573 | ||
|
eb21c07f5b | ||
|
b74115fe07 | ||
|
88efba96ef | ||
|
5d3097c43f | ||
|
8b100b4a17 | ||
|
9bd15ed5c4 | ||
|
55a12873b8 | ||
|
68d7c6daf7 | ||
|
c264c28840 | ||
|
e31898a8ec | ||
|
6e7288f0b5 | ||
|
04afa6a271 | ||
|
1a71242fc6 | ||
|
e026a748e2 | ||
|
549aba88df | ||
|
1c85dd0e32 | ||
|
d4ec8b917c | ||
|
3aa7523435 | ||
|
3a84650cd1 | ||
|
eac16c64e4 | ||
|
4b1d2401f6 | ||
|
707bf52d16 | ||
|
6200a7f291 | ||
|
a3b70aa061 | ||
|
8dbc3510b7 | ||
|
4aa9d19e2e | ||
|
ab9f7a0fe3 | ||
|
c942502d9b | ||
|
310a8a7310 | ||
|
a603a5624a | ||
|
afd2959e9e | ||
|
174673d602 | ||
|
df723fe70e | ||
|
0d611f7e36 | ||
|
548e668cd7 | ||
|
6b32cf6684 | ||
|
ef3866c1ab | ||
|
92e97ddefe | ||
|
73f508cf74 | ||
|
75c426f0fd | ||
|
8d5f4ce999 | ||
|
a848d0d19b | ||
|
ef38b7fc65 | ||
|
8995260066 | ||
|
1c7e1102f0 | ||
|
5362e06394 | ||
|
028a2dcb77 | ||
|
be57a97f6c | ||
|
102acb0d13 | ||
|
35c6cf9a85 | ||
|
9dafbb28f6 | ||
|
0f9fa29f78 | ||
|
21a950893e | ||
|
7d35b54fde | ||
|
4fa643d234 | ||
|
e8093a6fa3 | ||
|
c19bad70c2 | ||
|
1c81a9b3c0 | ||
|
485bac2a96 | ||
|
4d873e59f0 | ||
|
a5346609dd | ||
|
c47baa6cc4 | ||
|
5301facaa9 | ||
|
46bb93f361 | ||
|
0a05b26a33 | ||
|
45a40b1faa | ||
|
727b6c69a6 | ||
|
882e8c15cf | ||
|
998655fd48 | ||
|
a67985d040 | ||
|
7b3b52e3b0 | ||
|
4dc84c2564 | ||
|
81821020bc | ||
|
4f97642aea | ||
|
7e3b4404cf | ||
|
ad51d09672 | ||
|
b4dc50306f | ||
|
0c4cabc755 | ||
|
ead8520333 | ||
|
8faa515ac4 | ||
|
b970b27d9e | ||
|
97f93e09d6 | ||
|
b0b552a060 | ||
|
d3597d123d | ||
|
bc137ebb75 | ||
|
dc41fe509b | ||
|
88b28e4c46 | ||
|
fe1a5c8ab0 | ||
|
802cab7818 | ||
|
9b9def5a73 | ||
|
37201cbf3f | ||
|
f6d640b84a | ||
|
ea56e1d99a | ||
|
9a2ffeded1 | ||
|
8c036ae06b | ||
|
17851f77fd | ||
|
5c68e2e420 | ||
|
0b2fd9c59a | ||
|
465a7fe38d | ||
|
7b25eec29f | ||
|
fa204028f4 | ||
|
94cca145e3 | ||
|
b4435b15a7 | ||
|
e8727af212 | ||
|
b313da0563 | ||
|
12e1ac4713 | ||
|
214d97ac51 | ||
|
44f1122723 | ||
|
e4c4428291 | ||
|
ce2b2f109b | ||
|
d3f758e921 | ||
|
57913f81bd | ||
|
ae5644c725 | ||
|
f7384a2360 | ||
|
0b86a61259 | ||
|
5addee3163 | ||
|
8a61cdb158 | ||
|
121506d042 | ||
|
9ad3ce3a49 | ||
|
854110a092 | ||
|
2a25d1808c | ||
|
6533955727 | ||
|
ef52da9d2c | ||
|
44d8d6f392 | ||
|
1050dfaf64 | ||
|
1c8373e9ff | ||
|
b4d9a17ae4 | ||
|
c145965d3d | ||
|
7f1b3bda20 | ||
|
eadd8fa5bc | ||
|
7654abcfc8 | ||
|
f6c56e2ed9 | ||
|
5b4f921256 | ||
|
131c64de3a | ||
|
a7d4216972 | ||
|
09078e1c05 | ||
|
0509686fd9 | ||
|
aa1f5bf8f3 | ||
|
6b66433ac9 | ||
|
077ae1d9cf | ||
|
4a2c72e8e5 | ||
|
94275e3725 | ||
|
580d4b7642 | ||
|
40388e7f2f | ||
|
4b60b54456 | ||
|
8fd4d98662 | ||
|
dc859bff32 | ||
|
95bfd3e50c | ||
|
70e3543704 | ||
|
b76b0e9230 | ||
|
1c73144f2b | ||
|
82afa1dcca | ||
|
d463b2f16d | ||
|
16ad7b1fb1 | ||
|
ba34dd441d | ||
|
f6df8d7b94 | ||
|
97b89c46be | ||
|
e601d13cac | ||
|
032a916dc4 | ||
|
caf3141f7d | ||
|
55788ff391 | ||
|
52e78e9981 | ||
|
c21735b213 | ||
|
9ab2f16a22 | ||
|
b7fc876263 | ||
|
baf7c5f72f | ||
|
a914323088 | ||
|
4a75511f06 | ||
|
d949cb7948 | ||
|
d4dda052c4 | ||
|
9230985759 | ||
|
ad7e2a626b | ||
|
ad2c909305 | ||
|
4a9e505c4e | ||
|
8178b31abf | ||
|
8d3af64dd8 | ||
|
a2d3b29986 | ||
|
01503d31cf | ||
|
cdeb790997 | ||
|
c7ffa28d30 | ||
|
c6f24b9b46 | ||
|
36f9c1728c | ||
|
77daab1965 | ||
|
4a61972cfa | ||
|
fb24c6434a | ||
|
4efcbb1f2a | ||
|
52107a0d36 | ||
|
7643e5f5a4 | ||
|
fdd2681c04 | ||
|
301fc0bfc8 | ||
|
cdb3fe915d | ||
|
4e421693c6 | ||
|
647e504f3d | ||
|
a36e6582b4 | ||
|
fce3844ed5 | ||
|
d8d1cff659 | ||
|
21a45163a7 | ||
|
1cb97f783b | ||
|
c01cc89b0e | ||
|
41039bcdd5 | ||
|
6b51eb84fa | ||
|
76be5a784a | ||
|
edd1903c6c | ||
|
371d137c51 | ||
|
9d6d7de1c7 | ||
|
1d7203cbec | ||
|
6a7fa40efa | ||
|
a94c5e1784 | ||
|
a0ae384752 | ||
|
ed37f9bb59 | ||
|
db44cf8725 | ||
|
0eb08b21b8 | ||
|
7570745883 | ||
|
99fc2d65db | ||
|
eb5a773e27 | ||
|
13adb90fd4 | ||
|
3819cd045c | ||
|
2471a14690 | ||
|
c4e141d3d7 | ||
|
c14a5854c7 | ||
|
66f3670aed | ||
|
a1a6e98376 | ||
|
8a050e114b | ||
|
bfeb04c497 | ||
|
6c4fc9515b | ||
|
0f64e4430d | ||
|
369e4e77c9 | ||
|
d96ef32ab8 | ||
|
fc7671e6b1 | ||
|
c9d2fdd7d5 | ||
|
097fa8cebd | ||
|
76e613b95b | ||
|
b59d8bbd4e | ||
|
2cb50effa2 | ||
|
ea73a342f8 | ||
|
9e2db2d494 | ||
|
e5dfc12750 | ||
|
c6724a261f | ||
|
2116e27790 | ||
|
389453c83a | ||
|
1d4d19dac4 | ||
|
6dee8648d3 | ||
|
5efdbb4634 | ||
|
1314c007f5 | ||
|
2e3924b886 | ||
|
a000bf5414 | ||
|
0c61af46b8 | ||
|
1e4c63bfcd | ||
|
9f5c665188 | ||
|
0ccca167fa | ||
|
2d9c5bac10 | ||
|
f33f6d0193 | ||
|
3564f406e7 | ||
|
c485c783bd | ||
|
e03c1e624a | ||
|
65a66d3a9b | ||
|
dcf38e8d5e | ||
|
7f3b2b3d91 | ||
|
fd01f19081 | ||
|
c37fca4a5e | ||
|
ddce36837d | ||
|
fe811f4633 | ||
|
bc044f4a49 | ||
|
7cbcf08fde | ||
|
4aed5b81fe | ||
|
16e765d150 | ||
|
89905c6043 | ||
|
6c8329a8d5 | ||
|
aadc23e297 | ||
|
302f4b6ca8 | ||
|
4e6169d2ec | ||
|
de4acb2794 | ||
|
e1b955f644 | ||
|
7f8fed741d | ||
|
ed99ef1e42 | ||
|
f592958aa4 | ||
|
b892dbf013 | ||
|
187c069a29 | ||
|
b0364f7af7 | ||
|
5f139828f7 | ||
|
72329accef | ||
|
65b5f4927a | ||
|
30b272c6d9 | ||
|
8369d5f5ff | ||
|
3bb64562fb | ||
|
e4f288b1cc | ||
|
c830387fde | ||
|
e1da9f44b4 | ||
|
f3712b517c | ||
|
3d6fec5f48 | ||
|
fc56e31505 | ||
|
9246b433a7 | ||
|
f849a8bc71 | ||
|
8abca27c60 | ||
|
35d6a48bb2 | ||
|
34e84ebbc2 | ||
|
ee4f3a78cd | ||
|
cc91944087 | ||
|
6c0770c075 | ||
|
7b4a6848db | ||
|
879b9659d9 | ||
|
9063ea6748 | ||
|
b3fa6683fe | ||
|
7f3d803ac9 | ||
|
4596e6e82c | ||
|
b77d6e9751 | ||
|
c3bf711c7d | ||
|
71cf9d9721 | ||
|
ebe90262cb | ||
|
19b51afce1 | ||
|
9b59c5299a | ||
|
a32c5a3feb | ||
|
bc7df1d72d | ||
|
8632219f32 | ||
|
9a35a6de62 | ||
|
8744b2864e | ||
|
54fb12f1ee | ||
|
21e94457bc | ||
|
a77fc8fce4 | ||
|
56521ed045 | ||
|
866a12161c | ||
|
18055f7565 | ||
|
cefc0e41f1 | ||
|
ebd437cebd | ||
|
f5651357a9 | ||
|
407b316a85 | ||
|
f29bbc4166 | ||
|
8410a02aaf | ||
|
130fea64ff | ||
|
ebd64d918c | ||
|
d37e8b92d0 | ||
|
17ee8314aa | ||
|
14b1fd0231 | ||
|
7ef6f37799 | ||
|
c78cef3870 | ||
|
122d9fae57 | ||
|
d085c80e29 | ||
|
69bc81f62e | ||
|
67416814b0 | ||
|
4da7f6b8ba | ||
|
1ff6e06e29 | ||
|
fac7069c40 | ||
|
f27c4b33ec | ||
|
85734218a6 | ||
|
6fb05aa0fd | ||
|
8702f4e723 | ||
|
338a41db8f | ||
|
6f40d41ce2 | ||
|
0e9392b6c5 | ||
|
55dd275e9d | ||
|
39c06ec652 | ||
|
ddf801f9f3 | ||
|
4562afd544 | ||
|
71282919d3 | ||
|
0cdc83818a | ||
|
6adf09254b | ||
|
154ee42f70 | ||
|
81fbccf139 | ||
|
5a6925c8c3 | ||
|
47114629a8 | ||
|
910e2533a5 | ||
|
651a08c800 | ||
|
7c712e623a | ||
|
47c13d3607 | ||
|
4b3e1cf570 | ||
|
c6c8751091 | ||
|
9b56d4c6eb | ||
|
302776aeec | ||
|
2015d45647 | ||
|
56162ba648 | ||
|
e3ac7db83b | ||
|
e560dcc4ac | ||
|
04796d730d | ||
|
3155838245 | ||
|
71e2d6abc2 | ||
|
c2534d660c | ||
|
4b7e84b605 | ||
|
e2b5c464a0 | ||
|
f45092ddd8 | ||
|
b28e2d9b9d | ||
|
6afb7ca094 | ||
|
6d6c41c266 | ||
|
819bda631f | ||
|
a4b9dcfef9 | ||
|
6496bc318b | ||
|
a8dd5fa8e7 | ||
|
3c21b09067 | ||
|
0d85aa1896 | ||
|
beecff5986 | ||
|
5659f97f5c | ||
|
5cda69770e | ||
|
0e265e28d1 | ||
|
6e1f00132d | ||
|
535bd8d872 | ||
|
4393f61019 | ||
|
bb2743c176 | ||
|
643d6eb845 | ||
|
f3409802bd | ||
|
32da563207 | ||
|
3220ef3f24 | ||
|
2d13bad190 | ||
|
28bfb08e2a | ||
|
0b9b170d84 | ||
|
82829ae65a | ||
|
ebd1638468 | ||
|
3db9d66b03 | ||
|
892adae8d9 | ||
|
02bb5a7363 | ||
|
3c65b5af48 | ||
|
1c395ca4ec | ||
|
48b4819a4c | ||
|
a173140073 | ||
|
b188c5ff45 | ||
|
03c89273c2 | ||
|
97f4326b24 | ||
|
b20beb4550 | ||
|
008e88dc53 | ||
|
ddd75791b2 | ||
|
b62bbf69ca | ||
|
a9f62c33f4 | ||
|
aa0ca7696f | ||
|
d6dfb6fb52 | ||
|
6c96d524c4 | ||
|
a5a112e2ed | ||
|
cd815163ce | ||
|
dde805be1c | ||
|
1282e2869a | ||
|
16204dd173 | ||
|
f6bfcd1aba | ||
|
e820aa9d02 | ||
|
fb2904193a | ||
|
a422fadc14 | ||
|
893ac34362 | ||
|
b456e71c41 | ||
|
ba763a1ec3 | ||
|
7dc7a819e4 | ||
|
5678140acf | ||
|
30b1f031ff | ||
|
633e5e446c | ||
|
5a2d43fcd7 | ||
|
3d421e9db0 | ||
|
f15605a261 | ||
|
17237a4021 | ||
|
d93a1f407d | ||
|
3aef95c9a1 | ||
|
0db988310e | ||
|
1f6c1dddc0 | ||
|
771897b741 | ||
|
90f1209294 | ||
|
b07cd8fa5a | ||
|
28fb452e35 | ||
|
8c6ef127df | ||
|
ca19cb196c | ||
|
1e07dae25c | ||
|
d8d0fd35e9 | ||
|
0445de735e | ||
|
bd8e32b23c | ||
|
38c3313cc7 | ||
|
6c159297dc | ||
|
cda7af4420 | ||
|
fcdf9ad590 | ||
|
e1989971b1 | ||
|
b61f623b7c | ||
|
26ff0e3e0f | ||
|
3b44dc96f3 | ||
|
87c08352b7 | ||
|
2f0b05473d | ||
|
8709e14b4c | ||
|
cc2132dd85 | ||
|
e56d5ae59e | ||
|
af66ea1b89 | ||
|
cdb939fa65 | ||
|
cb0707cd61 | ||
|
3431aaba5b | ||
|
1d7ac4464d | ||
|
732b9a9c2e | ||
|
858851691f | ||
|
41852f5236 | ||
|
82aa5fe48b | ||
|
ebec3cd675 | ||
|
6915b1aa8a | ||
|
2a1f78447c | ||
|
10d8cb84d1 | ||
|
b53ed4650d | ||
|
db64b468b5 | ||
|
966261b3d1 | ||
|
94931e62e8 | ||
|
22a29446f1 | ||
|
5b4a820c15 | ||
|
2a022ff3dc | ||
|
d021555521 | ||
|
6b625ba9b9 | ||
|
eae32b2da6 | ||
|
30011e91d2 | ||
|
58e9a8dcbd | ||
|
7bdae2765c | ||
|
6d246ddbed | ||
|
909acd05fd | ||
|
07ce717ee0 | ||
|
071027dcd5 | ||
|
553c47e54c | ||
|
945cfc51e1 | ||
|
eaa036936a | ||
|
56100cab0a | ||
|
367718fadd | ||
|
53b4d38456 | ||
|
e827acea83 | ||
|
71bb394cf8 | ||
|
62c3328296 | ||
|
0aa4017146 | ||
|
dd77451b52 | ||
|
70e96cf808 | ||
|
63263b270b | ||
|
5731b777f0 | ||
|
74d9d1db39 | ||
|
c9efbcc276 | ||
|
9f405a271e | ||
|
d8898608f2 | ||
|
bfbc1eec6f | ||
|
c0798e3fca | ||
|
f840c68e02 | ||
|
425eb4807e | ||
|
3f188d028a | ||
|
7140ef3d83 | ||
|
9f5fdab3ed | ||
|
0329e5076d | ||
|
8b9c4a9042 | ||
|
fd853c000b | ||
|
f63c978008 | ||
|
666f52adb5 | ||
|
0fa3e7e088 | ||
|
be24fb446a | ||
|
597d307c66 | ||
|
f459f97853 | ||
|
862e302308 | ||
|
96b215784b | ||
|
31103d294e | ||
|
b51a93c359 | ||
|
1a3b90f6b5 | ||
|
02ee0e8423 | ||
|
d78b964255 | ||
|
4164fbf148 | ||
|
62603eaab9 | ||
|
207245541c | ||
|
18911baedd | ||
|
2048155474 | ||
|
94d6a6c37f | ||
|
82fb5b6876 | ||
|
0134d7092b | ||
|
8ae4a63600 | ||
|
8a9f5f5535 | ||
|
6f899d531d | ||
|
36e1dca73e | ||
|
09382e8d72 | ||
|
56d35a0b28 | ||
|
46f5d8d279 | ||
|
9d883d3853 | ||
|
4cb85ef77f | ||
|
8992c454ea | ||
|
ddf3c55472 | ||
|
b135ba315b | ||
|
9af8161a97 | ||
|
7d4fdfffc1 | ||
|
5469c8cd48 | ||
|
9f6da8eaba | ||
|
435ae832d4 | ||
|
825a6bdd65 | ||
|
4e77c938ab | ||
|
13b7958f59 | ||
|
1083cf686d | ||
|
98daa8f961 | ||
|
879e24bd82 | ||
|
2e2f2f51f8 | ||
|
17ab6db5bf | ||
|
5c5cd2b3b8 | ||
|
9d09fc4f59 | ||
|
8ff02d442c | ||
|
eade9c8e8e | ||
|
e7ed4ed62f | ||
|
68140a6699 | ||
|
bd4141b4b1 | ||
|
b2e7a4ed7e | ||
|
7abe084bf3 | ||
|
06966e697c | ||
|
47ccf639b1 | ||
|
e63b7b54bb | ||
|
5f1880eb60 | ||
|
f9be613b66 | ||
|
3628d98bbe | ||
|
8db4afa502 | ||
|
7c5a97c410 | ||
|
f35a02ae7e | ||
|
02fcd70a79 | ||
|
2ffdb7f01a | ||
|
75837607f4 | ||
|
f6617b0844 | ||
|
010b771373 | ||
|
5c1a88b32b | ||
|
ae3ea30e0e | ||
|
2bffff1eaa | ||
|
8c9e4ce1c6 | ||
|
4dc596fdf2 | ||
|
84295eda82 | ||
|
20831f02bb | ||
|
979c21da24 | ||
|
d0738a9b1d | ||
|
7b85fce485 | ||
|
006494bb39 | ||
|
46ee31b723 | ||
|
c20e573349 | ||
|
f45e570097 | ||
|
14677e31e6 | ||
|
fd7d7bf8b6 | ||
|
8425bec797 | ||
|
c7d309f44a | ||
|
7f3044565a | ||
|
7d5fcf95ef | ||
|
8a3d6ca5ba | ||
|
d3937e3a7e | ||
|
3c11481271 | ||
|
649e0abd12 | ||
|
518f976358 | ||
|
051a9d8ef9 | ||
|
62e016bf9b | ||
|
bd3f2bd9fc | ||
|
72dbc1b0ab | ||
|
c57596a610 | ||
|
5a264ba524 | ||
|
1a6fe2b711 | ||
|
06ea06bfa4 | ||
|
774fb4dbe1 | ||
|
a707ace212 | ||
|
8ceb1bfbdb | ||
|
33134a97ef | ||
|
6d9ee2c62f | ||
|
1373dcaa46 | ||
|
216c5be2e3 | ||
|
3d39fa32f0 | ||
|
247e5b69c2 | ||
|
b94589b439 | ||
|
d9ba57e952 | ||
|
70e4bd0097 | ||
|
e5995fd51f | ||
|
b9d288fba5 | ||
|
b6d9dac6be | ||
|
6024ccc589 | ||
|
3708aa32b1 | ||
|
fe76a2a239 | ||
|
15752c418f | ||
|
20f986980b | ||
|
f511d1a271 | ||
|
5772a63366 | ||
|
061fbbba39 | ||
|
5a0c827ea2 | ||
|
242d034acc | ||
|
6c70004cef | ||
|
35c74c10c4 | ||
|
55ab256e41 | ||
|
bcafcfb368 | ||
|
d24b08a5f0 | ||
|
b8637958ce | ||
|
b74b4808c6 | ||
|
ad9c82990d | ||
|
8b9c8d7855 | ||
|
67e5cea649 | ||
|
4857fa6fc5 | ||
|
c96dad999a | ||
|
2557b6950b | ||
|
1036b79a24 | ||
|
fa191b0cf4 | ||
|
1dc310d657 | ||
|
d0056ea753 | ||
|
7c78007df2 | ||
|
7226f9e76c | ||
|
db97705f67 | ||
|
1c94e4d18a | ||
|
5f8ba985a3 | ||
|
5926429fd4 | ||
|
a104e42e0c | ||
|
09572b2551 | ||
|
3c5d67b9a7 | ||
|
628349a2d4 | ||
|
54e079a242 | ||
|
42ae9c3853 | ||
|
e328ac116e | ||
|
d1f2cac5c0 | ||
|
404bc0ff12 | ||
|
f56000a8be | ||
|
d427907213 | ||
|
18d019c299 | ||
|
7f035e856f | ||
|
3f21e7de15 | ||
|
5fba286afb | ||
|
af4347c4ff | ||
|
7163312ad9 | ||
|
5c50e1a99e | ||
|
39a2c45081 | ||
|
a469ca3e1a | ||
|
959235ad02 | ||
|
c7f7f9d695 | ||
|
b349b8c4d1 | ||
|
f719068325 | ||
|
93ad0d0a90 | ||
|
df21419b8b | ||
|
9d1018c03c | ||
|
448baeb129 | ||
|
727150ab86 | ||
|
09ad243a14 | ||
|
ea4a54d404 | ||
|
c9fbafda4b | ||
|
265384b368 | ||
|
3160d31f8a | ||
|
41a1be03c2 | ||
|
c8ce98b1df | ||
|
7ed29a9fad | ||
|
9f668e5bf8 | ||
|
e778e50073 | ||
|
b2e1ab2128 | ||
|
ffd251ab62 | ||
|
ff1c83c91d | ||
|
f769740d17 | ||
|
061c0a8a3c | ||
|
3569a5c304 | ||
|
36122e71e8 | ||
|
337d7301e9 | ||
|
504985ad6e | ||
|
2e674d4ba2 | ||
|
ceb452db25 | ||
|
64bda16f8c | ||
|
0efbefb6bd | ||
|
7d3e1f4e28 | ||
|
bf821651b3 | ||
|
2c633f51ad | ||
|
5415d3869a | ||
|
3a837c41a6 | ||
|
4d9396873e | ||
|
ffc6a51dbd | ||
|
4f42df9a30 | ||
|
8607b0d4ee | ||
|
310cab4acb | ||
|
cf593e67a9 | ||
|
170e51a595 | ||
|
903fb97eef | ||
|
5772fcd812 | ||
|
80f3926210 | ||
|
75cdf4d57f | ||
|
508bfde5cb | ||
|
456ecebef3 | ||
|
0dfc41c381 | ||
|
326c2279a0 | ||
|
dfa3129239 | ||
|
83fca0a829 | ||
|
06147dde49 | ||
|
6205befb25 | ||
|
5589fab2c6 | ||
|
c974d7fefc | ||
|
02b72551c9 | ||
|
fd03cb4e99 | ||
|
943de60f23 | ||
|
3a6645ab35 | ||
|
7dd1d4d80c | ||
|
5a5b464057 | ||
|
956cce6989 | ||
|
a1e110fea4 | ||
|
154da940bb | ||
|
d708ed142c | ||
|
0bff07cae4 | ||
|
3cd6714336 | ||
|
eb02b7eb1d | ||
|
3f5d83b4d4 | ||
|
a4f99021aa | ||
|
64401ec012 | ||
|
3d6bf809f8 | ||
|
cdb0daa07e | ||
|
78c765fcac | ||
|
ecc04a3c76 | ||
|
09112f6259 | ||
|
e3b8631803 | ||
|
d7647e2e8d | ||
|
ff9ac29454 | ||
|
f0196370a7 | ||
|
de78fa9c5e | ||
|
e95d53c65e | ||
|
2b94031668 | ||
|
d2d586cc11 | ||
|
64f2ea416b | ||
|
a00081fe82 | ||
|
2d215019fd | ||
|
434ec08c91 | ||
|
98a0b2b04a | ||
|
5acc23cc59 | ||
|
aada99fcf4 | ||
|
427a862db6 | ||
|
bbb4feb9e4 | ||
|
a507dfd137 | ||
|
0b4119f505 | ||
|
c760ab8f22 | ||
|
67fcd7218c | ||
|
0bf8415bfe | ||
|
0ab82c7d5f | ||
|
8e6e6b9912 | ||
|
7bb48ecbcb | ||
|
c2a88e671e | ||
|
c5896bab96 | ||
|
6ddbe2b983 | ||
|
d951d64fba | ||
|
3093724721 | ||
|
67e4ac6d5c | ||
|
896ef425e5 | ||
|
d64b7d9b42 | ||
|
f587ee53a4 | ||
|
afc309a296 | ||
|
bbd7de8d90 | ||
|
7a2d14e061 | ||
|
cd933b56dc | ||
|
cbf410f0cd | ||
|
ab5015d4b1 | ||
|
7db6186ff8 | ||
|
1d77b3155a | ||
|
076fc1d9c5 | ||
|
ac40c984b0 | ||
|
f3c76f1002 | ||
|
2055795c99 | ||
|
77e1c653be | ||
|
48da7dced3 | ||
|
4af4771cfe | ||
|
94925722ac | ||
|
f4f4db4d47 | ||
|
70474a1462 | ||
|
bbb20e4b96 | ||
|
8445c758d6 | ||
|
5b1c7c69cd | ||
|
aa31a321cf | ||
|
cdedde1893 | ||
|
d74fe1eb7d | ||
|
017b14c383 | ||
|
5d45754342 | ||
|
158865a500 | ||
|
22451b3cf1 | ||
|
91baedfc70 | ||
|
dff682c1f1 | ||
|
cf217b5733 | ||
|
acab873450 | ||
|
8d0b502c6d | ||
|
0c438375f7 | ||
|
bcd3126885 | ||
|
c0edb8b311 | ||
|
853289a81d | ||
|
27485bea2d | ||
|
775271aed4 | ||
|
89d1b7e835 | ||
|
e5cb0dc241 | ||
|
b4529e071f | ||
|
bc7768f5ab | ||
|
be917106a6 | ||
|
e67c094320 | ||
|
52a1bd95f2 | ||
|
d686bfea1a | ||
|
50d56183a1 | ||
|
1e1de7dc27 | ||
|
b607aca391 | ||
|
05b3943454 | ||
|
497d9c5117 | ||
|
dbbb4089dd | ||
|
48997460c6 | ||
|
6b450090bf | ||
|
64a7e9f0c3 | ||
|
db067a2845 | ||
|
73fdbe8c1a | ||
|
8e8bba2346 | ||
|
cc1fc54b49 | ||
|
fb765b54bc | ||
|
2f4bea4506 | ||
|
89bb24f638 | ||
|
369391daed | ||
|
589e8810c7 | ||
|
7d2770eec3 | ||
|
c49a9cee0c | ||
|
58fc29d0b7 | ||
|
4d2823745d | ||
|
ba5e899308 | ||
|
19de816d42 | ||
|
a95fb601a2 | ||
|
4b0a0a47bd | ||
|
d56b748df3 | ||
|
95e3708199 | ||
|
dc06341886 | ||
|
8a3b1651e4 | ||
|
191389eac5 | ||
|
000bbc81e4 | ||
|
334a5253ce | ||
|
4a865eaf8b | ||
|
4b43186d0c | ||
|
dd4f91c6f1 | ||
|
fb05ba30d9 | ||
|
bdfe6391d9 | ||
|
5430d65b34 | ||
|
0f44241410 | ||
|
8606823b19 | ||
|
c8d8445ba6 | ||
|
8f7c894112 | ||
|
b7028c42b7 | ||
|
c988f2f0fd | ||
|
d31ba0621d | ||
|
cd594967bf | ||
|
5a310ec329 | ||
|
8e1dc3a600 | ||
|
444677fe21 | ||
|
e72cffe6ea | ||
|
f7620ca640 | ||
|
357275001e | ||
|
886ade241f | ||
|
0521125bcd | ||
|
a10459c08a | ||
|
a072e2a629 | ||
|
8b79d84614 | ||
|
9e00965103 | ||
|
1648fc6ef3 | ||
|
6cc3f953ce | ||
|
7e0dc0c309 | ||
|
1346c49c24 | ||
|
9d8eb633af | ||
|
f660c6147a | ||
|
1c50903f5c | ||
|
48950922fb | ||
|
ff2574fffc | ||
|
9fc25b71ce | ||
|
80739f412a | ||
|
0dd5663e9e | ||
|
a26f08127c | ||
|
b68947c672 | ||
|
0544db1174 | ||
|
5a41a07b76 | ||
|
2e16994276 | ||
|
8cf092ab0b | ||
|
d82ebd4225 | ||
|
1b4abccd2b | ||
|
edb6574c35 | ||
|
7b1f9cc1f3 | ||
|
10ee02929b | ||
|
874e8df94c | ||
|
7f14f03065 | ||
|
36c94a9813 | ||
|
05cd50fb94 | ||
|
30bc178b86 | ||
|
adab132bf3 | ||
|
e4a309af66 | ||
|
9806e2119a | ||
|
71d13ae5db | ||
|
5a204b3653 | ||
|
c919033a2c | ||
|
4d31c4ccc3 | ||
|
268e077ab5 | ||
|
87eb3c08c3 | ||
|
ffd0a992c5 | ||
|
28c0872676 | ||
|
222e39d0ea | ||
|
4211ed1a9b | ||
|
305b075365 | ||
|
aaabd8a20a | ||
|
5e08b889c1 | ||
|
669c402fd6 | ||
|
ac902d0bf0 | ||
|
01be471576 | ||
|
58feab5c23 | ||
|
5d8ad186d5 | ||
|
421dc817a2 | ||
|
144fcf29bd | ||
|
2975a33e95 | ||
|
e27bf9d2cb | ||
|
2a3594a05a | ||
|
0cd1b5d3bd | ||
|
6dc152b911 | ||
|
4b857f6088 | ||
|
766f62a1b7 | ||
|
dc0c4d2046 | ||
|
558f61e905 | ||
|
581b2fb068 | ||
|
9fa90786f3 | ||
|
8cf36c1afa | ||
|
5e7d69f247 | ||
|
05246729de | ||
|
6dfea43021 | ||
|
c9ac6498ae | ||
|
a830bc73af | ||
|
faf4c634be | ||
|
c81015905e | ||
|
8a181607be | ||
|
2d7567584a | ||
|
b0800504e4 | ||
|
9af80592b3 | ||
|
bebe82f9e4 | ||
|
ed9d4750bf | ||
|
d0802dcc7f | ||
|
337632f896 | ||
|
79163ace1a | ||
|
c37136eb4a | ||
|
0edc5e6507 | ||
|
1b54b71e80 | ||
|
0e8f149651 | ||
|
a78b18df9c | ||
|
33fa57e7a7 | ||
|
149364395d | ||
|
bbf8ef0ab8 | ||
|
359c0714ed | ||
|
a51594bd41 | ||
|
8a7c948dd3 | ||
|
3aca794440 | ||
|
34774178fe | ||
|
aca9c47d86 | ||
|
4c89edcba1 | ||
|
3884b128b6 | ||
|
ab07b5b381 | ||
|
ef7134e4b7 | ||
|
7b9b653bd4 | ||
|
b47e8fe787 | ||
|
7da73a9912 | ||
|
b3d7a3cddc | ||
|
512f9dd8a7 | ||
|
760889e49e | ||
|
593ce8876b | ||
|
37bd7ecbfe | ||
|
155ab52e30 | ||
|
a2be514d19 | ||
|
d5ba154b6c | ||
|
2315d02232 | ||
|
350cba2042 | ||
|
92e074d100 | ||
|
996506c44a | ||
|
1df50491fc | ||
|
ba9c928588 | ||
|
93277b8de7 | ||
|
91a4282581 | ||
|
e02e99ed02 | ||
|
652a79f14e | ||
|
a6319167c9 | ||
|
1f84026682 | ||
|
e5743ee98b | ||
|
42a00dee95 | ||
|
fd639d6348 | ||
|
12cf446375 | ||
|
238c54c6fb | ||
|
c6f6317839 | ||
|
bc93ff49b4 | ||
|
74166883fc | ||
|
6924e5de01 | ||
|
b6509de53c | ||
|
9573b0c8ab | ||
|
13d9b6ab71 | ||
|
77b464f1bd | ||
|
dcc04c6ea4 | ||
|
2dcbe0801b | ||
|
d6ce7005f8 | ||
|
6ae4c3cae4 | ||
|
3df5a77ee8 | ||
|
1e85bcf388 | ||
|
0723c34ec4 | ||
|
eaa8cc3052 | ||
|
ca9c3124f1 | ||
|
a78100c0ea | ||
|
21a2014385 | ||
|
cbc29b553c | ||
|
de14e72689 | ||
|
aefd52c12e | ||
|
dab5ddb838 | ||
|
63bd71b7e6 | ||
|
e1364ed8a4 | ||
|
2f047e3b63 | ||
|
fa976b1cba | ||
|
1fdd537253 | ||
|
d6bc78619b | ||
|
c52d02ea10 | ||
|
c45fd8d5fd | ||
|
216d25e3f0 | ||
|
49effb8b9e | ||
|
ea750a682c | ||
|
2dc332befd | ||
|
50c04b6d97 | ||
|
4e10acf5c2 | ||
|
be460124ca | ||
|
d09f4b7ef4 | ||
|
57845a4249 | ||
|
d080a5e944 | ||
|
6f7e97ac09 | ||
|
976d9d53cc | ||
|
94a84b0b24 | ||
|
16f76bc12d | ||
|
4fbcb5d77f | ||
|
46417e569a | ||
|
be5e5884b4 | ||
|
3c07edb5e5 | ||
|
e0b3960374 | ||
|
724b333a96 | ||
|
a5bd0da8d9 | ||
|
50c7d4aeb7 | ||
|
36c653716a | ||
|
db6e6a2533 | ||
|
c8afb60254 | ||
|
5db550ccd2 | ||
|
e187b13912 | ||
|
ad81ceb592 | ||
|
7669ddc83e | ||
|
b2970a6185 | ||
|
574b990984 | ||
|
8e64f117f9 | ||
|
4eb2f68da5 | ||
|
e9b7b5a203 | ||
|
9a32aa2805 | ||
|
19c1bafeb8 | ||
|
da4f9f7fb0 | ||
|
3a80195062 | ||
|
179dd6a5ba | ||
|
04b98d11af | ||
|
b45f58f7b7 | ||
|
dc4af19514 | ||
|
992d021acf | ||
|
914a8528d3 | ||
|
3c52dc845a | ||
|
10632caf4c | ||
|
602adbe619 | ||
|
8b96a87816 | ||
|
1ecfe22178 | ||
|
c2a12bc17d | ||
|
ce84a76713 | ||
|
ee9c4c1a41 | ||
|
fdeb59e4cd | ||
|
11959ea69f | ||
|
88e12e75b8 | ||
|
3bc827144a | ||
|
cc862eff6c | ||
|
f95efa1120 | ||
|
42d09f7ded | ||
|
cd5435b843 | ||
|
4d8198d15b | ||
|
19f9acb803 | ||
|
1d8990b8d8 | ||
|
0cf0715274 | ||
|
93d461dfa8 | ||
|
4494a3d8b0 | ||
|
ad4d9cdde0 | ||
|
e823dd3506 | ||
|
8cba23be21 | ||
|
92f16bd696 | ||
|
c7d158f860 | ||
|
9ca32292cc | ||
|
6bff74d0f9 | ||
|
88ae4bcbcc | ||
|
6d0200f6f5 | ||
|
7f5c797bc7 | ||
|
b0de3006a4 | ||
|
348dc08f73 | ||
|
484d003a3b | ||
|
d2cfc13e12 | ||
|
e5b04a0e7d | ||
|
a43946210f | ||
|
8099eb0012 | ||
|
9fa4d0bcc6 | ||
|
e405251f2c | ||
|
97c56f9e57 | ||
|
9dc691b47d | ||
|
b2dbfd3083 | ||
|
e216591419 | ||
|
5d39ac18dc | ||
|
38a8c6a87a | ||
|
3fe882c79c | ||
|
30282e79fe | ||
|
bc146d8b11 | ||
|
9d93d700be | ||
|
5ee9c04924 | ||
|
71c3128bb4 | ||
|
8ae853cd90 | ||
|
f9d5cc33cc | ||
|
58d71cad8a | ||
|
5fa3b8febc | ||
|
824e77f59b | ||
|
e5acb1e132 | ||
|
13573882a1 | ||
|
335d9000bf | ||
|
fe4283e3d7 | ||
|
aa583614d2 | ||
|
74f832ad1a | ||
|
78fcd6c43b | ||
|
271cff7283 | ||
|
8422e5abbd | ||
|
92635af1e0 | ||
|
238987d533 | ||
|
cde2e0b229 | ||
|
c70ceb1fbc | ||
|
34c2f6e3d3 | ||
|
2cb8ed4ade | ||
|
db01ce6ee8 | ||
|
aca697f6ff | ||
|
ef903bdf42 | ||
|
3851923a98 | ||
|
3bfacaea77 | ||
|
72aedb7e77 | ||
|
db3a7d4097 | ||
|
75410bf77b | ||
|
76fc04cc99 | ||
|
ef1d24d7f9 | ||
|
63d2ab7b6a | ||
|
efeacfa622 | ||
|
cb8ef7f32c | ||
|
e247e56aee | ||
|
f3ebd34531 | ||
|
94ccecc012 | ||
|
65d52776f8 | ||
|
d8aaa5a21b | ||
|
67edb9d216 | ||
|
68787f4f08 | ||
|
1c9630635c | ||
|
79238ede7a | ||
|
9628255b8f | ||
|
e35311ff85 | ||
|
8331b22632 | ||
|
2517220f1b | ||
|
734cdea427 | ||
|
7b2f321693 | ||
|
2673a1f0d0 | ||
|
9b376a2f4f | ||
|
c2647b9160 | ||
|
6a391219a8 | ||
|
0d970ed40e | ||
|
8eb6c49303 | ||
|
520b6b74ca | ||
|
6ff3eb2e2a | ||
|
c5c4144973 | ||
|
7c0701efad | ||
|
5005e91b1c | ||
|
f8be0502ca | ||
|
6e3c1aa742 | ||
|
5a73565182 | ||
|
d703a05182 | ||
|
897b160834 | ||
|
8ce6cf58fa | ||
|
480d66cc24 | ||
|
6074cdfda2 | ||
|
7d49986f4b | ||
|
5266b86c75 | ||
|
df5810a923 | ||
|
a9fde92dd9 | ||
|
ba69813ff3 | ||
|
72a11495fa | ||
|
f9188b05ca | ||
|
4abf9645e0 | ||
|
b2a1cb1e2e | ||
|
531623b9b2 | ||
|
bc29d2ad12 | ||
|
92b4a2fb57 | ||
|
407cbb1020 | ||
|
9cf787bc04 | ||
|
03b9e36014 | ||
|
9f5b9d32ad | ||
|
5e16b79d23 | ||
|
61074305ec | ||
|
4d6f75031a | ||
|
73f246b60e | ||
|
2ea878b3ac | ||
|
619f3871f2 | ||
|
4054d89f46 | ||
|
fb603bc0c5 | ||
|
f23b102e41 | ||
|
289557898e | ||
|
2997095bb3 | ||
|
ed2ca6c6fb | ||
|
e7c10c8b6a | ||
|
541a6fffe5 | ||
|
a83879dffb | ||
|
6e081f30c0 | ||
|
c80293f33a | ||
|
42b9ea4107 | ||
|
7b984108ab | ||
|
381e112dac | ||
|
6a5cc51db4 | ||
|
900ea92562 | ||
|
7e755611b0 | ||
|
d82e547f6d | ||
|
bd05de53d8 | ||
|
8351c3e0fd | ||
|
518485738e | ||
|
1061fe8bfc | ||
|
93cc50a0c3 | ||
|
b281b8395a | ||
|
6aedfbc2cb | ||
|
4620c22479 | ||
|
ee7545f64e | ||
|
7f683eb0ee | ||
|
e11494f443 | ||
|
5f2d3cfef7 | ||
|
9f6ecc267b | ||
|
9aa897fe7d | ||
|
cccbc2804e | ||
|
3722347305 | ||
|
9f17bf6378 | ||
|
15a86f0f5b | ||
|
6cc0985ab6 | ||
|
d8c21ba7bb | ||
|
162e8ba2dc | ||
|
2c380b9844 | ||
|
f9c918234d | ||
|
a25c80a9bc | ||
|
9d9c93ffe6 | ||
|
082a1828e7 | ||
|
f4f91f812b | ||
|
1ff474bbae | ||
|
6b0dfd7768 | ||
|
38d3901d1f | ||
|
2c68038eec | ||
|
d97326c428 | ||
|
5fa028c2ce | ||
|
b1ebda7efc | ||
|
943c3bf215 | ||
|
aafab389ac | ||
|
7765a0400a | ||
|
59248dbbd0 | ||
|
eae6b3d085 | ||
|
2f0d375b3a | ||
|
e4bc1fdfcc | ||
|
6bf6fe04e3 | ||
|
e5ff876ed3 | ||
|
0dac6913c4 | ||
|
22d216ae3e | ||
|
776aab36c3 | ||
|
d2f826c8ae | ||
|
9727230087 | ||
|
2472608407 | ||
|
1962aa3b65 | ||
|
30ca756157 | ||
|
a7ec1a9dbe | ||
|
cf0090094e | ||
|
3f65a5dd99 | ||
|
c902f1ed84 | ||
|
1df05d1c38 | ||
|
eac5be42f5 | ||
|
74b13715d4 | ||
|
283964b064 | ||
|
b0e26b7c90 | ||
|
60b6f7dec3 | ||
|
9ef7aae7a7 | ||
|
2bb7321123 | ||
|
2975c3d1c8 | ||
|
4dd6893fcb | ||
|
109240437e | ||
|
f23e447077 | ||
|
d5690081cc | ||
|
dac2fd741c | ||
|
ae390496ab | ||
|
e6168008d2 | ||
|
5659ebb3a3 | ||
|
eaf004254e | ||
|
49f66205d2 | ||
|
b977cde1af | ||
|
b78452b589 | ||
|
e1b9d518a7 | ||
|
6e4c7552ab | ||
|
95d978601b | ||
|
8604856a64 | ||
|
bce6dbfa27 | ||
|
fc42767031 | ||
|
6ee63b58ea | ||
|
f2c294dc4c | ||
|
6c515aede6 | ||
|
5359c22e52 | ||
|
1e546d5f72 | ||
|
3895c07a3c | ||
|
9c72d3bd43 | ||
|
d3d885c979 | ||
|
f26503aa3e | ||
|
815e0a235e | ||
|
a41a29f44b | ||
|
7df4cd69c5 | ||
|
7f0dffb242 | ||
|
190baa6fdf | ||
|
204873e70b | ||
|
b9b7a31991 | ||
|
275caa14a5 | ||
|
8cb5603ebf | ||
|
42feb0447a | ||
|
004a55d79b | ||
|
21fd6414bf | ||
|
d43f94d591 | ||
|
40e741779d | ||
|
cb070329dc | ||
|
9fdb637e0e | ||
|
6a4a73dac3 | ||
|
ec0bf9aaa3 | ||
|
9afe4769ea | ||
|
b57109b24d | ||
|
8be6e043ed | ||
|
da584aafc3 | ||
|
32a8d762de | ||
|
caabf6ddcc | ||
|
dad16ac551 | ||
|
7e791a37fb | ||
|
7062571d0a | ||
|
8460d3c2fb | ||
|
a8d87fb91e | ||
|
0d28b2ce40 | ||
|
a97911b214 | ||
|
2f4365f848 | ||
|
85e2021b57 | ||
|
76db19a13f | ||
|
6c333261b3 | ||
|
98cdb0006e | ||
|
6b4b76e9fc | ||
|
12e83fce75 | ||
|
969660dd45 | ||
|
7ec16a7051 | ||
|
4589045899 | ||
|
9f5aa8bfbf | ||
|
3143ed6573 | ||
|
e29f8aecc5 | ||
|
bfb2cdbede | ||
|
4908e3335b | ||
|
a05601cffd | ||
|
85ea5c973c | ||
|
61913e6bf8 | ||
|
b3ccef2284 | ||
|
8722c267e1 | ||
|
b952c5521b | ||
|
d88e4bda05 | ||
|
d047e897a1 | ||
|
e969a480ba | ||
|
0627daf8a8 | ||
|
f2bd336941 | ||
|
cb4fb30f98 | ||
|
fccfe51625 | ||
|
e87cc482a9 | ||
|
ae363d5a09 | ||
|
e10bee13ff | ||
|
547f5ace73 | ||
|
cda12f0e54 | ||
|
d16a507064 | ||
|
1d4c4c070c | ||
|
7dd07f58dc | ||
|
5209c6ddb7 | ||
|
7fceb85821 | ||
|
3da7c2d5c3 | ||
|
8d0c970722 | ||
|
2fd8beb476 | ||
|
3efdb9f09e | ||
|
1ec211fde3 | ||
|
e2986a225b | ||
|
56143a4732 | ||
|
e7142466d6 | ||
|
2bc608048f | ||
|
1a218e4195 | ||
|
5468cfa1df | ||
|
a3b58d96a4 | ||
|
80419dafd3 | ||
|
cbf9d48732 | ||
|
88d3c5ac67 | ||
|
cab950c771 | ||
|
49e012cf93 | ||
|
c87b5a736d | ||
|
4509b001d8 | ||
|
2fe29d85d9 | ||
|
31f365ee26 | ||
|
0e4585b9ea | ||
|
e8a107eb06 | ||
|
55049bb342 | ||
|
d2298ff62c | ||
|
2c49d37b4a | ||
|
60cf3a2507 | ||
|
a2e6982f8f | ||
|
ca64a2721b | ||
|
27c83b2329 | ||
|
e86df4cd8d | ||
|
9c8efeb775 | ||
|
87e636e7a3 | ||
|
6b4c014716 | ||
|
12eb9bee84 | ||
|
278da9ac1f | ||
|
1a13a78372 | ||
|
81e6a24628 | ||
|
0a292d3f87 | ||
|
ba93841f8b | ||
|
d0145f601e | ||
|
033ff59bb0 | ||
|
a0153b0cad | ||
|
0b66093c30 | ||
|
a308279a08 | ||
|
6ead986222 | ||
|
443ee00bf9 | ||
|
66e34252d8 | ||
|
12b5b6cb8a | ||
|
561369ed5c | ||
|
f6faa11c15 | ||
|
c52783e420 | ||
|
ebe5876844 | ||
|
83f336371c | ||
|
86cd6b9767 | ||
|
e49d2d6726 | ||
|
cd1c626d3d | ||
|
8f712552c5 | ||
|
1e11c27d2a | ||
|
47c2f521de | ||
|
004f2e5d49 | ||
|
9371c1bc3b | ||
|
bda53f2ca8 | ||
|
9c81d35730 | ||
|
8414a83a12 | ||
|
2164ad71ec | ||
|
0b55ea9f20 | ||
|
a7bd84a8ef | ||
|
faef7c0251 | ||
|
a7e519a004 | ||
|
cadcb5cfc9 | ||
|
00e550f3de | ||
|
cb1ac61b36 | ||
|
9ec7ec4859 | ||
|
52a7b8dfba | ||
|
c98a6d0ddf | ||
|
f3bf331367 | ||
|
5c0c849850 | ||
|
4d56ae82b4 | ||
|
27d94ac4bf | ||
|
f9d9cc5de8 | ||
|
2a487239f4 | ||
|
0b6ecb0d67 | ||
|
a5d3e802cf | ||
|
25ff5d9243 | ||
|
6ae944d768 | ||
|
33e59dd2a5 | ||
|
2f74cb79a3 | ||
|
123703e6e0 | ||
|
a48cdecde1 | ||
|
98d5afdcdb | ||
|
378a1176fc | ||
|
96e365bbda | ||
|
e9465ad7a8 | ||
|
19b8e0bc6e | ||
|
7ecf64610f | ||
|
9f8659746b | ||
|
43c177409e | ||
|
872ee75882 | ||
|
deddbd384a | ||
|
ddaceb14e3 | ||
|
9fa8b25f72 | ||
|
12871d7273 | ||
|
0ed18f879d | ||
|
30fdada7f3 | ||
|
465d6371cf | ||
|
f25f109537 | ||
|
f48527db46 | ||
|
74eb69ce57 | ||
|
5a29acc359 | ||
|
1b8ef26eb4 | ||
|
9a39349110 | ||
|
7639d860e1 | ||
|
968f97c987 | ||
|
8f4e77c427 | ||
|
1415af59c9 | ||
|
1d97f64661 | ||
|
ccc0fe30fc | ||
|
3a637db414 | ||
|
d38e60ad8a | ||
|
1f0d6112df | ||
|
76b43141bd | ||
|
372146fd11 | ||
|
c8295c7c18 | ||
|
37848cce0f | ||
|
0a03a8116c | ||
|
131586ed3e | ||
|
4867686e21 | ||
|
d7f9cc97c9 | ||
|
329bcef903 | ||
|
73f25dff67 | ||
|
9c1f33eee7 | ||
|
1bacb9231b | ||
|
36521ac6a9 | ||
|
4d23daba74 | ||
|
29c8a00e16 | ||
|
23538fe39a | ||
|
e9c5640daa | ||
|
4029ccc5b6 | ||
|
3dfa0fad29 | ||
|
086c76734c | ||
|
1be4ba27d2 | ||
|
8090a9ca8c | ||
|
bc593953a3 | ||
|
4bf5a3aa5f | ||
|
62ab41c01e | ||
|
394a3f4149 | ||
|
b4a8ed968a | ||
|
cda34d957f | ||
|
2b0568c6ab | ||
|
62aec70bb6 | ||
|
c52b276012 | ||
|
9cb80070d9 | ||
|
6fb38d5074 | ||
|
ed3b444044 | ||
|
6a1c2fac3d | ||
|
72125090d1 | ||
|
1dcc9db438 | ||
|
b7efd92203 | ||
|
35795d3135 | ||
|
f05f71a831 | ||
|
12d4a74427 | ||
|
fead7fc479 | ||
|
df6867f6c3 | ||
|
d38f1bb771 | ||
|
ac0ad8ec64 | ||
|
c8ca135454 | ||
|
86f2f788e9 | ||
|
3385b96615 | ||
|
4d26c414a5 | ||
|
9a6aa2f1fe | ||
|
562e04f27c | ||
|
aeba93dea8 | ||
|
1b08b775ac | ||
|
6845f75ea0 | ||
|
c5a4b4b4c6 | ||
|
cc855d22fc | ||
|
03df684c7e | ||
|
5dd7ec15e8 | ||
|
66618824e2 | ||
|
85d8d5cba9 | ||
|
b944ef5de6 | ||
|
0679774d41 | ||
|
9fce62b9c5 | ||
|
7ac1e1370f | ||
|
47743cc464 | ||
|
afae2bcb41 | ||
|
a9c6dbe391 | ||
|
54dc315845 | ||
|
bfe03a2b06 | ||
|
a0a9bb8631 | ||
|
e2b4ffab30 | ||
|
21904e2a26 | ||
|
bf2f4ac757 | ||
|
c059176982 | ||
|
d47091e765 | ||
|
15ede0b71c | ||
|
b49a8eb7e0 | ||
|
2dea5c31ca | ||
|
27b9ccc81e | ||
|
7bc48ea6e8 | ||
|
22369667a9 | ||
|
de5e9444b7 | ||
|
8b84a27721 | ||
|
725e1d11e3 | ||
|
c084e44681 | ||
|
8c221b908e | ||
|
c5fabd625e | ||
|
978fcea9d0 | ||
|
efc4930bc8 | ||
|
be8ca61c3c | ||
|
018acb456f | ||
|
9b677573a3 | ||
|
24159557df | ||
|
3fc86582f3 | ||
|
349d4ad534 | ||
|
f9206dcfdb | ||
|
2f4632fd67 | ||
|
a39a9bd592 | ||
|
282613c88c | ||
|
02ff1b92cb | ||
|
85bd49f391 | ||
|
1a5d74d9cf | ||
|
4cfa8cc561 | ||
|
2d9d12f0bf | ||
|
f3affb76d7 | ||
|
3e63d3a6b7 | ||
|
da9f005f9e | ||
|
c8a5089fd0 | ||
|
0fe0c2728f | ||
|
2059a55281 | ||
|
e5a27ca7f1 | ||
|
3265af2086 | ||
|
3c0fc790d1 | ||
|
4b57e72dba | ||
|
5ee33123db | ||
|
928f2e4686 | ||
|
ceffbd976f | ||
|
58decfb0b5 | ||
|
a2fef04697 | ||
|
f2b47c5c70 | ||
|
525e94c011 | ||
|
962afbfc0a | ||
|
729334d43d | ||
|
4fdb336444 | ||
|
52dc1d9f22 | ||
|
9dc2da66e1 | ||
|
85436029ff | ||
|
0ce71b19a8 | ||
|
3e258714bb | ||
|
ff457cfe3f | ||
|
59df82b8bd | ||
|
cef955abe6 | ||
|
1a4905cd76 | ||
|
e86266e12a | ||
|
fa819284d0 | ||
|
cf9ab9674a | ||
|
acd55f249a | ||
|
ef50e3186a | ||
|
b2a5f5a2d1 | ||
|
4a9a4d68a6 | ||
|
677cbbb712 | ||
|
535b54ce31 | ||
|
87be373a03 | ||
|
247189f769 | ||
|
b7a117c90d | ||
|
a0f4394099 | ||
|
2432080d3c | ||
|
fdcf7271a6 | ||
|
912960fc31 | ||
|
04ed7434d2 | ||
|
24244e17e0 | ||
|
33f4f31066 | ||
|
4d21bff69d | ||
|
2e9cc963f9 | ||
|
420a7bcac6 | ||
|
116249c38f | ||
|
a4bc5f6ca7 | ||
|
96cd8de896 | ||
|
e5f6e83dd4 | ||
|
54f95dbd92 | ||
|
4b767bca54 | ||
|
defcd86152 | ||
|
2af13a4e6c | ||
|
ccf123a481 | ||
|
6fd3f36e31 | ||
|
e2d4ef11bd | ||
|
f02888113e | ||
|
d6e945542e | ||
|
1631449c13 | ||
|
fc1de88cde | ||
|
c23c56649e | ||
|
31a0ddd731 | ||
|
3af3a89ba5 | ||
|
546d974084 | ||
|
f56b158e78 | ||
|
fdfedf274f | ||
|
3ce85797ea | ||
|
b78cbb60b4 | ||
|
209e3d58eb | ||
|
cabc7a89f8 | ||
|
233adb833b | ||
|
d851f2d048 | ||
|
f2e290d274 | ||
|
608330f0da | ||
|
46c65a7ebc | ||
|
a8933cf079 | ||
|
76389c45f9 | ||
|
a4bbc9ce5a | ||
|
9850341d4c | ||
|
f94316b8b8 | ||
|
697da8a8f4 | ||
|
b9c12e20dd | ||
|
0e8010ad78 | ||
|
870cf4ce2e | ||
|
46dc5eb58e | ||
|
40da78af8b | ||
|
caefbbbbad | ||
|
3f368e186b | ||
|
1a453f5e49 | ||
|
830dd045c1 | ||
|
9051e3782a | ||
|
2d0419d48c | ||
|
7c0b3ee82c | ||
|
c495c66a38 | ||
|
e9c1559c57 | ||
|
03c0b2f68b | ||
|
22a40a90b3 | ||
|
6df00b3e3a | ||
|
022bf3624c | ||
|
40ceb57c45 | ||
|
4493273c69 | ||
|
5d1a75bc13 | ||
|
991c4ee36e | ||
|
6747af076f | ||
|
2ed5e236da | ||
|
76e55ba079 | ||
|
c3ddb066b7 | ||
|
3fd8481522 | ||
|
67b59f7c00 | ||
|
765172d69f | ||
|
9dac81ca6f | ||
|
9e46d74507 | ||
|
03723b62eb | ||
|
90bca59192 | ||
|
a4b1b0a085 | ||
|
915ee780d4 | ||
|
24f91a48db | ||
|
1982ccd710 | ||
|
bbd1853e78 | ||
|
0069253043 | ||
|
10f56e9e6d | ||
|
fbbc1a1ef9 | ||
|
cc494edbbf | ||
|
0ef83c7136 | ||
|
5ebeb9c587 | ||
|
6d0898a49a | ||
|
416ee91c39 | ||
|
8cebb0c603 | ||
|
26edcdf4da | ||
|
8eb7859f30 | ||
|
332c2dc69b | ||
|
5211890607 | ||
|
e8549513bd | ||
|
47d533f9ed | ||
|
45b93ed3aa | ||
|
51d2c17ad7 | ||
|
6e8ff6ab71 | ||
|
41e8aa8a03 | ||
|
25fbbce11e | ||
|
45f5c18725 | ||
|
cf3db7a76b | ||
|
f7d14e13c7 | ||
|
c050ce3fbd | ||
|
061b9c90ab | ||
|
54b037c0c1 | ||
|
9da8cb99ce | ||
|
ffa0749849 | ||
|
6f07548023 | ||
|
6881e9a4c1 | ||
|
34b5d01e8d | ||
|
2854b33d56 | ||
|
c379342f6a | ||
|
3265580275 | ||
|
bbfec6f489 | ||
|
cee4565c34 | ||
|
543893adba | ||
|
2817dc1db5 | ||
|
859d10d907 | ||
|
2769db3f17 | ||
|
b22b3a627f | ||
|
f013cf296d | ||
|
b54c561070 | ||
|
a7e55cc5bf | ||
|
069d45e8df | ||
|
d5d77afaaf | ||
|
3489e3ba28 | ||
|
c81d0c17fb | ||
|
1941086b4a | ||
|
f3988ae1d1 | ||
|
6aa9b08ff2 | ||
|
bcb8f1b42b | ||
|
a93dcb0285 | ||
|
b5e07a31d8 | ||
|
6ace96708b | ||
|
662a5eea35 | ||
|
3c3cbf08a9 | ||
|
51b0bafb80 | ||
|
c6c89e9b9e | ||
|
038e872e7a | ||
|
1c08487939 | ||
|
e159b1a32a | ||
|
ff20b3a052 | ||
|
3a68c7de12 | ||
|
23f4fee872 | ||
|
54173a395a | ||
|
2fbac50837 | ||
|
9552e617b5 | ||
|
ec47bc3d2e | ||
|
4a87a900be | ||
|
e7691aa856 | ||
|
3ebf777d7b | ||
|
2184013191 | ||
|
45972f33cb | ||
|
e887b42ef5 | ||
|
31534769a8 | ||
|
55205bbd74 | ||
|
9655d58432 | ||
|
31c0d8bc98 | ||
|
8d5d7054c6 | ||
|
14fdfbd884 | ||
|
213f106cac | ||
|
287638af90 | ||
|
051ca50929 |
4
.gitattributes
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
* text=auto eol=lf
|
||||
|
||||
*.bat text eol=crlf
|
||||
*.jar binary
|
51
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve. Please write in English only.
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Don't report bugs that are already in the issue list of Retro Music: https://github.com/h4h13/RetroMusicPlayer/issues
|
||||
|
||||
[ ] Yes
|
||||
-->
|
||||
|
||||
**Have you read the [FAQ](https://github.com/RetroMusicPlayer/RetroMusicPlayer/blob/master/FAQ.md)?**
|
||||
[Yes/No]
|
||||
|
||||
**Has the bug already been reported? Please search in GitHub issue tab before creating an issue.**
|
||||
[Yes/No]
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**How To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Crash log**
|
||||
If the app is crashing, add a crash log
|
||||
<details>
|
||||
<summary>Click to view logs</summary>
|
||||
PASTE YOUR LOGS HERE.
|
||||
</details>
|
||||
|
||||
**Device info:**
|
||||
- Device: [e.g. OnePlus 7]
|
||||
- Android version: [e.g. Android 9]
|
||||
- App version [e.g. 3.5.300_0517]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project. Please write in English only.
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Don't report bugs that are already in the issue list of Retro Music: https://github.com/h4h13/RetroMusicPlayer/issues
|
||||
|
||||
[ ] Yes
|
||||
-->
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
34
.github/workflows/android.yml
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
name: Android CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ dev ]
|
||||
pull_request:
|
||||
branches: [ dev ]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 17
|
||||
- uses: gradle/gradle-build-action@v2
|
||||
- name: Lint Android
|
||||
run: ./gradlew lint
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'zulu'
|
||||
- uses: gradle/gradle-build-action@v2
|
||||
- name: Build
|
||||
run: ./gradlew app:assemble
|
6
.gitignore
vendored
|
@ -38,8 +38,4 @@ obj/
|
|||
captures
|
||||
app/normal/release/
|
||||
/models/
|
||||
|
||||
app/font/
|
||||
app/src/debug/
|
||||
/app/nofont/
|
||||
/crowdin.properties
|
||||
/app/release/
|
||||
|
|
35
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Contributing
|
||||
|
||||
## Using the issue tracker
|
||||
The [issue tracker](https://github.com/RetroMusicPlayer/RetroMusicPlayer/issues) is the preferred channel for bug reports, feature requests and submitting pull requests, but please follow these rules:
|
||||
|
||||
* Please **do not** derail or troll issues. Keep the discussion on topic and respect the opinions of others.
|
||||
|
||||
* Please **do not** post comments consisting solely of "+1" or "👍". Use [GitHub's "reactions" feature](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments) instead.
|
||||
|
||||
* Please **do not** write [enhancement]/[bug]/[feature request] or similar stuff in the title of issues, as there are labels for that purpose that will be added by devs or collaborators.
|
||||
|
||||
## Bug reports
|
||||
A bug is a _demonstrable problem_. Good bug reports are extremely helpful, so thanks!
|
||||
|
||||
Guidelines for bug reports:
|
||||
|
||||
* Use the GitHub issue search, check if the issue has already been reported both in the open issues and in the closed ones, if there are some missing details, add them in issue comments, without creating a new one
|
||||
* Check if the issue has been fixed — try to reproduce it using the latest available build
|
||||
* Isolate the problem — ideally create a reproducible scenario and a live example
|
||||
* Do not report multiple bugs in a single ticket, otherwise it will be confusing.
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more information. Please try to be as detailed as possible in your report.
|
||||
|
||||
|
||||
## Feature requests
|
||||
Feature requests are welcome, please make sure to be as detailed as possible and use screenshots, videos, GIFs, to demonstrate it better, if possible.
|
||||
|
||||
|
||||
## Pull requests
|
||||
**Please ask first** before embarking on any significant pull request (e.g. implementing features, refactoring code), otherwise you risk spending a lot of time working on something that developers might not want to merge into the project. To avoid that, you can join the official [Telegram group](https://t.me/retromusicapp) or open an issue.
|
||||
|
||||
## License
|
||||
By contributing your code, you agree to license your contribution under the [GNU General Public License](https://github.com/RetroMusicPlayer/RetroMusicPlayer/blob/master/LICENSE.md).
|
||||
|
||||
Please note we have a code of conduct, please follow it in all your interactions with the project.
|
195
FAQ.md
|
@ -1,152 +1,157 @@
|
|||
#### Q: How do I get the beta version of Retro Music?
|
||||
## **Q: How do I get the beta version of Retro Music?**
|
||||
You can opt-in for the beta build by clicking on this link: https://play.google.com/apps/testing/code.name.monkey.retromusic
|
||||
|
||||
___
|
||||
|
||||
#### Q: How to restore my purchases?
|
||||
Make sure to switch and use account in the Play Store app through which you purchased before installing Retro music. The account used to install the app is also used to purchase/restore the pro license.
|
||||
## **Q: How to restore my purchases?**
|
||||
Make sure to switch and use your account in the Play Store app through which you purchased before installing Retro Music. The Google account used to install the app is also used to purchase/restore the pro license.
|
||||
|
||||
If you already installed, remove all other accounts except the one with which you purchased premium. Then restore the purchase.
|
||||
If you've already installed the app, remove all other accounts except the one from which you purchased premium, and then restore the purchase.
|
||||
|
||||
___
|
||||
|
||||
#### Q: How do I use offline synced lyrics?
|
||||
There are two methods for how to get offline synced lyrics.
|
||||
## Q: **How do I use offline synced lyrics?**
|
||||
There are three methods for adding offline synced lyrics in Retro Music.
|
||||
|
||||
#### Method 1:-
|
||||
##### STEP 1:
|
||||
Find the time stamped lyrics for your songs which don't have lyrics already. A time stamped lyrics looks like this, "[00:04:02] Some lyrics text" for example.
|
||||
##### STEP 2:
|
||||
Copy these time stamped lyrics.
|
||||
##### STEP 3:
|
||||
### ***Method 1:-***
|
||||
#### STEP 1:
|
||||
Find the time-stamped lyrics for your songs that don't have lyrics already. A time-stamped lyric looks like this, "[00:04:02] Some lyrics text" for example.
|
||||
#### STEP 2:
|
||||
Copy these time-stamped lyrics.
|
||||
#### STEP 3:
|
||||
Open retro music and head to the song synced lyrics editor.
|
||||
##### STEP 4:
|
||||
#### STEP 4:
|
||||
Paste the lyrics there normally and exit the editor
|
||||
##### STEP 5:
|
||||
Open lyrics and you should see your time stamped lyrics there.
|
||||
#### STEP 5:
|
||||
Open lyrics and you should see your time-stamped lyrics there.
|
||||
|
||||
#### Method 2:-
|
||||
##### STEP 1:
|
||||
Find the ".lrc" files for your songs which doesn't have lyrics already.
|
||||
##### STEP 2:
|
||||
A ".lrc" is like a text file which contains the time stamped lyrics for example, "[00:04:02] Some lyrics text"
|
||||
##### STEP 3:
|
||||
### ***Method 2:-***
|
||||
#### STEP 1:
|
||||
Download the time-stamped lyrics for your songs that don't have lyrics already. You can find ".lrc" files for popular songs at either of the websites given below. A time-stamped lyric looks like this, "[00:04:02] Some lyrics text" for example.
|
||||
#### STEP 2:
|
||||
A ".lrc" is like a text file that contains the time-stamped lyrics for example, "[00:04:02] Some lyrics text". Save your time-stamped lyrics are ".lrc" file.
|
||||
#### STEP 3:
|
||||
Now you have to rename the file you created in this way: <song_name> - <artist_name>.lrc or for better matching copy the <song_name> and the <artist_name> from the tag editor and then rename the file.
|
||||
##### STEP 4:
|
||||
You have to copy or move this file into a location on the SD Card: whatever_sdcard/RetroMusic/lyrics/ and paste it there.
|
||||
Finally you will be able to see the lyrics working.
|
||||
> If you want to skip to particular time stamp, simply scroll to the time stamp from where you want to start from and a 'Play' icon will appear left to the particular stamp. Tap on play button to play from there.
|
||||
### STEP 4:
|
||||
Now paste the LRC files to the following path: /sdcard/Retromusic/lyrics/
|
||||
Here sdcard is your internal storage.
|
||||
|
||||
### ***Method 3:- (Requires third-party app)***
|
||||
#### STEP 1:
|
||||
Download automatag or autotagger from Play Store.
|
||||
#### STEP 2:
|
||||
Find the time-stamped lyrics for your songs that don't have lyrics already. You can find ".lrc" files for popular songs at either of the websites given below. A time-stamped lyric looks like this, "[00:04:02] Some lyrics text" for example.
|
||||
#### STEP 3:
|
||||
Find your song to edit and paste the synced lyrics.
|
||||
> These apps add those synced lyrics in the music file itself instead of creating a ".lrc file for it."
|
||||
|
||||
#### Q: Why isn't the artist's image downloading?
|
||||
Last.fm has disabled the download of artist's images for the time being, whether functionality for this will be restored in future is uncertain. So we have moved to deezer API for artist images which have very less artists in their database and you might not able to see covers on every artist profiles.
|
||||
**Lyrics Website Links:**
|
||||
- https://www.lyricsify.com/
|
||||
- https://www.syair.info
|
||||
|
||||
**Some Important Notes:**
|
||||
- If you want to skip to a particular timestamp, simply scroll to the time stamp from where you want to start and a 'Play' icon will appear left to the particular stamp. Tap on the play button to play from there.
|
||||
- When you save lyrics by pasting lyrics in lyrics editor, close the lyrics and open again for lyrics to show.
|
||||
- For those who are having difficulty in making the synced lyrics work, we have a short tutorial video on it. Hope this helps you. {[Link in the note or here](https://youtu.be/1oIOTGWhNMY)}
|
||||
|
||||
#### Q: How do I change the theme?
|
||||
___
|
||||
|
||||
## **Q: How do I change the theme?**
|
||||
Settings -> Look and feel -> Select your theme.
|
||||
___
|
||||
|
||||
## **Q: Equalizer is very laggy and unstable or I am getting a "No equalizer found" error. Why?**
|
||||
- The Retro music in-built equalizer was removed updates ago so the only equalizer you will have by your OEM or Android native equalizer which isn't made by us and have no control over them. So you can report those issues to your OEM so that they can provide a fix in the next updates.
|
||||
|
||||
#### Q: Equailizer is very laggy and unstable or I am getting "No equalizer found" error. why?
|
||||
The Retro music in-built equalizer was removed updates ago so the only equalizer you will have by your OEM or android which aren't made by us and have no control over them. So you can report those issues to your OEM so that they can provide a fix in next updates.
|
||||
- If you are seeing "No Equalizer Found" in your device, this means your device doesn't have a stock equalizer "MusicFx" Equalizer. You can try using this one. It's made by AEX ROM developers.
|
||||
|
||||
If you are seeing "No Equalizer Found" in your device, this means your device doesn't have stock equalizer MusicFx Equalizer. You can try using this one. Its made by AEX ROM developers.
|
||||
https://drive.google.com/file/d/1_1bpsn6roeEyElGKikbU39lVKUH8O3xp/view?**usp=drivesdk
|
||||
|
||||
https://drive.google.com/file/d/1_1bpsn6roeEyElGKikbU39lVKUH8O3xp/view?usp=drivesdk
|
||||
___
|
||||
|
||||
|
||||
#### Q: Why aren't last added songs showing?
|
||||
## **Q: Why aren't last added songs showing?**
|
||||
Settings -> Other -> Last added playlist interval -> Select an option from the list.
|
||||
___
|
||||
|
||||
|
||||
#### Q: How do I enable fullscreen lockscreen controls?
|
||||
## **Q: How do I enable fullscreen lock screen controls?**
|
||||
Settings -> Personalize -> Fullscreen controls -> Enable (this will only be visible when songs are playing from Retro Music).
|
||||
___
|
||||
|
||||
|
||||
#### Q: Why are gallery or random pictures showing up as album art?
|
||||
## **Q: Why are my gallery or random pictures showing up as album art?**
|
||||
Settings -> Images -> Ignore media store covers -> Enable
|
||||
___
|
||||
|
||||
|
||||
#### Q: Which file types are supported?
|
||||
## **Q: Which file types are supported?**
|
||||
Retro Music uses the native media player that comes with your Android phone, so as long as a file type is supported by your phone, it's supported by Retro Music.
|
||||
___
|
||||
|
||||
|
||||
#### Q: Why is my device slowing down when I'm using the app?
|
||||
## **Q: Why is my device slowing down when I'm using the app?**
|
||||
Retro Music is image intensive, it keeps images in the cache for quick loading.
|
||||
___
|
||||
|
||||
|
||||
#### Q: The title "Retro Music" is showing on the top of the app, how can i fix this?
|
||||
## **Q: The title "Retro Music" is showing on the top of the app, how can I fix this?**
|
||||
Clear the app's cache and data.
|
||||
___
|
||||
|
||||
## **Q: My app is crashing, how do I fix this?** (Sorry, settings have changed internally)
|
||||
Please try to clear the data of the app. If it doesn't work, reinstalling fresh from the play store should help.
|
||||
___
|
||||
|
||||
#### Q: My app is crashing, how do i fix this? (Sorry, settings have changed internally)
|
||||
Please try clear data of the app. If it doesn't work, reinstalling fresh from play store should help.
|
||||
|
||||
|
||||
#### Q: Why has all the text gone white/dissapeared?
|
||||
## **Q: Why has all the text gone white/disappeared?**
|
||||
Change the theme to Black or Dark and change it back to what you had before.
|
||||
___
|
||||
|
||||
## **Q: Why some of my songs are not showing in my library?**
|
||||
- Try checking up if those songs are not less than 30 seconds, if so head to settings -> other -> filter song duration. Put this to zero and see the songs that should start appearing in the library.
|
||||
|
||||
#### Q: Why some of my songs are not showing in my library?
|
||||
Try checking up if those songs are not less than 30 seconds, if so head to settings -> other -> filter song duration. Put this to zero and see the songs should start appearimg in the library.
|
||||
- If this doesn't work out for you, re-scanning the media folder should help and subsequently rebooting the device to refresh the media store.
|
||||
|
||||
If this doesn't work out for you, re-scanning the media folder should help and subsquently rebooting the device to refresh media store.
|
||||
- At last, resort, If nothing worked and your audio files are stored in SD card. Try moving them to internal memory then back to SD card.
|
||||
|
||||
At last resort, If nothing worked and your audio files are stored in SD card. Try moving them to internal memory then back to SD card.
|
||||
___
|
||||
|
||||
## **Q: Why does my library shows song files twice or no song at all?**
|
||||
If you are seeing duplication of songs in the library or no songs at all, then it's because of the Media Store issue which got affected by some other app.
|
||||
|
||||
#### Q: Why some of my songs are not showing in my library?
|
||||
1. Try checking up if those songs are not less than 30 seconds, if so head to settings -> other -> filter song duration. Put this to zero and see the songs should start appearimg in the library.
|
||||
***To fix this:***
|
||||
1. Head to your device settings
|
||||
|
||||
2. If this doesn't work out for you, re-scanning the media folder should help and subsquently rebooting the device to refresh media store.
|
||||
1. Open up "Apps & notifications" (This name depends from ROM to ROM)
|
||||
|
||||
3. At last resort, If nothing worked and your audio files are stored in SD card. Try moving them to internal memory then back to SD card.
|
||||
1. Find the 'Media storage' app and clear storage (both data and cache) of it.
|
||||
|
||||
1. Then open the Retro Music app and manually scan your music from your storage.
|
||||
|
||||
#### Q: Why my playlist/playlist songs keep disappearing?
|
||||
Playlist/Playlist songs disappearing is based on android media store system. Save those playlist as file(Tap on three dot menu next to available playlist and save as file) and it should get fixed.
|
||||
1. Reboot the device to refresh the media store (Not sure if this is necessary)
|
||||
|
||||
**NOTE:** Don't panic when you will open Retro Music and see "Zero" songs there in the library. It's because you cleared Media Store which is responsible for recognising files on your device.
|
||||
___
|
||||
|
||||
#### Q: Why does my library shows song files twice or no song at all?
|
||||
If you are seeing duplication of songs in the library or no songs at all, then it's because of Media Store issue which got affected by some other app.
|
||||
## **Q: I can't find the folder menu anymore after the latest update?**
|
||||
Head to settings -> personalise. And select folders from "library categories". If there is no option of folders, tap on reset and select folders.
|
||||
___
|
||||
|
||||
To fix this:
|
||||
## **Q: After updating the app to the latest version, the font got removed. Why?**
|
||||
- Retro Music's font has now been replaced with system font, which means the default font your system uses will be used by Retro Music too. It fixes all font-related issues you used to face/are facing in the app.
|
||||
|
||||
• Head to your device settings
|
||||
- With the recent Retro Music v5 release, we have a built-in optional font "Manrope font" which you can toggle from Settings > Look & Feel > Toggle "Use manrope font".
|
||||
|
||||
• Open up "Apps & notifications" (This name depends from ROM to ROM)
|
||||
- If you think the font looks ugly, then you just need to change the default font from your Android settings (or use any Magisk module). If you can't, there's nothing we can do about it.
|
||||
___
|
||||
|
||||
• Find 'Media storage' app and clear storage (both data and cache) of it.
|
||||
## **Q: How to export playlist?**
|
||||
- ***From Retro Music:***
|
||||
|
||||
• Then open Retro Music app and manually scan your music from your storage.
|
||||
Head to the playlists tab > tap on the three-dot menu on the playlist you want to export > save as a file.
|
||||
|
||||
• Reboot the device to refresh media store (Not sure if this is necessary)
|
||||
- ***From Other Music Players:***
|
||||
|
||||
NOTE: Don't panic when you will open Retro Music and see "Zero" songs there in the library. It's because you cleared Media Store which is responsible for recognising files on your device.
|
||||
In your built-in music player, there should be an option to save that playlist as a file. Save them and import them from the file manager by opening it into retro music.
|
||||
|
||||
> Note that such playlist must be of your offline music only since retro music is an offline music player, not an online music player. So if your playlist is of online music, it can't be opened on other offline players nor can be exported
|
||||
___
|
||||
|
||||
#### Q: I can't find folder menu anymore after latest update?
|
||||
Head to settings -> personalise. And select folders from "library categories". If there is option of folders, tap on reset and select folders.
|
||||
## **Q: How to restore/import playlist?**
|
||||
Retro Music will automatically detect any playlist file when that playlist file is stored in internal storage/Playlist. However, if it doesn't, just open any "File manager" and open that playlist file with Retro Music.
|
||||
|
||||
|
||||
#### Q: After updating the app to latest version, font got removed. Why?
|
||||
Retro Music's font have now been replaced with system font now, which means the default font your system uses will be used by Retro too. It fixes all font related issues you used to face/are facing in the app.
|
||||
|
||||
If you think the font looks ugly, then you just need to change the default font from your Android settings (or use any Magisk module). If you can't, there's nothing we can do about it.
|
||||
|
||||
|
||||
#### Q: How to export playlist:
|
||||
In your built-in music player, there should be an option to save those playlist as file. Save them and import from file manager by opening it into retro music.
|
||||
|
||||
Note that those playlist must be of your offline music only since retro music is offline music player not an online music player. So if your playlist are of online music, it can't be opened on other offline players nor can be exported
|
||||
|
||||
|
||||
#### Q: How to restore/import playlist:
|
||||
Retro Music will automatically detect any playlist file when that playlist file is stored in InternalStorage/Playlist. However, if it doesn't, just open "file manager" and open that playlist file with Retro Music.
|
||||
|
||||
For restoring playlists, the location of songs must be same in both Playlist file and in your storage. For example, your music is in "Internalstorage/Music" and playlist file has songs location "Internalstorage/Songs". Then it will not going to work since both these location are different.
|
||||
|
||||
|
||||
#### Q: Adding songs to playlist or marking them as favourite are making app crash. Why?
|
||||
It's a known issue with only android 10 with its media store API when songs are in SD card due to introduction of Scoped Storage by Google. The issue have been created on Google Issue Tracker by many users. Many other players which doesn't have this issue are using a custom database for storing playlist. We will soon be implementing a custom database for playlist to fix this issue!
|
||||
|
||||
Workaround: You can move all songs to internal storage to fix the issue.
|
||||
|
||||
ISSUE link: https://issuetracker.google.com/issues/147619577
|
||||
For restoring playlists successfully, the location of songs must be the same in both the "Playlist" file and in your storage. For example, If your music is in "Internal storage/Music" and the playlist file has songs location "Internal storage/Songs". Then it will not be going to work since both these locations are different.
|
113
README.md
|
@ -2,77 +2,96 @@
|
|||
|
||||
Material Design music player for Android music lovers
|
||||
|
||||
## Differences between RetroMusicPlayer and Metro
|
||||
- Google Play libraries removed (in favor for F-Droid)
|
||||
- Pro features for free
|
||||
- Bug fixes
|
||||
## Downloads
|
||||
|
||||
## Screenshots
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid"
|
||||
height="80">](https://f-droid.org/en/packages/io.github.muntashirakon.Music/)
|
||||
|
||||
## Differences between Metro and [RetroMusicPlayer](https://github.com/h4h13/RetroMusicPlayer)
|
||||
- Google Play libraries removed (fully libre)
|
||||
- Pro features available for free
|
||||
- Fully offline (INTERNET permission removed)
|
||||
- Bug fixes
|
||||
- Minor differences in UI
|
||||
|
||||
## 📱 Screenshots
|
||||
### App Themes
|
||||
| <img src="screenshots/home.jpeg" width="200"/> | <img src="screenshots/home_dark.jpeg" width="200"/> | <img src="screenshots/home_black.jpeg" width="200"/> |
|
||||
| <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg" width="200"/> | <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg" width="200"/> | <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg" width="200"/> |
|
||||
|:---:|:---:|:---:|
|
||||
|Clearly white| Kinda dark | Just black|
|
||||
|
||||
### Player screen
|
||||
| <img src="screenshots/home.jpeg" width="200"/>| <img src="screenshots/list.jpeg" width="200"/>| <img src="screenshots/albums.jpeg" width="200"/>| <img src="screenshots/settings.jpeg" width="200"/>|
|
||||
|:---:|:---:|:---:|:---:|
|
||||
| Home | Songs | Albums | Settings |
|
||||
| <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg" width="200"/>| <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg" width="200"/>| <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/6.jpg" width="200"/>| <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/7.jpg" width="200"/>| <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/8.jpg" width="200"/>|
|
||||
|:---:|:---:|:---:|:---:|:---:|
|
||||
| Home | Songs | Albums | Artists | Settings |
|
||||
|
||||
### 9+ Now playing themes
|
||||
|
||||
| <img src="screenshots/np_normal.jpeg" width="200"/> |<img src="screenshots/np_fit.jpeg" width="200"/>| <img src="screenshots/np_flat.jpeg" width="200"/> | <img src="screenshots/np_color.jpeg" width="200"/> | <img src="screenshots/np_material.jpeg" width="200"/> |
|
||||
### Synced lyrics screen (Over Cover)
|
||||
| <img src="screenshots/synced_over_light.jpg" width="200"/>| <img src="screenshots/synced_over_dark.jpg" width="200"/>| <img src="screenshots/synced_over_black.jpg" width="200"/>|
|
||||
|:---:|:---:|:---:|
|
||||
| Synced Over Cover light | Synced Over Cover dark | Synced Over Cover black |
|
||||
|
||||
### Synced lyrics screen (Replace Cover)
|
||||
| <img src="screenshots/synced_replace_light.jpg" width="200"/>| <img src="screenshots/synced_replace_dark.jpg" width="200"/>| <img src="screenshots/synced_replace_black.jpg" width="200"/>|
|
||||
|:---:|:---:|:---:|
|
||||
| Synced Replace Cover light | Synced Replace Cover dark | Synced Replace Cover black |
|
||||
|
||||
### 10+ Now playing themes
|
||||
| <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg" width="200"/> |<img src="screenshots/fit.jpg" width="200"/>| <img src="screenshots/flat.jpg" width="200"/> | <img src="screenshots/color.jpg" width="200"/> | <img src="screenshots/material.jpg" width="200"/> |
|
||||
|:-----: |:-----: |:-----: |:-----: |:-----: |
|
||||
| Normal | Fit | Flat | Color | Material |
|
||||
|
||||
| <img src="screenshots/no_classic.jpeg" width="200"/> |<img src="screenshots/np_adaptive.jpeg" width="200"/>| <img src="screenshots/np_blur.jpeg" width="200"/> | <img src="screenshots/np_tiny.jpeg" width="200"/> | <img src="screenshots/np_peak.jpeg" width="200"/> |
|
||||
| <img src="screenshots/classic.jpg" width="200"/> |<img src="screenshots/adaptive.jpg" width="200"/>| <img src="screenshots/blur.jpg" width="200"/> | <img src="screenshots/tiny.jpg" width="200"/> | <img src="screenshots/peek.jpg" width="200"/> |
|
||||
|:-----: |:-----: |:-----: |:-----: |:-----: |
|
||||
| Classic | Adaptive | Blur | Tiny | Peak |
|
||||
| Classic | Adaptive | Blur | Tiny | Peek |
|
||||
|
||||
### 🧭 Navigation never made easier
|
||||
Self-explanatory interface without overloaded menus
|
||||
## 🧭 Navigation never made easier
|
||||
Self-explanatory interface without overloaded menus.
|
||||
|
||||
### 🎨 Colorful
|
||||
You can choose between three different main themes: Clearly white, Kind
|
||||
dark and Just black for AMOLED displays. Select your favorite accent
|
||||
## 🎨 Colorful
|
||||
You can choose between three different main themes: Clearly White, Kinda
|
||||
Dark and Just Black for AMOLED displays. Select your favorite accent
|
||||
color from a color palette.
|
||||
|
||||
### 🏠 Home
|
||||
Where you can have your recently/ top played Artists, Albums and
|
||||
Favorite Songs. No other music player has this feature
|
||||
## 🏠 Home
|
||||
Where you can view your recently/top played artists, albums and
|
||||
favorite songs. No other music player has this feature.
|
||||
|
||||
### 📦 Included Features
|
||||
- Base 3 themes (Clearly white, Kinda dark and Just Black)
|
||||
## 📦 Included Features
|
||||
- Base 3 themes (Clearly White, Kinda Dark and Just Black)
|
||||
- Choose from 10+ now playing themes
|
||||
- Drive Mode
|
||||
- Driving Mode
|
||||
- Headset/Bluetooth support
|
||||
- Music Duration Filter
|
||||
- Folder support - Play song by folder
|
||||
- Music duration filter
|
||||
- Android auto support
|
||||
- Wallpaper accent picker on Android 8.1+
|
||||
- Material You support on Android 12+
|
||||
- Monet themed icon support on Android 13+
|
||||
- Folder support - Play songs by folder
|
||||
- Gapless playback
|
||||
- Volume controls
|
||||
- More than 10 Now playing themes
|
||||
- Carousel effect for an album cover
|
||||
- Home screen Widgets
|
||||
- Lock Screen Playback Controls
|
||||
- Lyrics Screen(download and sync with music)
|
||||
- Sleep Timer
|
||||
- Home screen Widgets
|
||||
- Easy Drag to Sort Playlist & Play Queue
|
||||
- Carousel effect for album covers
|
||||
- Home screen widgets
|
||||
- Lock screen playback controls
|
||||
- Lyrics screen (download and sync with music)
|
||||
- Sleep timer
|
||||
- Easy drag to sort playlist & play queue
|
||||
- Tag editor
|
||||
- Create, Edit, Import playlists
|
||||
- Create, edit and import playlists
|
||||
- Playing queue with reorder
|
||||
- User profile
|
||||
- 30 Languages support
|
||||
- Browse and play your music by Songs, Albums, Artists, Playlists,
|
||||
Genre
|
||||
- Smart Auto Playlists - Recently played/Top Played/History Fully
|
||||
playlist support & Build your own playlist on the go
|
||||
- 30+ languages support
|
||||
- Browse and play your music by songs, albums, artists, playlists and
|
||||
genre
|
||||
- Smart Auto Playlists - Recently played, most played and history
|
||||
- Build your playlist on the go
|
||||
|
||||
We are trying our best to bring you the best user experience. The app is regularly being updated for bug fixes and new features.
|
||||
|
||||
We are trying our best to bring you the best user experience. Until now
|
||||
it is a beta version - bug fixes (if any) and more features are on the
|
||||
way. for FAQ's https://goo.gl/DR2mE2
|
||||
|
||||
### 🗂️ License
|
||||
## 🗂️ License
|
||||
|
||||
Metro is released under the GNU General Public License v3.0
|
||||
(GPLv3), which can be found here: [License](LICENSE.md)
|
||||
(GPLv3), which can be found [here](LICENSE.md)
|
||||
|
||||
|
||||
> Please note: Metro is an offline music player app. It doesn't support music downloading or online music streaming.
|
||||
|
|
164
app/build.gradle
|
@ -1,40 +1,30 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: "ru.cleverpumpkin.proguard-dictionaries-generator"
|
||||
apply plugin: "androidx.navigation.safeargs.kotlin"
|
||||
|
||||
proguardDictionaries {
|
||||
dictionaryNames = [
|
||||
"build/class-dictionary",
|
||||
"build/package-dictionary",
|
||||
"build/obfuscation-dictionary"
|
||||
]
|
||||
}
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
apply plugin: 'com.google.devtools.ksp'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion = '30.0.0'
|
||||
compileSdk 33
|
||||
namespace "code.name.monkey.retromusic"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
minSdk 21
|
||||
targetSdk 33
|
||||
|
||||
renderscriptTargetApi 29 //must match target sdk and build tools
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
applicationId 'io.github.muntashirakon.Music'
|
||||
versionCode 10443
|
||||
versionName '3.5.10'
|
||||
versionCode 10603
|
||||
versionName '6.1.0'
|
||||
|
||||
multiDexEnabled true
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix '.debug'
|
||||
|
@ -42,15 +32,17 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/NOTICE'
|
||||
exclude 'META-INF/java.properties'
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
lintOptions {
|
||||
disable 'MissingTranslation'
|
||||
disable 'InvalidPackage'
|
||||
abortOnError false
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += ['META-INF/LICENSE', 'META-INF/NOTICE', 'META-INF/java.properties']
|
||||
}
|
||||
}
|
||||
lint {
|
||||
abortOnError true
|
||||
warning 'ImpliedQuantity', 'Instantiatable', 'MissingQuantity', 'MissingTranslation'
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
@ -59,90 +51,82 @@ android {
|
|||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
includeInBundle = false
|
||||
}
|
||||
configurations.configureEach {
|
||||
resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9'
|
||||
}
|
||||
androidExtensions {
|
||||
experimental = true
|
||||
}
|
||||
kapt {
|
||||
generateStubs = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation project(':appthemehelper')
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
|
||||
implementation "androidx.gridlayout:gridlayout:1.0.0"
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
implementation "androidx.viewpager2:viewpager2:1.1.0-alpha01"
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.annotation:annotation:1.1.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||
implementation 'androidx.core:core-ktx:1.3.1'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.2.5'
|
||||
|
||||
implementation "androidx.appcompat:appcompat:$appcompat_version"
|
||||
implementation 'androidx.annotation:annotation:1.6.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0'
|
||||
implementation "androidx.preference:preference-ktx:$preference_version"
|
||||
implementation "androidx.core:core-ktx:$core_version"
|
||||
implementation 'androidx.palette:palette-ktx:1.0.0'
|
||||
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta8'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation 'androidx.mediarouter:mediarouter:1.3.1'
|
||||
|
||||
implementation 'com.google.android.material:material:1.3.0-alpha01'
|
||||
implementation "androidx.navigation:navigation-runtime-ktx:$navigation_version"
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
|
||||
|
||||
def room_version = '2.5.1'
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
implementation "androidx.room:room-ktx:$room_version"
|
||||
ksp "androidx.room:room-compiler:$room_version"
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
|
||||
implementation "androidx.core:core-splashscreen:1.0.0"
|
||||
|
||||
implementation "com.google.android.material:material:$mdc_version"
|
||||
|
||||
def retrofit_version = '2.9.0'
|
||||
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
|
||||
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
|
||||
|
||||
def material_dialog_version = "0.9.6.0"
|
||||
def material_dialog_version = "3.3.0"
|
||||
implementation "com.afollestad.material-dialogs:core:$material_dialog_version"
|
||||
implementation "com.afollestad.material-dialogs:commons:$material_dialog_version"
|
||||
implementation 'com.afollestad:material-cab:0.1.12'
|
||||
implementation "com.afollestad.material-dialogs:input:$material_dialog_version"
|
||||
implementation "com.afollestad.material-dialogs:color:$material_dialog_version"
|
||||
|
||||
implementation 'com.github.bumptech.glide:glide:3.8.0'
|
||||
implementation 'com.github.bumptech.glide:okhttp3-integration:1.5.0'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0'
|
||||
implementation 'com.afollestad:material-cab:2.0.1'
|
||||
|
||||
implementation('com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.11.0@aar') {
|
||||
transitive = true
|
||||
}
|
||||
def kotlin_coroutines_version = "1.3.8"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
|
||||
|
||||
implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:3.4.0.201406110918-r'
|
||||
def koin_version = '3.4.0'
|
||||
implementation "io.insert-koin:koin-core:$koin_version"
|
||||
implementation "io.insert-koin:koin-android:$koin_version"
|
||||
|
||||
implementation 'com.github.ksoichiro:android-observablescrollview:1.6.0'
|
||||
implementation 'com.github.kabouzeid:recyclerview-fastscroll:1.9-kmod'
|
||||
def glide_version = '4.15.1'
|
||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
||||
ksp "com.github.bumptech.glide:ksp:$glide_version"
|
||||
|
||||
implementation 'com.github.AdrienPoupa:jaudiotagger:2.2.3'
|
||||
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
|
||||
|
||||
implementation 'com.github.bosphere.android-fadingedgelayout:fadingedgelayout:1.0.0'
|
||||
|
||||
implementation 'net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:3.0.0-RC3'
|
||||
implementation 'com.github.jetradarmobile:android-snowfall:1.2.1'
|
||||
|
||||
implementation "dev.chrisbanes.insetter:insetter:0.6.1"
|
||||
|
||||
implementation 'com.github.Adonai:jaudiotagger:2.3.15'
|
||||
implementation 'com.r0adkll:slidableactivity:2.1.0'
|
||||
implementation 'com.heinrichreimersoftware:material-intro:1.6'
|
||||
implementation 'me.zhanghai.android.fastscroll:library:1.1.0'
|
||||
|
||||
def lifecycle_version = "2.2.0"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
|
||||
implementation 'me.jorgecastillo:androidcolorx:0.2.0'
|
||||
debugImplementation 'com.amitshekhar.android:debug-db:1.0.4'
|
||||
implementation 'com.github.dhaval2404:imagepicker:1.7.1'
|
||||
|
||||
def koin_version = "2.1.5"
|
||||
implementation "org.koin:koin-core:$koin_version"
|
||||
implementation "org.koin:koin-core-ext:$koin_version"
|
||||
implementation "org.koin:koin-androidx-scope:$koin_version"
|
||||
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
|
||||
implementation "org.koin:koin-androidx-fragment:$koin_version"
|
||||
implementation "org.koin:koin-androidx-ext:$koin_version"
|
||||
|
||||
def nav_version = "2.3.0"
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
|
||||
|
||||
implementation 'com.heinrichreimersoftware:material-intro:2.0.0'
|
||||
implementation 'com.github.dhaval2404:imagepicker:2.1'
|
||||
implementation 'me.zhanghai.android.fastscroll:library:1.2.0'
|
||||
implementation 'cat.ereza:customactivityoncrash:2.4.0'
|
||||
implementation 'me.tankery.lib:circularSeekBar:1.4.2'
|
||||
}
|
60
app/proguard-rules.pro
vendored
|
@ -16,9 +16,9 @@
|
|||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# Preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
|
@ -26,40 +26,46 @@
|
|||
|
||||
-dontwarn java.lang.invoke.*
|
||||
-dontwarn **$$Lambda$*
|
||||
-dontwarn javax.annotation.**
|
||||
|
||||
# RetroFit
|
||||
-dontwarn retrofit.**
|
||||
-keep class retrofit.** { *; }
|
||||
-keepattributes Signature
|
||||
-keepattributes Exceptions
|
||||
-dontwarn javax.annotation.**
|
||||
|
||||
# Glide
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
-keep class * extends com.bumptech.glide.module.AppGlideModule {
|
||||
<init>(...);
|
||||
}
|
||||
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder {
|
||||
*** rewind();
|
||||
}
|
||||
|
||||
-keep class !android.support.v7.internal.view.menu.**,** {*;}
|
||||
# OkHttp
|
||||
-keepattributes Signature
|
||||
-keepattributes *Annotation*
|
||||
-keep interface com.squareup.okhttp3.** { *; }
|
||||
-dontwarn com.squareup.okhttp3.**
|
||||
|
||||
-dontwarn
|
||||
-ignorewarnings
|
||||
#-dontwarn
|
||||
#-ignorewarnings
|
||||
|
||||
-keep public class android.support.design.widget.BottomNavigationView { *; }
|
||||
-keep public class android.support.design.internal.BottomNavigationMenuView { *; }
|
||||
-keep public class android.support.design.internal.BottomNavigationPresenter { *; }
|
||||
-keep public class android.support.design.internal.BottomNavigationItemView { *; }
|
||||
#Jaudiotagger
|
||||
-dontwarn org.jaudiotagger.**
|
||||
-dontwarn org.jcodec.**
|
||||
-keep class org.jaudiotagger.** { *; }
|
||||
-keep class org.jcodec.** { *; }
|
||||
|
||||
#-dontwarn android.support.v8.renderscript.*
|
||||
#-keepclassmembers class android.support.v8.renderscript.RenderScript {
|
||||
# native *** rsn*(...);
|
||||
# native *** n*(...);
|
||||
#}
|
||||
|
||||
#-keep class org.jaudiotagger.** { *; }
|
||||
|
||||
|
||||
-obfuscationdictionary build/obfuscation-dictionary.txt
|
||||
-classobfuscationdictionary build/class-dictionary.txt
|
||||
-packageobfuscationdictionary build/package-dictionary.txt
|
||||
-keepclassmembers enum * { *; }
|
||||
-keepattributes *Annotation*, Signature, Exception
|
||||
-keepnames class androidx.navigation.fragment.NavHostFragment
|
||||
-keep class * extends androidx.fragment.app.Fragment{}
|
||||
-keepnames class * extends android.os.Parcelable
|
||||
-keepnames class * extends java.io.Serializable
|
||||
-keep class code.name.monkey.retromusic.network.model.** { *; }
|
||||
-keep class code.name.monkey.retromusic.model.** { *; }
|
||||
-keep class com.google.android.material.bottomsheet.** { *; }
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"version": 1,
|
||||
"artifactType": {
|
||||
"type": "APK",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "io.github.muntashirakon.Music",
|
||||
"variantName": "release",
|
||||
"elements": [
|
||||
{
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"properties": [],
|
||||
"versionCode": 10443,
|
||||
"versionName": "3.5.10",
|
||||
"enabled": true,
|
||||
"outputFile": "app-release.apk"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"version": 1,
|
||||
"artifactType": {
|
||||
"type": "APK",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "code.name.monkey.retromusic",
|
||||
"variantName": "release",
|
||||
"elements": [
|
||||
{
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"properties": [],
|
||||
"versionCode": 10438,
|
||||
"versionName": "10438",
|
||||
"enabled": true,
|
||||
"outputFile": "app-release.apk"
|
||||
}
|
||||
]
|
||||
}
|
BIN
app/src/debug/res/font/bold.ttf
Normal file
BIN
app/src/debug/res/font/google_sans_bold.ttf
Normal file
BIN
app/src/debug/res/font/google_sans_medium.ttf
Normal file
BIN
app/src/debug/res/font/google_sans_regular.ttf
Normal file
BIN
app/src/debug/res/font/medium.ttf
Normal file
BIN
app/src/debug/res/font/regular.ttf
Normal file
12
app/src/debug/res/font/sans.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<font-family xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<font
|
||||
android:font="@font/google_sans_regular"
|
||||
android:fontWeight="400" />
|
||||
<font
|
||||
android:font="@font/google_sans_medium"
|
||||
android:fontWeight="600" />
|
||||
<font
|
||||
android:font="@font/google_sans_bold"
|
||||
android:fontWeight="700" />
|
||||
</font-family>
|
5
app/src/debug/res/values/bools.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="md3_available">true</bool>
|
||||
<bool name="allowBackup">false</bool>
|
||||
</resources>
|
4
app/src/debug/res/values/donottranslate.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">Metro Debug</string>
|
||||
</resources>
|
105
app/src/debug/res/values/styles.xml
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="TextViewNormal" parent="">
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
</style>
|
||||
|
||||
<style name="TextViewNormalCompress" parent="TextAppearance.MaterialComponents.Caption">
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
</style>
|
||||
|
||||
<style name="TextViewHeadline4" parent="TextAppearance.MaterialComponents.Headline4">
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
</style>
|
||||
|
||||
<style name="TextViewHeadline4.Compress" parent="TextAppearance.MaterialComponents.Headline4">
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
<item name="android:textSize">32sp</item>
|
||||
</style>
|
||||
|
||||
<style name="TextViewHeadline5" parent="TextAppearance.MaterialComponents.Headline5">
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
</style>
|
||||
|
||||
<style name="TextViewCaption" parent="TextAppearance.MaterialComponents.Caption">
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
</style>
|
||||
|
||||
<style name="TextViewHeadline6" parent="TextAppearance.MaterialComponents.Headline6">
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
</style>
|
||||
|
||||
<style name="TextViewHeadline3" parent="TextAppearance.MaterialComponents.Headline3">
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
</style>
|
||||
|
||||
<style name="TextViewHeadline2" parent="TextAppearance.MaterialComponents.Headline2">
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
</style>
|
||||
|
||||
<style name="TextViewSubtitle1" parent="TextAppearance.MaterialComponents.Subtitle1">
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
</style>
|
||||
|
||||
<style name="TextViewSubtitle2" parent="TextAppearance.MaterialComponents.Subtitle2">
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
</style>
|
||||
|
||||
<style name="TextViewBody1" parent="TextAppearance.MaterialComponents.Body1">
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
</style>
|
||||
|
||||
<style name="TextViewButton" parent="TextAppearance.MaterialComponents.Button">
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
</style>
|
||||
|
||||
<style name="TextViewBody2" parent="TextAppearance.MaterialComponents.Body2">
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
</style>
|
||||
|
||||
<style name="TextViewOverline" parent="TextAppearance.MaterialComponents.Overline">
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTextAppearance.MaterialAlertDialog.Button" parent="Widget.MaterialComponents.Button.TextButton">
|
||||
<item name="android:textAppearance">@style/TextViewButton</item>
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="android:padding">0dp</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTextAppearance.MaterialAlertDialog.Body" parent="MaterialAlertDialog.MaterialComponents.Body.Text">
|
||||
<item name="android:textAppearance">@style/TextViewBody1</item>
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
<item name="android:paddingTop">16dp</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTextAppearance.MaterialAlertDialog.Title" parent="MaterialAlertDialog.MaterialComponents.Title.Text">
|
||||
<item name="android:textAppearance">@style/TextViewHeadline6</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
<item name="android:padding">16dp</item>
|
||||
</style>
|
||||
|
||||
<style name="ToolbarTextAppearanceNormal">
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="android:textAllCaps">false</item>
|
||||
<item name="android:textAppearance">@style/TextViewHeadline6</item>
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
<item name="android:textSize">20sp</item>
|
||||
<item name="android:letterSpacing">0.0125</item>
|
||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="circleImageView" parent="ShapeAppearance.MaterialComponents">
|
||||
<item name="cornerSize">40dp</item>
|
||||
</style>
|
||||
|
||||
<style name="BottomSheetItemTextAppearance" parent="Widget.MaterialComponents.BottomNavigationView.Colored">
|
||||
<item name="android:textSize">13sp</item>
|
||||
<item name="fontFamily">@font/sans</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -1,39 +1,57 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="io.github.muntashirakon.music"
|
||||
package="code.name.monkey.retromusic"
|
||||
android:installLocation="auto">
|
||||
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="29" />
|
||||
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_SETTINGS"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_CONNECT"
|
||||
android:usesPermissionFlags="neverForLocation"
|
||||
tools:targetApi="s" />
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
android:allowBackup="true"
|
||||
android:allowBackup="@bool/allowBackup"
|
||||
android:appCategory="audio"
|
||||
android:configChanges="locale|layoutDirection"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:restoreAnyVersion="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:configChanges="locale|layoutDirection"
|
||||
android:theme="@style/Theme.RetroMusic.FollowSystem"
|
||||
android:usesCleartextTraffic="false"
|
||||
tools:ignore="AllowBackup,GoogleAppIndexingWarning"
|
||||
tools:targetApi="m">
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:ignore="UnusedAttribute">
|
||||
<activity
|
||||
android:name=".activities.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/SplashTheme">
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/Theme.RetroMusic.SplashScreen">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<action android:name="android.intent.action.MUSIC_PLAYER" />
|
||||
|
@ -44,7 +62,6 @@
|
|||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
|
@ -104,30 +121,54 @@
|
|||
<data android:mimeType="vnd.android.cursor.dir/audio" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".activities.albums.AlbumDetailsActivity" />
|
||||
<activity android:name=".activities.artists.ArtistDetailActivity" />
|
||||
<activity android:name=".activities.playlist.PlaylistDetailActivity" />
|
||||
<activity android:name=".activities.PlayingQueueActivity" />
|
||||
<activity android:name=".activities.AboutActivity" />
|
||||
<activity android:name=".activities.tageditor.AlbumTagEditorActivity" />
|
||||
<activity android:name=".activities.tageditor.SongTagEditorActivity" />
|
||||
<activity android:name=".activities.SettingsActivity" />
|
||||
<activity android:name=".activities.LyricsActivity" />
|
||||
<activity android:name=".activities.UserInfoActivity" />
|
||||
<activity android:name=".activities.GenreDetailsActivity" />
|
||||
<activity android:name=".activities.LicenseActivity" />
|
||||
<activity android:name=".activities.WhatsNewActivity" />
|
||||
<activity android:name=".activities.bugreport.BugReportActivity" />
|
||||
<activity android:name=".activities.ShareInstagramStory" />
|
||||
<activity android:name=".activities.DriveModeActivity" />
|
||||
<activity
|
||||
android:name=".activities.search.SearchActivity"
|
||||
android:windowSoftInputMode="stateVisible" />
|
||||
|
||||
<activity android:name=".activities.PermissionActivity" />
|
||||
<activity
|
||||
android:name=".activities.LockScreenActivity"
|
||||
android:noHistory="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:showOnLockScreen="true" />
|
||||
<activity
|
||||
android:name=".fragments.backup.RestoreActivity"
|
||||
android:excludeFromRecents="false"
|
||||
android:exported="true"
|
||||
android:label="@string/restore"
|
||||
android:theme="@style/Theme.RetroMusic.Dialog">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:mimeType="application/octet-stream" />
|
||||
<data android:mimeType="application/x-zip-compressed" />
|
||||
<data android:mimeType="application/zip" />
|
||||
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<data android:mimeType="*/*" />
|
||||
<!--
|
||||
Work around Android's ugly primitive PatternMatcher
|
||||
implementation that can't cope with finding a . early in
|
||||
the path unless it's explicitly matched.
|
||||
-->
|
||||
<data android:host="*" />
|
||||
<data android:pathPattern=".*\\.rmbak" />
|
||||
<data android:pathPattern=".*\\..*\\.rmbak" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.rmbak" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.rmbak" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.rmbak" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".appshortcuts.AppShortcutLauncherActivity"
|
||||
|
@ -138,16 +179,13 @@
|
|||
android:name=".activities.saf.SAFGuideActivity"
|
||||
android:theme="@style/Theme.Intro" />
|
||||
|
||||
<provider
|
||||
android:name=".misc.GenericFileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
|
||||
<activity
|
||||
android:name=".activities.ErrorActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="cat.ereza.customactivityoncrash.RESTART" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
|
@ -159,13 +197,17 @@
|
|||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
|
||||
<receiver android:name=".service.MediaButtonIntentReceiver">
|
||||
<receiver
|
||||
android:name=".service.MediaButtonIntentReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".appwidgets.BootReceiver">
|
||||
<receiver
|
||||
android:name=".appwidgets.BootReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||
|
@ -174,7 +216,7 @@
|
|||
|
||||
<receiver
|
||||
android:name=".appwidgets.AppWidgetBig"
|
||||
android:exported="false"
|
||||
android:exported="true"
|
||||
android:label="@string/app_widget_big_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
|
@ -187,7 +229,7 @@
|
|||
|
||||
<receiver
|
||||
android:name=".appwidgets.AppWidgetClassic"
|
||||
android:exported="false"
|
||||
android:exported="true"
|
||||
android:label="@string/app_widget_classic_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
|
@ -199,7 +241,7 @@
|
|||
</receiver>
|
||||
<receiver
|
||||
android:name=".appwidgets.AppWidgetSmall"
|
||||
android:exported="false"
|
||||
android:exported="true"
|
||||
android:label="@string/app_widget_small_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
|
@ -211,7 +253,7 @@
|
|||
</receiver>
|
||||
<receiver
|
||||
android:name=".appwidgets.AppWidgetText"
|
||||
android:exported="false"
|
||||
android:exported="true"
|
||||
android:label="@string/app_widget_text_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
|
@ -223,7 +265,7 @@
|
|||
</receiver>
|
||||
<receiver
|
||||
android:name=".appwidgets.AppWidgetCard"
|
||||
android:exported="false"
|
||||
android:exported="true"
|
||||
android:label="@string/app_widget_card_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
|
@ -233,13 +275,36 @@
|
|||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/app_widget_card_info" />
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".appwidgets.AppWidgetMD3"
|
||||
android:exported="true"
|
||||
android:label="@string/app_widget_md3_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/app_widget_md3_info" />
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".appwidgets.AppWidgetCircle"
|
||||
android:exported="true"
|
||||
android:label="@string/app_widget_circle_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/app_widget_circle_info" />
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".service.MusicService"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
tools:ignore="ExportedService">
|
||||
android:foregroundServiceType="mediaPlayback"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.media.browse.MediaBrowserService" />
|
||||
</intent-filter>
|
||||
|
@ -253,15 +318,33 @@
|
|||
android:name="com.lge.support.SPLIT_WINDOW"
|
||||
android:value="true" />
|
||||
|
||||
<!-- Android Auto -->
|
||||
<meta-data
|
||||
android:name="io.github.muntashirakon.music.glide.RetroMusicGlideModule"
|
||||
android:value="GlideModule" />
|
||||
android:name="com.google.android.gms.car.application"
|
||||
android:resource="@xml/automotive_app_desc" />
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.car.application.theme"
|
||||
android:resource="@style/CarTheme" />
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.car.notification.SmallIcon"
|
||||
android:resource="@drawable/ic_notification" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule"
|
||||
android:value="GlideModule" />
|
||||
<meta-data
|
||||
android:name="com.android.vending.splits.required"
|
||||
android:value="true" />
|
||||
<!-- For auto-storage of locale on Android 12 and lower -->
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
android:enabled="false"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="autoStoreLocales"
|
||||
android:value="true" />
|
||||
</service>
|
||||
</application>
|
||||
|
||||
<!--
|
||||
This is not that important, it's just here so that we can query equalizer package
|
||||
and check if it's present on A11+ because of Package visibility restrictions.
|
||||
-->
|
||||
<queries>
|
||||
<package android:name="com.android.musicfx" />
|
||||
</queries>
|
||||
</manifest>
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
[
|
||||
{
|
||||
"name": "Hemanth Savarala",
|
||||
"summary": "Lead Developer & Designer",
|
||||
"link": "https://github.com/h4h13",
|
||||
"profile_image": "https://i.imgur.com/AoVs9oj.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Lennart Glamann",
|
||||
"summary": "Play Store banner and Images",
|
||||
"link": "https://t.me/FlixbusLennart",
|
||||
"profile_image": "https://i.imgur.com/Q5Nsx1R.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Daksh P. Jain",
|
||||
"summary": "Telegram group maintainer",
|
||||
"link": "https://dakshpjain.eu.org",
|
||||
"profile_image": "https://i.imgur.com/fnYpg65.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Milind Goel",
|
||||
"summary": "Github & Telegram maintainer",
|
||||
"link": "https://t.me/MilindGoel15",
|
||||
"profile_image": "https://i.imgur.com/Bz4De21_d.jpg"
|
||||
}
|
||||
]
|
BIN
app/src/main/assets/images/daksh.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/assets/images/haythem.jpg
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
app/src/main/assets/images/hemanth.jpg
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
app/src/main/assets/images/lenny.jpg
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
app/src/main/assets/images/milind.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
app/src/main/assets/images/pratham.jpg
Normal file
After Width: | Height: | Size: 3.1 KiB |
76
app/src/main/assets/license.html
Normal file
|
@ -0,0 +1,76 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="IE=edge" http-equiv="X-UA-Compatible">
|
||||
<style media="screen" type="text/css">
|
||||
|
||||
* {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
{style-placeholder}
|
||||
a {
|
||||
color: {link-color};
|
||||
}
|
||||
a:active {
|
||||
color: {link-color-active};
|
||||
}
|
||||
ol {
|
||||
list-style-position: inside;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
li {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p><b><a href="https://github.com/kabouzeid/Phonograph" title="Phonograph"> Phonograph</a></b> by
|
||||
Karim Abou Zeid</p>
|
||||
<p><b><a href="http://developer.android.com/tools/support-library/index.html"
|
||||
title="AOSP Support Libraries">AOSP Support Libraries</a></b> by AOSP contributors</p>
|
||||
<p><b><a href="https://github.com/bumptech/glide" title="Glide"> Glide</a></b> by Sam Judd</p>
|
||||
<p><b><a href="https://github.com/square/retrofit" title="Retrofit"> Retrofit</a></b> by Square team
|
||||
</p>
|
||||
<p><b><a href="http://square.github.io/okhttp/" title="OkHttp"> OkHttp</a></b> by Square team</p>
|
||||
<p><b><a href="https://github.com/InsertKoinIO/koin"
|
||||
title="Koin">Koin</a></b> by Arnaud Giuliani</p>
|
||||
<p><b><a href="https://github.com/afollestad" title="Material Dialogs"> Material Dialogs and Cab</a></b>
|
||||
by Aidan Michael Follestad</p>
|
||||
<p><b><a href="https://github.com/afollestad/material-cab" title="Material Contextual Action Bar">
|
||||
Material Contextual Action Bar</a></b> by Aidan Michael Follestad</p>
|
||||
<p><b><a href="https://github.com/h6ah4i/android-advancedrecyclerview"
|
||||
title="Advanced RecyclerView"> Advanced RecyclerView</a></b> by Haruki Hasegawa</p>
|
||||
<p><b><a href="https://github.com/Ereza/CustomActivityOnCrash"
|
||||
title="Custom Activity on Crash">Custom Activity on Crash</a></b> by Eduard Ereza Martínez
|
||||
</p>
|
||||
<p><b><a href="https://github.com/NanoHttpd/nanohttpd"
|
||||
title="NanoHttpd">NanoHttpd</a></b> by NanoHttpd Team</p>
|
||||
<p><b><a href="https://github.com/tankery/CircularSeekBar"
|
||||
title="Circular Seekbar">Circular Seekbar</a></b> by Tankery</p>
|
||||
<p><b><a href="https://github.com/Kaned1as/jaudiotagger"
|
||||
title="jAudioTagger">jAudioTagger</a></b> by Kanedias</p>
|
||||
<p><b><a href="https://github.com/zhanghai/AndroidFastScroll"
|
||||
title="Android Fast Scroll">Android Fast Scroll</a></b> by Zhang Hai</p>
|
||||
<p><b><a href="https://github.com/Dhaval2404/ImagePicker"
|
||||
title="Image Picker">Image Picker</a></b> by Dhaval Patel</p>
|
||||
<p><b><a href="https://github.com/heinrichreimer/material-intro"
|
||||
title="Material Intro">Material Intro</a></b> by Jan Heinrich Reimer</p>
|
||||
<p><b><a href="https://github.com/r0adkll/Slidr"
|
||||
title="Slidr">Slidr</a></b> by Drew Heavner</p>
|
||||
<p><b><a href="https://github.com/bosphere/Android-FadingEdgeLayout"
|
||||
title="FadingEdgeLayout">FadingEdgeLayout</a></b> by bosphere</p>
|
||||
<p><b><a href="https://github.com/yshrsmz/KeyboardVisibilityEvent"
|
||||
title="KeyboardVisibilityEvent">KeyboardVisibilityEvent</a></b> by Yasuhiro SHIMIZU</p>
|
||||
<p><b><a href="https://github.com/JetradarMobile/android-snowfall"
|
||||
title="android-snowfall">android-snowfall</a></b> by Jetradar Mobile</p>
|
||||
<p><b><a href="https://github.com/chrisbanes/insetter"
|
||||
title="Insetter">Insetter</a></b> by Chris Banes</p>
|
||||
<p><b><a href="https://materialdesignicons.com" title="Icons"> Icons</a></b> by Austin Andrews</p>
|
||||
<p><b><a href="https://www.techjuice.pk" title="City wallpaper"> Material Design City Wallpaper</a></b>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
|
@ -1,63 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="IE=edge" http-equiv="X-UA-Compatible">
|
||||
<style media="screen" type="text/css">
|
||||
|
||||
* {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
{style-placeholder}
|
||||
a {
|
||||
color: {link-color};
|
||||
}
|
||||
a:active {
|
||||
color: {link-color-active};
|
||||
}
|
||||
ol {
|
||||
list-style-position: inside;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
li {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p><b><a href="https://github.com/kabouzeid/Phonograph" title="Phonograph"> Phonograph</a></b> by
|
||||
Karim Abou Zeid</p>
|
||||
<p><b><a href="https://github.com/afollestad" title="Material Dialogs"> Material Dialogs and Cab</a></b>
|
||||
by Aidan Michael Follestad</p>
|
||||
<p><b><a href="http://developer.android.com/tools/support-library/index.html"
|
||||
title="AOSP Support Libraries"> AOSP Support Libraries</a></b>by AOSP contributors</p>
|
||||
<p><b><a href="https://github.com/bumptech/glide" title="Glide"> Glide</a></b> by Sam Judd</p>
|
||||
<p><b><a href="https://github.com/square/retrofit" title="Retrofit"> Retrofit</a></b> by Square team
|
||||
</p>
|
||||
<p><b><a href="https://github.com/afollestad/material-cab" title="Material Contextual Action Bar">
|
||||
Material Contextual Action Bar</a></b> by Aidan Michael Follestad</p>
|
||||
<p><b><a href="http://square.github.io/okhttp/" title="OkHttp"> OkHttp</a></b> by Square team</p>
|
||||
<p><b><a href="https://github.com/hdodenhof/CircleImageView" title="CircleImageView">
|
||||
CircleImageView</a></b> by Henning Dodenhof</p>
|
||||
<p><b><a href="https://github.com/DreaminginCodeZH/MaterialProgressBar" title="MaterialProgressBar">
|
||||
MaterialProgressBar</a></b> by Zhang Hai</p>
|
||||
<p><b><a href="https://github.com/anjlab/android-inapp-billing-v3"
|
||||
title="Android In-App Billing v3 Library"> Android In-App Billing v3 Library</a></b> by
|
||||
Henning Dodenhof</p>
|
||||
<p><b><a href="https://github.com/h6ah4i/android-advancedrecyclerview"
|
||||
title="Advanced RecyclerView"> Advanced RecyclerView</a></b> by Haruki Hasegawa</p>
|
||||
<p><b><a href="https://github.com/ksoichiro/Android-ObservableScrollView"
|
||||
title="Android-ObservableScrollView"> Android-ObservableScrollView</a></b> by Soichiro
|
||||
Kashima</p>
|
||||
<p><b><a href="https://materialdesignicons.com" title="Icons"> Icons</a></b> by Austin Andrews</p>
|
||||
<p><b><a href="https://www.techjuice.pk" title="City wallpaper"> Material Design City Wallpaper</a></b>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -6,22 +6,23 @@
|
|||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
body {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
div {
|
||||
margin: 20px 10px;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-block-end: 0rem;
|
||||
margin-block-start: 0rem;
|
||||
display: inline-block;
|
||||
vertical-align: center;
|
||||
}
|
||||
|
||||
li {
|
||||
font-size: 0.85rem;
|
||||
padding-top: 0.5rem;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
|
@ -36,7 +37,6 @@
|
|||
|
||||
span {
|
||||
font-size: 0.7rem;
|
||||
line-height: 0.7rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
|
@ -44,39 +44,423 @@
|
|||
margin-block-end: 0.5rem;
|
||||
}
|
||||
|
||||
h3 span {
|
||||
border-radius: 0.2rem;
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
padding-top: 0.3rem;
|
||||
padding-bottom: 0.3rem;
|
||||
h3 {
|
||||
margin: 10px 0px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.tag {
|
||||
border-radius: 5px;
|
||||
margin-left: 10px;
|
||||
padding: 5px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
{style-placeholder}
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h5>April 30, 2020</h5>
|
||||
<h2>v3.5.110</h2>
|
||||
<span class="tag"><i>Beta version</i></span>
|
||||
<h3><span class="colorHeader">What's New</span></h3>
|
||||
<ul>
|
||||
<li>Changed profile form image to icon</li>
|
||||
<li>New what's new screen</li>
|
||||
<li>Added In-App language changer, where you can select language</li>
|
||||
</ul>
|
||||
<h3><span class="colorHeader">Improved</span></h3>
|
||||
<ul>
|
||||
<li>Improved loading of Songs, Albums, Artists, Genres, Playlists</li>
|
||||
</ul>
|
||||
<!--<h3><span class="colorHeader">Bug fixes</span></h3>
|
||||
<ul>
|
||||
<li></li>
|
||||
</ul>-->
|
||||
<p>*If you face any UI related issues you clear app data and cache, if itsnot working try to
|
||||
uninstall and install
|
||||
again. </p>
|
||||
|
||||
<div>
|
||||
<h5>March 30, 2023</h5>
|
||||
<h2>v6.1.0</h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>App now targets Android 13, support for Granular media permissions, Photo picker, Per-app language preferences & Predictive back gesture</li>
|
||||
</ul>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Fixed playlist reordering crash</li>
|
||||
<li>Other minor bugs fixes and improvements</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>March 13, 2023</h5>
|
||||
<h2>v6.0.4</h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>Minor redesign in Playlist details screen</li>
|
||||
<li>Updated translations</li>
|
||||
</ul>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Fixed file popup menu actions in Folders tab</li>
|
||||
<li>Fixed playlist image loading</li>
|
||||
<li>Fixed blurry album art in Android 13</li>
|
||||
<li>Minor bug fixes and improvements</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>July 10, 2022</h5>
|
||||
<h2>v6.0.3<span class="tag"><i>Beta</i></span></h2>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Migrated icons to Material symbols</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>June 21, 2022</h5>
|
||||
<h2>v6.0.2<span class="tag"><i>Beta</i></span></h2>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Minor bug fixes and improvements</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>June 13, 2022</h5>
|
||||
<h2>v6.0.1<span class="tag"><i>Beta</i></span></h2>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Fixed ChromeCast crash</li>
|
||||
<li>Fixed Slider crashes</li>
|
||||
<li>Fixed storage related crashes on Android 10</li>
|
||||
<li>Fixed CrossFade not working Fade Audio is not working</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>June 7, 2022</h5>
|
||||
<h2>v6.0.0<span class="tag"><i>Beta</i></span></h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>Better Cast</li>
|
||||
<li>Mini player in settings screen</li>
|
||||
<li>Changed Seekbar with Sliders</li>
|
||||
<li>Added NavigationRailView for Landscape</li>
|
||||
<li>Show remaining time in Sleep timer dialog</li>
|
||||
</ul>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Fixed Top/Recent Artists/Albums not updating (Wrong sort order)</li>
|
||||
<li>Fixed all Blacklist related crashes</li>
|
||||
<li>Fix restart button not working in crash activity</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>May 25, 2022</h5>
|
||||
<h2>v5.8.5</h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>Display song images along in the artist and album details pages</li>
|
||||
<li>Removed the Internet permissions and all the associated features</li>
|
||||
</ul>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Fixed crashing occurs during changing screen orientation</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>May 13, 2022</h5>
|
||||
<h2>v5.8.4</h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>Added a toggle to enable/disable swipe down to dismiss mini player</li>
|
||||
</ul>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Fixed Playback speed and pitch not working when CrossFade is enabled</li>
|
||||
<li>Fixed crash when adding folders to blacklist</li>
|
||||
<li>Fix bugs in MD3 theme</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>May 07, 2022</h5>
|
||||
<h2>v5.8.3</h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>Added a new MD3 now playing theme</li>
|
||||
<li>Swipe down to dismiss Mini player</li>
|
||||
<li>Add support for Just Black with Material You</li>
|
||||
</ul>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Fixed sharing of files from SD Card</li>
|
||||
<li>Fix ChromeCast crash and bugs</li>
|
||||
<li>Fix Audio Crossfade</li>
|
||||
<li>Tried to fix incorrect song data in notification</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>April 8, 2022</h5>
|
||||
<h2>v5.8.0</h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>Updated translations</li>
|
||||
</ul>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Fixed Classic Notification crash</li>
|
||||
<li>Fixed crash when clicking on Playlist in the Search Tab</li>
|
||||
<li>Fixed settings change not reflecting immediately</li>
|
||||
<li>Fixed shuffle</li>
|
||||
<li>Fixed Song duration not visible in Card & Blur card themes</li>
|
||||
<li>Fixed some Album skip styles</li>
|
||||
<li>Minor bug fixes & UI improvements</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>March 13, 2022</h5>
|
||||
<h2>v5.7.3</h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>Added adaptive color in Material now playing theme</li>
|
||||
<li>Added an option to share crash report</li>
|
||||
<li>Added options to clear, pause history</li>
|
||||
</ul>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Adapt Wallpaper accent for better readability</li>
|
||||
<li>Optimized search</li>
|
||||
<li>Made sleep timer precise</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>February 13, 2022</h5>
|
||||
<h2>v5.7.2<span class="tag"><i>Beta</i></span></h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>Animated splash screen on Android 12 devices</li>
|
||||
</ul>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Fixed crash when removing song from Playing queue</li>
|
||||
<li>Fixed lyrics editing crash</li>
|
||||
<li>Fixed shuffle button not working</li>
|
||||
<li>Fixed crash on song deletion</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>February 1, 2022</h5>
|
||||
<h2>v5.7.1<span class="tag"><i>Beta</i></span></h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>Added option to disable changing song by swiping anywhere on the now playing screen</li>
|
||||
</ul>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Fixed Playlist save on A11+</li>
|
||||
<li>Fixed Just Black theme</li>
|
||||
<li>Fixed some UI issues</li>
|
||||
</ul>
|
||||
<h3>Improved</h3>
|
||||
<ul>
|
||||
<li>Improvements to search</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>January 25, 2022</h5>
|
||||
<h2>v5.7.0<span class="tag"><i>Beta</i></span></h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>Added Android Auto</li>
|
||||
<li>Added accent color extraction on Android 8.1+ devices</li>
|
||||
<li>Added new Circle widget</li>
|
||||
<li>Added Collapsing appbar to library tabs with an option to switch back to simple appbar
|
||||
</li>
|
||||
<li>Added Search tab</li>
|
||||
<li>Added option to use circular play button</li>
|
||||
<li>Added lyrics editing on A11+ devices again</li>
|
||||
<li>Added Long Press to forward, rewind current song</li>
|
||||
<li>Added ability to set Playback speed and pitch</li>
|
||||
<li>Added option to show lyrics over Cover</li>
|
||||
<li>Added option to keep screen on when showing lyrics</li>
|
||||
<li>Added option to switch to Manrope font</li>
|
||||
</ul>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Fixed Gapless Playback</li>
|
||||
<li>Fixed Shuffle FAB going behind Mini Player</li>
|
||||
<li>Fixed crashes on Pre-marshmallow devices</li>
|
||||
<li>Blacklisted songs can't be played after opening from outside the app</li>
|
||||
<li>Fixed various small bugs and some minor improvements</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>January 1, 2021</h5>
|
||||
<h2>v5.6.1</h2>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Fixed artist covers not updating and showing album cover images.</li>
|
||||
<li>Fixed FAB's not visible (Shuffle, Save, etc.)</li>
|
||||
<li>Fixed a crash when a Song is deleted in Artist Details</li>
|
||||
<li>Fixed Snowfall effect</li>
|
||||
<li>Fixed empty notification when queue is cleared</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>December 25, 2021</h5>
|
||||
<h2>v5.6.0<span class="tag"><i>Beta</i></span></h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>Added Artwork editing for songs</li>
|
||||
<li>Circle theme has album art now</li>
|
||||
<li>Upgraded tag editor library, we should support reading tags of more formats now e.g.
|
||||
opus.
|
||||
</li>
|
||||
<li>Added Snowfall effect</li>
|
||||
<li>Crossfade effect for background when Song is changed for Blur, Blur card themes</li>
|
||||
<li>Album art is hidden when Show lyrics is enabled</li>
|
||||
<li>Added fastscroll in Playlists tab</li>
|
||||
<li>Added Chooser to choose what to restore</li>
|
||||
<li>Hide BottomNavigation when only one tab is selected in Library Categories(This was
|
||||
already there but when changing screens it was getting visible)
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Fixed Ringtone crash</li>
|
||||
<li>Fixed blank album cover bug</li>
|
||||
<li>Fixed bottom navigation visible in Playing Queue</li>
|
||||
<li>Fixed lockscreen dragging glitch</li>
|
||||
<li>Fixed incorrect colors when no cover art is available</li>
|
||||
<li>Fixed favorite not updating when song is changed</li>
|
||||
<li>Fixed playlist not getting created & playlist creation crash with same name</li>
|
||||
<li>Fixed a bug in "Plain" Now playing theme where onClick event is consumed by the views
|
||||
behind the bottom sheet
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>December 6, 2021</h5>
|
||||
<h2>v5.4.2<span class="tag"><i>Beta</i></span></h2>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Bug Fixes</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>December 1, 2021</h5>
|
||||
<h2>v5.4.1<span class="tag"><i>Beta</i></span></h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>Added file selection from system file picker for restore</li>
|
||||
<li>Added Grid size selection for Playlists</li>
|
||||
</ul>
|
||||
<h3>Improved</h3>
|
||||
<ul>
|
||||
<li>Enable Material You by default on Android 12</li>
|
||||
<li>Reworked Grid Style saving</li>
|
||||
<li>Improved Playlist preview images</li>
|
||||
<li>UI improvements</li>
|
||||
</ul>
|
||||
<h3>Improved</h3>
|
||||
<ul>
|
||||
<li>Fixed File deletion on Android 10</li>
|
||||
<li>Fixed Sleep Timer crash</li>
|
||||
<li>Fixed Bottom Toolbar not clickable in now playing when Shuffle is clicked</li>
|
||||
<li>Fixed Album Artist sort order</li>
|
||||
<li>Fixed a crash when clicking the "Clear All" button in the Blacklist folder selection
|
||||
</li>
|
||||
<li>Fixed Continuous crashes on A12 when the song ends</li>
|
||||
<li>Fixed New Music Mix multiple clicks crash</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>November 22, 2021</h5>
|
||||
<h2>v5.4.0<span class="tag"><i>Beta</i></span></h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li><b>Material You</b></li>
|
||||
<li>Going Edge-to-Edge</li>
|
||||
<li>Added Backup & restore</li>
|
||||
</ul>
|
||||
<h3>Improved</h3>
|
||||
<ul>
|
||||
<li>Improved Animations</li>
|
||||
<li>Improved Crossfade</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>September 06, 2021</h5>
|
||||
<h2>v5.0.0</h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>Added ability to Remember last tab</li>
|
||||
<li>Added Whitelisting songs</li>
|
||||
<li>You can now browse SDCard from Folders Tab</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>August 22, 2021</h5>
|
||||
<h2>v4.4.0<span class="tag"><i>Beta</i></span></h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>Added Crossfade</li>
|
||||
<li>Multi-select in Album and Artist Details</li>
|
||||
<li>Albums now show Album Artists instead of artist of first song</li>
|
||||
</ul>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Fixed Playlist Preview Images</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>August 11, 2021</h5>
|
||||
<h2>v4.2.30<span class="tag"><i>Beta</i></span></h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>Revamped Playlist Tab</li>
|
||||
<li>Revamped Genres Tab</li>
|
||||
<li>Added support for embedded Synced Lyrics</li>
|
||||
<li>Added animated icons</li>
|
||||
</ul>
|
||||
<h3>Fixed</h3>
|
||||
<ul>
|
||||
<li>Fixed Language Switching</li>
|
||||
<li>Fixed some reported bugs</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>July 18, 2021</h5>
|
||||
<h2>v4.2.020<span class="tag"><i>Beta</i></span></h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>Added ChromeCast Support</li>
|
||||
<li>Added Lyrics Editor for Normal and Synced Lyrics</li>
|
||||
<li>Added Ripple Animation for Color Theme</li>
|
||||
<li>Added Drag to seek in Tiny Theme</li>
|
||||
<li>Added Playlist Order</li>
|
||||
<li>Added Search Filters</li>
|
||||
<li>Added Audio Fade</li>
|
||||
<li>Synced Lyrics in all Themes</li>
|
||||
<li>Swipe anywhere to change song</li>
|
||||
</ul>
|
||||
<h3>Improved</h3>
|
||||
<ul>
|
||||
<li>Fixed Navigate by Album Artist</li>
|
||||
<li>Changed New Music Mix Actions</li>
|
||||
<li>Improved Animations</li>
|
||||
<li>And some minor bug fixes and improvements</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>October 12, 2020</h5>
|
||||
<h2>v4.0.10</h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>Re-built from scratch using MVVM Architecture and JetPack Components</li>
|
||||
<li>New Material Design icon</li>
|
||||
<li>Implemented a custom database for playlists</li>
|
||||
<li>Added new Material Design motions</li>
|
||||
<li>Bug fixes & performance improvements</li>
|
||||
<li>Revamped Home tab UI</li>
|
||||
<li>Android 11 support</li>
|
||||
<li>And more!</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>April 30, 2020</h5>
|
||||
<h2>v3.5.110<span class="tag"><i>Beta</i></span></h2>
|
||||
<h3>What's New</h3>
|
||||
<ul>
|
||||
<li>Changed profile form image to icon</li>
|
||||
<li>New what's new screen</li>
|
||||
<li>Added In-App language changer, where you can select language</li>
|
||||
</ul>
|
||||
<h3>Improved</h3>
|
||||
<ul>
|
||||
<li>Improved loading of Songs, Albums, Artists, Genres, Playlists</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 17 KiB |
74
app/src/main/java/code/name/monkey/retromusic/App.kt
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic
|
||||
|
||||
import android.app.Application
|
||||
import androidx.preference.PreferenceManager
|
||||
import cat.ereza.customactivityoncrash.config.CaocConfig
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.activities.ErrorActivity
|
||||
import code.name.monkey.retromusic.activities.MainActivity
|
||||
import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager
|
||||
import code.name.monkey.retromusic.helper.WallpaperAccentManager
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.core.context.startKoin
|
||||
|
||||
class App : Application() {
|
||||
|
||||
private val wallpaperAccentManager = WallpaperAccentManager(this)
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
instance = this
|
||||
|
||||
startKoin {
|
||||
androidContext(this@App)
|
||||
modules(appModules)
|
||||
}
|
||||
// default theme
|
||||
if (!ThemeStore.isConfigured(this, 3)) {
|
||||
ThemeStore.editTheme(this)
|
||||
.accentColorRes(code.name.monkey.appthemehelper.R.color.md_deep_purple_A200)
|
||||
.coloredNavigationBar(true)
|
||||
.commit()
|
||||
}
|
||||
wallpaperAccentManager.init()
|
||||
|
||||
if (VersionUtils.hasNougatMR())
|
||||
DynamicShortcutManager(this).initDynamicShortcuts()
|
||||
|
||||
// setting Error activity
|
||||
CaocConfig.Builder.create().errorActivity(ErrorActivity::class.java)
|
||||
.restartActivity(MainActivity::class.java).apply()
|
||||
|
||||
// Set Default values for now playing preferences
|
||||
// This will reduce startup time for now playing settings fragment as Preference listener of AbsSlidingMusicPanelActivity won't be called
|
||||
PreferenceManager.setDefaultValues(this, R.xml.pref_now_playing_screen, false)
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
super.onTerminate()
|
||||
wallpaperAccentManager.release()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var instance: App? = null
|
||||
|
||||
fun getContext(): App {
|
||||
return instance!!
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,59 +1,62 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.muntashirakon.music
|
||||
package code.name.monkey.retromusic
|
||||
|
||||
import android.provider.BaseColumns
|
||||
import android.provider.MediaStore
|
||||
|
||||
object Constants {
|
||||
const val RATE_ON_GOOGLE_PLAY =
|
||||
"https://play.google.com/store/apps/details?id=code.name.monkey.retromusic"
|
||||
const val TRANSLATE = "https://github.com/h4h13/RetroMusicPlayer"
|
||||
const val TRANSLATE = "https://crowdin.com/project/retromusicplayer"
|
||||
const val GITHUB_PROJECT = "https://github.com/MuntashirAkon/Metro"
|
||||
const val TELEGRAM_CHANGE_LOG = "https://t.me/retromusiclog"
|
||||
const val TELEGRAM_CHANGE_LOG = "https://t.me/AppManagerChannel"
|
||||
const val USER_PROFILE = "profile.jpg"
|
||||
const val USER_BANNER = "banner.jpg"
|
||||
const val APP_INSTAGRAM_LINK = "https://www.instagram.com/retromusicapp/"
|
||||
const val APP_TELEGRAM_LINK = "https://t.me/retromusicapp/"
|
||||
const val APP_TWITTER_LINK = "https://twitter.com/retromusicapp"
|
||||
const val FAQ_LINK = "https://github.com/h4h13/RetroMusicPlayer/blob/master/FAQ.md"
|
||||
const val PINTEREST = "https://in.pinterest.com/retromusicapp/"
|
||||
const val BASE_URL = "https://ws.audioscrobbler.com/2.0/"
|
||||
const val FAQ_LINK = "https://github.com/MuntashirAkon/Metro/blob/master/FAQ.md"
|
||||
|
||||
const val IS_MUSIC =
|
||||
MediaStore.Audio.AudioColumns.IS_MUSIC + "=1" + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"
|
||||
|
||||
const val DATA = "_data"
|
||||
|
||||
@Suppress("Deprecation")
|
||||
val baseProjection = arrayOf(
|
||||
BaseColumns._ID, // 0
|
||||
MediaStore.Audio.AudioColumns.TITLE, // 1
|
||||
MediaStore.Audio.AudioColumns.TRACK, // 2
|
||||
MediaStore.Audio.AudioColumns.YEAR, // 3
|
||||
MediaStore.Audio.AudioColumns.DURATION, // 4
|
||||
MediaStore.Audio.AudioColumns.DATA, // 5
|
||||
DATA, // 5
|
||||
MediaStore.Audio.AudioColumns.DATE_MODIFIED, // 6
|
||||
MediaStore.Audio.AudioColumns.ALBUM_ID, // 7
|
||||
MediaStore.Audio.AudioColumns.ALBUM, // 8
|
||||
MediaStore.Audio.AudioColumns.ARTIST_ID, // 9
|
||||
MediaStore.Audio.AudioColumns.ARTIST,// 10
|
||||
MediaStore.Audio.AudioColumns.COMPOSER// 11
|
||||
MediaStore.Audio.AudioColumns.ARTIST, // 10
|
||||
MediaStore.Audio.AudioColumns.COMPOSER, // 11
|
||||
ALBUM_ARTIST // 12
|
||||
)
|
||||
const val NUMBER_OF_TOP_TRACKS = 99
|
||||
}
|
||||
|
||||
const val EXTRA_PLAYLIST_TYPE = "type"
|
||||
const val EXTRA_GENRE = "extra_genre"
|
||||
const val EXTRA_PLAYLIST = "extra_playlist"
|
||||
const val EXTRA_PLAYLIST_ID = "extra_playlist_id"
|
||||
const val EXTRA_ALBUM_ID = "extra_album_id"
|
||||
const val EXTRA_ARTIST_ID = "extra_artist_id"
|
||||
const val EXTRA_SONG = "extra_songs"
|
||||
const val EXTRA_PLAYLIST = "extra_list"
|
||||
const val EXTRA_PLAYLISTS = "extra_playlists"
|
||||
const val LIBRARY_CATEGORIES = "library_categories"
|
||||
const val EXTRA_SONG_INFO = "extra_song_info"
|
||||
const val DESATURATED_COLOR = "desaturated_color"
|
||||
|
@ -64,29 +67,27 @@ const val NOW_PLAYING_SCREEN_ID = "now_playing_screen_id"
|
|||
const val CAROUSEL_EFFECT = "carousel_effect"
|
||||
const val COLORED_NOTIFICATION = "colored_notification"
|
||||
const val CLASSIC_NOTIFICATION = "classic_notification"
|
||||
const val GAPLESS_PLAYBACK = "gapless_playback"
|
||||
const val ALBUM_ART_ON_LOCKSCREEN = "album_art_on_lockscreen"
|
||||
const val GAP_LESS_PLAYBACK = "gapless_playback"
|
||||
const val ALBUM_ART_ON_LOCK_SCREEN = "album_art_on_lock_screen"
|
||||
const val BLURRED_ALBUM_ART = "blurred_album_art"
|
||||
const val NEW_BLUR_AMOUNT = "new_blur_amount"
|
||||
const val TOGGLE_HEADSET = "toggle_headset"
|
||||
const val GENERAL_THEME = "general_theme"
|
||||
const val ACCENT_COLOR = "accent_color"
|
||||
const val SHOULD_COLOR_APP_SHORTCUTS = "should_color_app_shortcuts"
|
||||
const val CIRCULAR_ALBUM_ART = "circular_album_art"
|
||||
const val USER_NAME = "user_name"
|
||||
const val TOGGLE_FULL_SCREEN = "toggle_full_screen"
|
||||
const val TOGGLE_VOLUME = "toggle_volume"
|
||||
const val ROUND_CORNERS = "corner_window"
|
||||
const val TOGGLE_GENRE = "toggle_genre"
|
||||
const val PROFILE_IMAGE_PATH = "profile_image_path"
|
||||
const val BANNER_IMAGE_PATH = "banner_image_path"
|
||||
const val ADAPTIVE_COLOR_APP = "adaptive_color_app"
|
||||
const val TOGGLE_SEPARATE_LINE = "toggle_separate_line"
|
||||
const val HOME_ARTIST_GRID_STYLE = "home_artist_grid_style"
|
||||
const val HOME_ALBUM_GRID_STYLE = "home_album_grid_style"
|
||||
const val TOGGLE_ADD_CONTROLS = "toggle_add_controls"
|
||||
const val ALBUM_COVER_STYLE = "album_cover_style_id"
|
||||
const val ALBUM_COVER_TRANSFORM = "album_cover_transform"
|
||||
const val TAB_TEXT_MODE = "tab_text_mode"
|
||||
const val LANGUAGE_NAME = "language_name"
|
||||
const val DIALOG_CORNER = "dialog_corner"
|
||||
const val LOCALE_AUTO_STORE_ENABLED = "locale_auto_store_enabled"
|
||||
const val SLEEP_TIMER_FINISH_SONG = "sleep_timer_finish_song"
|
||||
const val ALBUM_GRID_STYLE = "album_grid_style_home"
|
||||
const val ARTIST_GRID_STYLE = "artist_grid_style_home"
|
||||
|
@ -94,12 +95,12 @@ const val SAF_SDCARD_URI = "saf_sdcard_uri"
|
|||
const val SONG_SORT_ORDER = "song_sort_order"
|
||||
const val SONG_GRID_SIZE = "song_grid_size"
|
||||
const val GENRE_SORT_ORDER = "genre_sort_order"
|
||||
const val LAST_PAGE = "last_start_page"
|
||||
const val BLUETOOTH_PLAYBACK = "bluetooth_playback"
|
||||
const val INITIALIZED_BLACKLIST = "initialized_blacklist"
|
||||
const val ARTIST_SORT_ORDER = "artist_sort_order"
|
||||
const val ARTIST_ALBUM_SORT_ORDER = "artist_album_sort_order"
|
||||
const val ALBUM_SORT_ORDER = "album_sort_order"
|
||||
const val PLAYLIST_SORT_ORDER = "playlist_sort_order"
|
||||
const val ALBUM_SONG_SORT_ORDER = "album_song_sort_order"
|
||||
const val ARTIST_SONG_SORT_ORDER = "artist_song_sort_order"
|
||||
const val ALBUM_GRID_SIZE = "album_grid_size"
|
||||
|
@ -107,22 +108,47 @@ const val ALBUM_GRID_SIZE_LAND = "album_grid_size_land"
|
|||
const val SONG_GRID_SIZE_LAND = "song_grid_size_land"
|
||||
const val ARTIST_GRID_SIZE = "artist_grid_size"
|
||||
const val ARTIST_GRID_SIZE_LAND = "artist_grid_size_land"
|
||||
const val PLAYLIST_GRID_SIZE = "playlist_grid_size"
|
||||
const val PLAYLIST_GRID_SIZE_LAND = "playlist_grid_size_land"
|
||||
const val COLORED_APP_SHORTCUTS = "colored_app_shortcuts"
|
||||
const val AUDIO_DUCKING = "audio_ducking"
|
||||
const val LAST_ADDED_CUTOFF = "last_added_interval"
|
||||
const val LAST_SLEEP_TIMER_VALUE = "last_sleep_timer_value"
|
||||
const val NEXT_SLEEP_TIMER_ELAPSED_REALTIME = "next_sleep_timer_elapsed_real_time"
|
||||
const val IGNORE_MEDIA_STORE_ARTWORK = "ignore_media_store_artwork"
|
||||
const val LAST_CHANGELOG_VERSION = "last_changelog_version"
|
||||
const val AUTO_DOWNLOAD_IMAGES_POLICY = "auto_download_images_policy"
|
||||
const val START_DIRECTORY = "start_directory"
|
||||
const val RECENTLY_PLAYED_CUTOFF = "recently_played_interval"
|
||||
const val LOCK_SCREEN = "lock_screen"
|
||||
const val ALBUM_ARTISTS_ONLY = "album_artists_only"
|
||||
const val ALBUM_ARTIST = "album_artist"
|
||||
const val ALBUM_DETAIL_SONG_SORT_ORDER = "album_detail_song_sort_order"
|
||||
const val ARTIST_DETAIL_SONG_SORT_ORDER = "artist_detail_song_sort_order"
|
||||
const val LYRICS_OPTIONS = "lyrics_tab_position"
|
||||
const val CHOOSE_EQUALIZER = "choose_equalizer"
|
||||
const val TOGGLE_SHUFFLE = "toggle_shuffle"
|
||||
const val EQUALIZER = "equalizer"
|
||||
const val SONG_GRID_STYLE = "song_grid_style"
|
||||
const val PAUSE_ON_ZERO_VOLUME = "pause_on_zero_volume"
|
||||
const val FILTER_SONG = "filter_song"
|
||||
const val EXPAND_NOW_PLAYING_PANEL = "expand_now_playing_panel"
|
||||
const val EXTRA_ARTIST_NAME = "extra_artist_name"
|
||||
const val TOGGLE_SUGGESTIONS = "toggle_suggestions"
|
||||
const val AUDIO_FADE_DURATION = "audio_fade_duration"
|
||||
const val CROSS_FADE_DURATION = "cross_fade_duration"
|
||||
const val SHOW_LYRICS = "show_lyrics"
|
||||
const val REMEMBER_LAST_TAB = "remember_last_tab"
|
||||
const val LAST_USED_TAB = "last_used_tab"
|
||||
const val WHITELIST_MUSIC = "whitelist_music"
|
||||
const val MATERIAL_YOU = "material_you"
|
||||
const val SNOWFALL = "snowfall"
|
||||
const val LYRICS_TYPE = "lyrics_type"
|
||||
const val PLAYBACK_SPEED = "playback_speed"
|
||||
const val PLAYBACK_PITCH = "playback_pitch"
|
||||
const val CUSTOM_FONT = "custom_font"
|
||||
const val APPBAR_MODE = "appbar_mode"
|
||||
const val WALLPAPER_ACCENT = "wallpaper_accent"
|
||||
const val SCREEN_ON_LYRICS = "screen_on_lyrics"
|
||||
const val CIRCLE_PLAY_BUTTON = "circle_play_button"
|
||||
const val SWIPE_ANYWHERE_NOW_PLAYING = "swipe_anywhere_now_playing"
|
||||
const val PAUSE_HISTORY = "pause_history"
|
||||
const val MANAGE_AUDIO_FOCUS = "manage_audio_focus"
|
||||
const val SWIPE_DOWN_DISMISS = "swipe_to_dismiss"
|
42
app/src/main/java/code/name/monkey/retromusic/HomeSection.kt
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic
|
||||
|
||||
import androidx.annotation.IntDef
|
||||
|
||||
@IntDef(
|
||||
RECENT_ALBUMS,
|
||||
TOP_ALBUMS,
|
||||
RECENT_ARTISTS,
|
||||
TOP_ARTISTS,
|
||||
SUGGESTIONS,
|
||||
FAVOURITES,
|
||||
GENRES,
|
||||
PLAYLISTS
|
||||
)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class HomeSection
|
||||
|
||||
const val RECENT_ALBUMS = 3
|
||||
const val TOP_ALBUMS = 1
|
||||
const val RECENT_ARTISTS = 2
|
||||
const val TOP_ARTISTS = 0
|
||||
const val SUGGESTIONS = 5
|
||||
const val FAVOURITES = 4
|
||||
const val GENRES = 6
|
||||
const val PLAYLISTS = 7
|
||||
const val HISTORY_PLAYLIST = 8
|
||||
const val LAST_ADDED_PLAYLIST = 9
|
||||
const val TOP_PLAYED_PLAYLIST = 10
|
156
app/src/main/java/code/name/monkey/retromusic/MainModule.kt
Normal file
|
@ -0,0 +1,156 @@
|
|||
package code.name.monkey.retromusic
|
||||
|
||||
import androidx.room.Room
|
||||
import code.name.monkey.retromusic.auto.AutoMusicProvider
|
||||
import code.name.monkey.retromusic.db.MIGRATION_23_24
|
||||
import code.name.monkey.retromusic.db.RetroDatabase
|
||||
import code.name.monkey.retromusic.fragments.LibraryViewModel
|
||||
import code.name.monkey.retromusic.fragments.albums.AlbumDetailsViewModel
|
||||
import code.name.monkey.retromusic.fragments.artists.ArtistDetailsViewModel
|
||||
import code.name.monkey.retromusic.fragments.genres.GenreDetailsViewModel
|
||||
import code.name.monkey.retromusic.fragments.playlists.PlaylistDetailsViewModel
|
||||
import code.name.monkey.retromusic.model.Genre
|
||||
import code.name.monkey.retromusic.repository.*
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
|
||||
private val roomModule = module {
|
||||
|
||||
single {
|
||||
Room.databaseBuilder(androidContext(), RetroDatabase::class.java, "playlist.db")
|
||||
.addMigrations(MIGRATION_23_24)
|
||||
.build()
|
||||
}
|
||||
|
||||
factory {
|
||||
get<RetroDatabase>().playlistDao()
|
||||
}
|
||||
|
||||
factory {
|
||||
get<RetroDatabase>().playCountDao()
|
||||
}
|
||||
|
||||
factory {
|
||||
get<RetroDatabase>().historyDao()
|
||||
}
|
||||
|
||||
single {
|
||||
RealRoomRepository(get(), get(), get())
|
||||
} bind RoomRepository::class
|
||||
}
|
||||
private val autoModule = module {
|
||||
single {
|
||||
AutoMusicProvider(
|
||||
androidContext(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get()
|
||||
)
|
||||
}
|
||||
}
|
||||
private val mainModule = module {
|
||||
single {
|
||||
androidContext().contentResolver
|
||||
}
|
||||
}
|
||||
private val dataModule = module {
|
||||
single {
|
||||
RealRepository(
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
)
|
||||
} bind Repository::class
|
||||
|
||||
single {
|
||||
RealSongRepository(get())
|
||||
} bind SongRepository::class
|
||||
|
||||
single {
|
||||
RealGenreRepository(get(), get())
|
||||
} bind GenreRepository::class
|
||||
|
||||
single {
|
||||
RealAlbumRepository(get())
|
||||
} bind AlbumRepository::class
|
||||
|
||||
single {
|
||||
RealArtistRepository(get(), get())
|
||||
} bind ArtistRepository::class
|
||||
|
||||
single {
|
||||
RealPlaylistRepository(get())
|
||||
} bind PlaylistRepository::class
|
||||
|
||||
single {
|
||||
RealTopPlayedRepository(get(), get(), get(), get())
|
||||
} bind TopPlayedRepository::class
|
||||
|
||||
single {
|
||||
RealLastAddedRepository(
|
||||
get(),
|
||||
get(),
|
||||
get()
|
||||
)
|
||||
} bind LastAddedRepository::class
|
||||
|
||||
single {
|
||||
RealSearchRepository(
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val viewModules = module {
|
||||
|
||||
viewModel {
|
||||
LibraryViewModel(get())
|
||||
}
|
||||
|
||||
viewModel { (albumId: Long) ->
|
||||
AlbumDetailsViewModel(
|
||||
get(),
|
||||
albumId
|
||||
)
|
||||
}
|
||||
|
||||
viewModel { (artistId: Long?, artistName: String?) ->
|
||||
ArtistDetailsViewModel(
|
||||
get(),
|
||||
artistId,
|
||||
artistName
|
||||
)
|
||||
}
|
||||
|
||||
viewModel { (playlistId: Long) ->
|
||||
PlaylistDetailsViewModel(
|
||||
get(),
|
||||
playlistId
|
||||
)
|
||||
}
|
||||
|
||||
viewModel { (genre: Genre) ->
|
||||
GenreDetailsViewModel(
|
||||
get(),
|
||||
genre
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val appModules = listOf(mainModule, dataModule, autoModule, viewModules, roomModule)
|
|
@ -0,0 +1,258 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
|
||||
import code.name.monkey.retromusic.databinding.ActivityDriveModeBinding
|
||||
import code.name.monkey.retromusic.db.toSongEntity
|
||||
import code.name.monkey.retromusic.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
|
||||
import code.name.monkey.retromusic.glide.BlurTransformation
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback
|
||||
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.repository.RealRepository
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import com.bumptech.glide.Glide
|
||||
import com.google.android.material.slider.Slider
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
|
||||
/**
|
||||
* Created by hemanths on 2020-02-02.
|
||||
*/
|
||||
|
||||
class DriveModeActivity : AbsMusicServiceActivity(), Callback {
|
||||
|
||||
private lateinit var binding: ActivityDriveModeBinding
|
||||
private var lastPlaybackControlsColor: Int = Color.GRAY
|
||||
private var lastDisabledPlaybackControlsColor: Int = Color.GRAY
|
||||
private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper
|
||||
private val repository: RealRepository by inject()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityDriveModeBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
setUpMusicControllers()
|
||||
|
||||
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this)
|
||||
lastPlaybackControlsColor = accentColor()
|
||||
binding.close.setOnClickListener {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
binding.repeatButton.drawAboveSystemBars()
|
||||
}
|
||||
|
||||
private fun setUpMusicControllers() {
|
||||
setUpPlayPauseFab()
|
||||
setUpPrevNext()
|
||||
setUpRepeatButton()
|
||||
setUpShuffleButton()
|
||||
setUpProgressSlider()
|
||||
setupFavouriteToggle()
|
||||
}
|
||||
|
||||
private fun setupFavouriteToggle() {
|
||||
binding.songFavourite.setOnClickListener {
|
||||
toggleFavorite(MusicPlayerRemote.currentSong)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleFavorite(song: Song) {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val playlist = repository.favoritePlaylist()
|
||||
val songEntity = song.toSongEntity(playlist.playListId)
|
||||
val isFavorite = repository.isSongFavorite(song.id)
|
||||
if (isFavorite) {
|
||||
repository.removeSongFromPlaylist(songEntity)
|
||||
} else {
|
||||
repository.insertSongs(listOf(song.toSongEntity(playlist.playListId)))
|
||||
}
|
||||
sendBroadcast(Intent(MusicService.FAVORITE_STATE_CHANGED))
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateFavorite() {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val isFavorite: Boolean =
|
||||
repository.isSongFavorite(MusicPlayerRemote.currentSong.id)
|
||||
withContext(Dispatchers.Main) {
|
||||
binding.songFavourite.setImageResource(if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUpProgressSlider() {
|
||||
binding.progressSlider.addOnChangeListener { _: Slider, progress: Float, fromUser: Boolean ->
|
||||
if (fromUser) {
|
||||
MusicPlayerRemote.seekTo(progress.toInt())
|
||||
onUpdateProgressViews(
|
||||
MusicPlayerRemote.songProgressMillis,
|
||||
MusicPlayerRemote.songDurationMillis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
progressViewUpdateHelper.stop()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
progressViewUpdateHelper.start()
|
||||
}
|
||||
|
||||
private fun setUpPrevNext() {
|
||||
binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() }
|
||||
binding.previousButton.setOnClickListener { MusicPlayerRemote.back() }
|
||||
}
|
||||
|
||||
private fun setUpShuffleButton() {
|
||||
binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() }
|
||||
}
|
||||
|
||||
private fun setUpRepeatButton() {
|
||||
binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() }
|
||||
}
|
||||
|
||||
private fun setUpPlayPauseFab() {
|
||||
binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler())
|
||||
}
|
||||
|
||||
override fun onRepeatModeChanged() {
|
||||
super.onRepeatModeChanged()
|
||||
updateRepeatState()
|
||||
}
|
||||
|
||||
override fun onShuffleModeChanged() {
|
||||
super.onShuffleModeChanged()
|
||||
updateShuffleState()
|
||||
}
|
||||
|
||||
override fun onPlayStateChanged() {
|
||||
super.onPlayStateChanged()
|
||||
updatePlayPauseDrawableState()
|
||||
}
|
||||
|
||||
override fun onServiceConnected() {
|
||||
super.onServiceConnected()
|
||||
updatePlayPauseDrawableState()
|
||||
updateSong()
|
||||
updateRepeatState()
|
||||
updateShuffleState()
|
||||
updateFavorite()
|
||||
}
|
||||
|
||||
private fun updatePlayPauseDrawableState() {
|
||||
if (MusicPlayerRemote.isPlaying) {
|
||||
binding.playPauseButton.setImageResource(R.drawable.ic_pause)
|
||||
} else {
|
||||
binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateShuffleState() {
|
||||
when (MusicPlayerRemote.shuffleMode) {
|
||||
MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter(
|
||||
lastPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
|
||||
else -> binding.shuffleButton.setColorFilter(
|
||||
lastDisabledPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateRepeatState() {
|
||||
when (MusicPlayerRemote.repeatMode) {
|
||||
MusicService.REPEAT_MODE_NONE -> {
|
||||
binding.repeatButton.setImageResource(R.drawable.ic_repeat)
|
||||
binding.repeatButton.setColorFilter(
|
||||
lastDisabledPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
|
||||
MusicService.REPEAT_MODE_ALL -> {
|
||||
binding.repeatButton.setImageResource(R.drawable.ic_repeat)
|
||||
binding.repeatButton.setColorFilter(
|
||||
lastPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
|
||||
MusicService.REPEAT_MODE_THIS -> {
|
||||
binding.repeatButton.setImageResource(R.drawable.ic_repeat_one)
|
||||
binding.repeatButton.setColorFilter(
|
||||
lastPlaybackControlsColor,
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPlayingMetaChanged() {
|
||||
super.onPlayingMetaChanged()
|
||||
updateSong()
|
||||
updateFavorite()
|
||||
}
|
||||
|
||||
override fun onFavoriteStateChanged() {
|
||||
super.onFavoriteStateChanged()
|
||||
updateFavorite()
|
||||
}
|
||||
|
||||
private fun updateSong() {
|
||||
val song = MusicPlayerRemote.currentSong
|
||||
|
||||
binding.songTitle.text = song.title
|
||||
binding.songText.text = song.artistName
|
||||
|
||||
Glide.with(this)
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.songCoverOptions(song)
|
||||
.transform(BlurTransformation.Builder(this).build())
|
||||
.into(binding.image)
|
||||
}
|
||||
|
||||
override fun onUpdateProgressViews(progress: Int, total: Int) {
|
||||
binding.progressSlider.run {
|
||||
valueTo = total.toFloat()
|
||||
value = progress.toFloat().coerceIn(valueFrom, valueTo)
|
||||
}
|
||||
|
||||
binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
|
||||
binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import cat.ereza.customactivityoncrash.CustomActivityOnCrash
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.util.FileUtils.createFile
|
||||
import code.name.monkey.retromusic.util.Share.shareFile
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class ErrorActivity : AppCompatActivity() {
|
||||
private val dayFormat: DateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
||||
private val reportPrefix = "bug_report-"
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(cat.ereza.customactivityoncrash.R.layout.customactivityoncrash_default_error_activity)
|
||||
|
||||
val restartButton =
|
||||
findViewById<Button>(cat.ereza.customactivityoncrash.R.id.customactivityoncrash_error_activity_restart_button)
|
||||
|
||||
val config = CustomActivityOnCrash.getConfigFromIntent(intent)
|
||||
if (config == null) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
restartButton.setText(cat.ereza.customactivityoncrash.R.string.customactivityoncrash_error_activity_restart_app)
|
||||
restartButton.setOnClickListener {
|
||||
CustomActivityOnCrash.restartApplication(
|
||||
this@ErrorActivity,
|
||||
config
|
||||
)
|
||||
}
|
||||
val moreInfoButton =
|
||||
findViewById<Button>(cat.ereza.customactivityoncrash.R.id.customactivityoncrash_error_activity_more_info_button)
|
||||
|
||||
moreInfoButton.setOnClickListener { //We retrieve all the error data and show it
|
||||
MaterialAlertDialogBuilder(this@ErrorActivity)
|
||||
.setTitle(cat.ereza.customactivityoncrash.R.string.customactivityoncrash_error_activity_error_details_title)
|
||||
.setMessage(
|
||||
CustomActivityOnCrash.getAllErrorDetailsFromIntent(
|
||||
this@ErrorActivity,
|
||||
intent
|
||||
)
|
||||
)
|
||||
.setPositiveButton(
|
||||
cat.ereza.customactivityoncrash.R.string.customactivityoncrash_error_activity_error_details_close,
|
||||
null
|
||||
)
|
||||
.setNeutralButton(
|
||||
R.string.customactivityoncrash_error_activity_error_details_share
|
||||
) { _, _ ->
|
||||
|
||||
val bugReport = createFile(
|
||||
context = this,
|
||||
"Bug Report",
|
||||
"$reportPrefix${dayFormat.format(Date())}",
|
||||
CustomActivityOnCrash.getAllErrorDetailsFromIntent(
|
||||
this@ErrorActivity,
|
||||
intent
|
||||
), ".txt"
|
||||
)
|
||||
shareFile(this, bugReport, "text/*")
|
||||
}
|
||||
.show()
|
||||
}
|
||||
val errorActivityDrawableId = config.errorDrawable
|
||||
val errorImageView =
|
||||
findViewById<ImageView>(cat.ereza.customactivityoncrash.R.id.customactivityoncrash_error_activity_image)
|
||||
if (errorActivityDrawableId != null) {
|
||||
errorImageView.setImageResource(
|
||||
errorActivityDrawableId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil.isWindowBackgroundDark
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil.lightenColor
|
||||
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
|
||||
import code.name.monkey.retromusic.activities.base.AbsThemeActivity
|
||||
import code.name.monkey.retromusic.databinding.ActivityLicenseBinding
|
||||
import code.name.monkey.retromusic.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
|
||||
import code.name.monkey.retromusic.extensions.surfaceColor
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
/** Created by hemanths on 2019-09-27. */
|
||||
class LicenseActivity : AbsThemeActivity() {
|
||||
private lateinit var binding: ActivityLicenseBinding
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityLicenseBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
ToolbarContentTintHelper.colorBackButton(binding.toolbar)
|
||||
try {
|
||||
val buf = StringBuilder()
|
||||
val json = assets.open("license.html")
|
||||
BufferedReader(InputStreamReader(json, StandardCharsets.UTF_8)).use { br ->
|
||||
var str: String?
|
||||
while (br.readLine().also { str = it } != null) {
|
||||
buf.append(str)
|
||||
}
|
||||
}
|
||||
|
||||
// Inject color values for WebView body background and links
|
||||
val isDark = isWindowBackgroundDark(this)
|
||||
val backgroundColor = colorToCSS(
|
||||
surfaceColor(Color.parseColor(if (isDark) "#424242" else "#ffffff"))
|
||||
)
|
||||
val contentColor = colorToCSS(Color.parseColor(if (isDark) "#ffffff" else "#000000"))
|
||||
val changeLog = buf.toString()
|
||||
.replace(
|
||||
"{style-placeholder}", String.format(
|
||||
"body { background-color: %s; color: %s; }", backgroundColor, contentColor
|
||||
)
|
||||
)
|
||||
.replace("{link-color}", colorToCSS(accentColor()))
|
||||
.replace(
|
||||
"{link-color-active}",
|
||||
colorToCSS(
|
||||
lightenColor(accentColor())
|
||||
)
|
||||
)
|
||||
binding.license.loadData(changeLog, "text/html", "UTF-8")
|
||||
} catch (e: Throwable) {
|
||||
binding.license.loadData(
|
||||
"<h1>Unable to load</h1><p>" + e.localizedMessage + "</p>", "text/html", "UTF-8"
|
||||
)
|
||||
}
|
||||
binding.license.drawAboveSystemBars()
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun colorToCSS(color: Int): String {
|
||||
return String.format(
|
||||
"rgb(%d, %d, %d)",
|
||||
Color.red(color),
|
||||
Color.green(color),
|
||||
Color.blue(color)
|
||||
) // on API 29, WebView doesn't load with hex colors
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.app.KeyguardManager
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import androidx.core.content.getSystemService
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
|
||||
import code.name.monkey.retromusic.databinding.ActivityLockScreenBinding
|
||||
import code.name.monkey.retromusic.extensions.hideStatusBar
|
||||
import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto
|
||||
import code.name.monkey.retromusic.extensions.whichFragment
|
||||
import code.name.monkey.retromusic.fragments.player.lockscreen.LockScreenControlsFragment
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
|
||||
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
|
||||
import com.bumptech.glide.Glide
|
||||
import com.r0adkll.slidr.Slidr
|
||||
import com.r0adkll.slidr.model.SlidrConfig
|
||||
import com.r0adkll.slidr.model.SlidrListener
|
||||
import com.r0adkll.slidr.model.SlidrPosition
|
||||
|
||||
class LockScreenActivity : AbsMusicServiceActivity() {
|
||||
private lateinit var binding: ActivityLockScreenBinding
|
||||
private var fragment: LockScreenControlsFragment? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
lockScreenInit()
|
||||
binding = ActivityLockScreenBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
hideStatusBar()
|
||||
setTaskDescriptionColorAuto()
|
||||
|
||||
val config = SlidrConfig.Builder().listener(object : SlidrListener {
|
||||
override fun onSlideStateChanged(state: Int) {
|
||||
}
|
||||
|
||||
override fun onSlideChange(percent: Float) {
|
||||
}
|
||||
|
||||
override fun onSlideOpened() {
|
||||
}
|
||||
|
||||
override fun onSlideClosed(): Boolean {
|
||||
if (VersionUtils.hasOreo()) {
|
||||
val keyguardManager =
|
||||
getSystemService<KeyguardManager>()
|
||||
keyguardManager?.requestDismissKeyguard(this@LockScreenActivity, null)
|
||||
}
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
}).position(SlidrPosition.BOTTOM).build()
|
||||
|
||||
Slidr.attach(this, config)
|
||||
|
||||
fragment = whichFragment<LockScreenControlsFragment>(R.id.playback_controls_fragment)
|
||||
|
||||
binding.slide.apply {
|
||||
translationY = 100f
|
||||
alpha = 0f
|
||||
animate().translationY(0f).alpha(1f).setDuration(1500).start()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("Deprecation")
|
||||
private fun lockScreenInit() {
|
||||
if (VersionUtils.hasOreoMR1()) {
|
||||
setShowWhenLocked(true)
|
||||
//setTurnScreenOn(true)
|
||||
} else {
|
||||
window.addFlags(
|
||||
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
|
||||
// or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPlayingMetaChanged() {
|
||||
super.onPlayingMetaChanged()
|
||||
updateSongs()
|
||||
}
|
||||
|
||||
override fun onServiceConnected() {
|
||||
super.onServiceConnected()
|
||||
updateSongs()
|
||||
}
|
||||
|
||||
private fun updateSongs() {
|
||||
val song = MusicPlayerRemote.currentSong
|
||||
Glide.with(this)
|
||||
.asBitmapPalette()
|
||||
.songCoverOptions(song)
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.dontAnimate()
|
||||
.into(object : RetroMusicColoredTarget(binding.image) {
|
||||
override fun onColorReady(colors: MediaNotificationProcessor) {
|
||||
fragment?.setColor(colors)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.MediaStore
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.contains
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
|
||||
import code.name.monkey.retromusic.extensions.*
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.SearchQueryHelper.getSongs
|
||||
import code.name.monkey.retromusic.interfaces.IScrollHelper
|
||||
import code.name.monkey.retromusic.model.CategoryInfo
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.repository.PlaylistSongsLoader
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.logE
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.get
|
||||
|
||||
class MainActivity : AbsSlidingMusicPanelActivity() {
|
||||
companion object {
|
||||
const val TAG = "MainActivity"
|
||||
const val EXPAND_PANEL = "expand_panel"
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setTaskDescriptionColorAuto()
|
||||
hideStatusBar()
|
||||
updateTabs()
|
||||
|
||||
setupNavigationController()
|
||||
|
||||
WhatsNewFragment.showChangeLog(this)
|
||||
}
|
||||
|
||||
private fun setupNavigationController() {
|
||||
val navController = findNavController(R.id.fragment_container)
|
||||
val navInflater = navController.navInflater
|
||||
val navGraph = navInflater.inflate(R.navigation.main_graph)
|
||||
|
||||
val categoryInfo: CategoryInfo = PreferenceUtil.libraryCategory.first { it.visible }
|
||||
if (categoryInfo.visible) {
|
||||
if (!navGraph.contains(PreferenceUtil.lastTab)) PreferenceUtil.lastTab =
|
||||
categoryInfo.category.id
|
||||
navGraph.setStartDestination(
|
||||
if (PreferenceUtil.rememberLastTab) {
|
||||
PreferenceUtil.lastTab.let {
|
||||
if (it == 0) {
|
||||
categoryInfo.category.id
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
} else categoryInfo.category.id
|
||||
)
|
||||
}
|
||||
navController.graph = navGraph
|
||||
navigationView.setupWithNavController(navController)
|
||||
// Scroll Fragment to top
|
||||
navigationView.setOnItemReselectedListener {
|
||||
currentFragment(R.id.fragment_container).apply {
|
||||
if (this is IScrollHelper) {
|
||||
scrollToTop()
|
||||
}
|
||||
}
|
||||
}
|
||||
navController.addOnDestinationChangedListener { _, destination, _ ->
|
||||
if (destination.id == navGraph.startDestinationId) {
|
||||
currentFragment(R.id.fragment_container)?.enterTransition = null
|
||||
}
|
||||
when (destination.id) {
|
||||
R.id.action_home, R.id.action_song, R.id.action_album, R.id.action_artist, R.id.action_folder, R.id.action_playlist, R.id.action_genre, R.id.action_search -> {
|
||||
// Save the last tab
|
||||
if (PreferenceUtil.rememberLastTab) {
|
||||
saveTab(destination.id)
|
||||
}
|
||||
// Show Bottom Navigation Bar
|
||||
setBottomNavVisibility(visible = true, animate = true)
|
||||
}
|
||||
R.id.playing_queue_fragment -> {
|
||||
setBottomNavVisibility(visible = false, hideBottomSheet = true)
|
||||
}
|
||||
else -> setBottomNavVisibility(
|
||||
visible = false,
|
||||
animate = true
|
||||
) // Hide Bottom Navigation Bar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveTab(id: Int) {
|
||||
if (PreferenceUtil.libraryCategory.firstOrNull { it.category.id == id }?.visible == true) {
|
||||
PreferenceUtil.lastTab = id
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean =
|
||||
findNavController(R.id.fragment_container).navigateUp()
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
val expand = intent?.extra<Boolean>(EXPAND_PANEL)?.value ?: false
|
||||
if (expand && PreferenceUtil.isExpandPanel) {
|
||||
fromNotification = true
|
||||
slidingPanel.bringToFront()
|
||||
expandPanel()
|
||||
intent?.removeExtra(EXPAND_PANEL)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServiceConnected() {
|
||||
super.onServiceConnected()
|
||||
intent ?: return
|
||||
handlePlaybackIntent(intent)
|
||||
}
|
||||
|
||||
private fun handlePlaybackIntent(intent: Intent) {
|
||||
lifecycleScope.launch(IO) {
|
||||
val uri: Uri? = intent.data
|
||||
val mimeType: String? = intent.type
|
||||
var handled = false
|
||||
if (intent.action != null &&
|
||||
intent.action == MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH
|
||||
) {
|
||||
val songs: List<Song> = getSongs(intent.extras!!)
|
||||
if (MusicPlayerRemote.shuffleMode == MusicService.SHUFFLE_MODE_SHUFFLE) {
|
||||
MusicPlayerRemote.openAndShuffleQueue(songs, true)
|
||||
} else {
|
||||
MusicPlayerRemote.openQueue(songs, 0, true)
|
||||
}
|
||||
handled = true
|
||||
}
|
||||
if (uri != null && uri.toString().isNotEmpty()) {
|
||||
MusicPlayerRemote.playFromUri(this@MainActivity, uri)
|
||||
handled = true
|
||||
} else if (MediaStore.Audio.Playlists.CONTENT_TYPE == mimeType) {
|
||||
val id = parseLongFromIntent(intent, "playlistId", "playlist")
|
||||
if (id >= 0L) {
|
||||
val position: Int = intent.getIntExtra("position", 0)
|
||||
val songs: List<Song> = PlaylistSongsLoader.getPlaylistSongList(get(), id)
|
||||
MusicPlayerRemote.openQueue(songs, position, true)
|
||||
handled = true
|
||||
}
|
||||
} else if (MediaStore.Audio.Albums.CONTENT_TYPE == mimeType) {
|
||||
val id = parseLongFromIntent(intent, "albumId", "album")
|
||||
if (id >= 0L) {
|
||||
val position: Int = intent.getIntExtra("position", 0)
|
||||
val songs = libraryViewModel.albumById(id).songs
|
||||
MusicPlayerRemote.openQueue(
|
||||
songs,
|
||||
position,
|
||||
true
|
||||
)
|
||||
handled = true
|
||||
}
|
||||
} else if (MediaStore.Audio.Artists.CONTENT_TYPE == mimeType) {
|
||||
val id = parseLongFromIntent(intent, "artistId", "artist")
|
||||
if (id >= 0L) {
|
||||
val position: Int = intent.getIntExtra("position", 0)
|
||||
val songs: List<Song> = libraryViewModel.artistById(id).songs
|
||||
MusicPlayerRemote.openQueue(
|
||||
songs,
|
||||
position,
|
||||
true
|
||||
)
|
||||
handled = true
|
||||
}
|
||||
}
|
||||
if (handled) {
|
||||
setIntent(Intent())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseLongFromIntent(
|
||||
intent: Intent,
|
||||
longKey: String,
|
||||
stringKey: String,
|
||||
): Long {
|
||||
var id = intent.getLongExtra(longKey, -1)
|
||||
if (id < 0) {
|
||||
val idString = intent.getStringExtra(stringKey)
|
||||
if (idString != null) {
|
||||
try {
|
||||
id = idString.toLong()
|
||||
} catch (e: NumberFormatException) {
|
||||
logE(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
return id
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.Manifest.permission.BLUETOOTH_CONNECT
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.core.view.isVisible
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
|
||||
import code.name.monkey.retromusic.databinding.ActivityPermissionBinding
|
||||
import code.name.monkey.retromusic.extensions.*
|
||||
|
||||
class PermissionActivity : AbsMusicServiceActivity() {
|
||||
private lateinit var binding: ActivityPermissionBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityPermissionBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
setStatusBarColorAuto()
|
||||
setTaskDescriptionColorAuto()
|
||||
setupTitle()
|
||||
|
||||
binding.storagePermission.setButtonClick {
|
||||
requestPermissions()
|
||||
}
|
||||
if (VersionUtils.hasMarshmallow()) {
|
||||
binding.audioPermission.show()
|
||||
binding.audioPermission.setButtonClick {
|
||||
if (!hasAudioPermission()) {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
|
||||
intent.data = ("package:" + applicationContext.packageName).toUri()
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (VersionUtils.hasS()) {
|
||||
binding.bluetoothPermission.show()
|
||||
binding.bluetoothPermission.setButtonClick {
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
arrayOf(BLUETOOTH_CONNECT),
|
||||
BLUETOOTH_PERMISSION_REQUEST
|
||||
)
|
||||
}
|
||||
} else {
|
||||
binding.audioPermission.setNumber("2")
|
||||
}
|
||||
|
||||
binding.finish.accentBackgroundColor()
|
||||
binding.finish.setOnClickListener {
|
||||
if (hasPermissions()) {
|
||||
startActivity(
|
||||
Intent(this, MainActivity::class.java).addFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK or
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
)
|
||||
)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
finishAffinity()
|
||||
remove()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun setupTitle() {
|
||||
val appName =
|
||||
getString(
|
||||
R.string.message_welcome,
|
||||
"<b>Metro</b>"
|
||||
)
|
||||
.parseAsHtml()
|
||||
binding.appNameText.text = appName
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
binding.finish.isEnabled = hasStoragePermission()
|
||||
if (hasStoragePermission()) {
|
||||
binding.storagePermission.checkImage.isVisible = true
|
||||
binding.storagePermission.checkImage.imageTintList =
|
||||
ColorStateList.valueOf(accentColor())
|
||||
}
|
||||
if (VersionUtils.hasMarshmallow()) {
|
||||
if (hasAudioPermission()) {
|
||||
binding.audioPermission.checkImage.isVisible = true
|
||||
binding.audioPermission.checkImage.imageTintList =
|
||||
ColorStateList.valueOf(accentColor())
|
||||
}
|
||||
}
|
||||
if (VersionUtils.hasS()) {
|
||||
if (hasBluetoothPermission()) {
|
||||
binding.bluetoothPermission.checkImage.isVisible = true
|
||||
binding.bluetoothPermission.checkImage.imageTintList =
|
||||
ColorStateList.valueOf(accentColor())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasStoragePermission(): Boolean {
|
||||
return hasPermissions()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.S)
|
||||
private fun hasBluetoothPermission(): Boolean {
|
||||
return ActivityCompat.checkSelfPermission(
|
||||
this,
|
||||
BLUETOOTH_CONNECT
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun hasAudioPermission(): Boolean {
|
||||
return Settings.System.canWrite(this)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.os.Bundle
|
||||
import android.provider.MediaStore.Images.Media
|
||||
import android.view.MenuItem
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.os.BundleCompat
|
||||
import androidx.core.view.drawToBitmap
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.retromusic.activities.base.AbsThemeActivity
|
||||
import code.name.monkey.retromusic.databinding.ActivityShareInstagramBinding
|
||||
import code.name.monkey.retromusic.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.setStatusBarColor
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
|
||||
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.Share
|
||||
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
|
||||
import com.bumptech.glide.Glide
|
||||
|
||||
/**
|
||||
* Created by hemanths on 2020-02-02.
|
||||
*/
|
||||
|
||||
class ShareInstagramStory : AbsThemeActivity() {
|
||||
|
||||
private lateinit var binding: ActivityShareInstagramBinding
|
||||
|
||||
companion object {
|
||||
const val EXTRA_SONG = "extra_song"
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityShareInstagramBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
setStatusBarColor(Color.TRANSPARENT)
|
||||
|
||||
binding.toolbar.setBackgroundColor(Color.TRANSPARENT)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
|
||||
val song = intent.extras?.let { BundleCompat.getParcelable(it, EXTRA_SONG, Song::class.java) }
|
||||
song?.let { songFinal ->
|
||||
Glide.with(this)
|
||||
.asBitmapPalette()
|
||||
.songCoverOptions(songFinal)
|
||||
.load(RetroGlideExtension.getSongModel(songFinal))
|
||||
.into(object : RetroMusicColoredTarget(binding.image) {
|
||||
override fun onColorReady(colors: MediaNotificationProcessor) {
|
||||
setColors(colors.backgroundColor)
|
||||
}
|
||||
})
|
||||
|
||||
binding.shareTitle.text = songFinal.title
|
||||
binding.shareText.text = songFinal.artistName
|
||||
binding.shareButton.setOnClickListener {
|
||||
val path: String = Media.insertImage(
|
||||
contentResolver,
|
||||
binding.mainContent.drawToBitmap(Bitmap.Config.ARGB_8888),
|
||||
"Design", null
|
||||
)
|
||||
Share.shareStoryToSocial(
|
||||
this@ShareInstagramStory,
|
||||
path.toUri()
|
||||
)
|
||||
}
|
||||
}
|
||||
binding.shareButton.setTextColor(
|
||||
MaterialValueHelper.getPrimaryTextColor(
|
||||
this,
|
||||
ColorUtil.isColorLight(accentColor())
|
||||
)
|
||||
)
|
||||
binding.shareButton.backgroundTintList =
|
||||
ColorStateList.valueOf(accentColor())
|
||||
}
|
||||
|
||||
private fun setColors(color: Int) {
|
||||
binding.mainContent.background =
|
||||
GradientDrawable(
|
||||
GradientDrawable.Orientation.TOP_BOTTOM,
|
||||
intArrayOf(color, Color.BLACK)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
package code.name.monkey.retromusic.activities
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import androidx.core.widget.NestedScrollView
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil.isWindowBackgroundDark
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil.isColorLight
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil.lightenColor
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper.getPrimaryTextColor
|
||||
import code.name.monkey.retromusic.BuildConfig
|
||||
import code.name.monkey.retromusic.Constants
|
||||
import code.name.monkey.retromusic.databinding.FragmentWhatsNewBinding
|
||||
import code.name.monkey.retromusic.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.openUrl
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil.lastVersion
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.*
|
||||
|
||||
class WhatsNewFragment : BottomSheetDialogFragment() {
|
||||
private var _binding: FragmentWhatsNewBinding? = null
|
||||
val binding get() = _binding!!
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentWhatsNewBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
try {
|
||||
val buf = StringBuilder()
|
||||
val stream = requireContext().assets.open("retro-changelog.html")
|
||||
stream.reader(StandardCharsets.UTF_8).buffered().use { br ->
|
||||
var str: String?
|
||||
while (br.readLine().also { str = it } != null) {
|
||||
buf.append(str)
|
||||
}
|
||||
}
|
||||
|
||||
// Inject color values for WebView body background and links
|
||||
val isDark = isWindowBackgroundDark(requireContext())
|
||||
val accentColor = accentColor()
|
||||
binding.webView.setBackgroundColor(0)
|
||||
val contentColor = colorToCSS(Color.parseColor(if (isDark) "#ffffff" else "#000000"))
|
||||
val textColor = colorToCSS(Color.parseColor(if (isDark) "#60FFFFFF" else "#80000000"))
|
||||
val accentColorString = colorToCSS(accentColor())
|
||||
val cardBackgroundColor =
|
||||
colorToCSS(Color.parseColor(if (isDark) "#353535" else "#ffffff"))
|
||||
val accentTextColor = colorToCSS(
|
||||
getPrimaryTextColor(
|
||||
requireContext(), isColorLight(accentColor)
|
||||
)
|
||||
)
|
||||
val changeLog = buf.toString()
|
||||
.replace(
|
||||
"{style-placeholder}",
|
||||
"body { color: $contentColor; } li {color: $textColor;} h3 {color: $accentColorString;} .tag {background-color: $accentColorString; color: $accentTextColor; } div{background-color: $cardBackgroundColor;}"
|
||||
)
|
||||
.replace("{link-color}", colorToCSS(accentColor()))
|
||||
.replace(
|
||||
"{link-color-active}",
|
||||
colorToCSS(
|
||||
lightenColor(accentColor())
|
||||
)
|
||||
)
|
||||
binding.webView.loadData(changeLog, "text/html", "UTF-8")
|
||||
binding.webView.webViewClient = object : WebViewClient() {
|
||||
override fun shouldOverrideUrlLoading(
|
||||
view: WebView?,
|
||||
request: WebResourceRequest?
|
||||
): Boolean {
|
||||
val url = request?.url ?: return false
|
||||
//you can do checks here e.g. url.host equals to target one
|
||||
startActivity(Intent(Intent.ACTION_VIEW, url))
|
||||
return true
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
binding.webView.loadData(
|
||||
"<h1>Unable to load</h1><p>" + e.localizedMessage + "</p>", "text/html", "UTF-8"
|
||||
)
|
||||
}
|
||||
setChangelogRead(requireContext())
|
||||
binding.tgFab.setOnClickListener {
|
||||
openUrl(Constants.TELEGRAM_CHANGE_LOG)
|
||||
}
|
||||
binding.tgFab.accentColor()
|
||||
binding.tgFab.shrink()
|
||||
binding.container.setOnScrollChangeListener { _: NestedScrollView?, _: Int, scrollY: Int, _: Int, oldScrollY: Int ->
|
||||
val dy = scrollY - oldScrollY
|
||||
if (dy > 0) {
|
||||
binding.tgFab.shrink()
|
||||
} else if (dy < 0) {
|
||||
binding.tgFab.extend()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val TAG = "WhatsNewFragment"
|
||||
private fun colorToCSS(color: Int): String {
|
||||
return String.format(
|
||||
Locale.getDefault(),
|
||||
"rgba(%d, %d, %d, %d)",
|
||||
Color.red(color),
|
||||
Color.green(color),
|
||||
Color.blue(color),
|
||||
Color.alpha(color)
|
||||
) // on API 29, WebView doesn't load with hex colors
|
||||
}
|
||||
|
||||
private fun setChangelogRead(context: Context) {
|
||||
try {
|
||||
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
val currentVersion = PackageInfoCompat.getLongVersionCode(pInfo)
|
||||
lastVersion = currentVersion
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun showChangeLog(activity: FragmentActivity) {
|
||||
val pInfo = activity.packageManager.getPackageInfo(activity.packageName, 0)
|
||||
val currentVersion = PackageInfoCompat.getLongVersionCode(pInfo)
|
||||
if (currentVersion > lastVersion && !BuildConfig.DEBUG) {
|
||||
val changelogBottomSheet = WhatsNewFragment()
|
||||
changelogBottomSheet.show(activity.supportFragmentManager, TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities.base
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Rect
|
||||
import android.media.AudioManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.rootView
|
||||
import code.name.monkey.retromusic.util.logD
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
|
||||
abstract class AbsBaseActivity : AbsThemeActivity() {
|
||||
private var hadPermissions: Boolean = false
|
||||
private lateinit var permissions: Array<String>
|
||||
private var permissionDeniedMessage: String? = null
|
||||
|
||||
open fun getPermissionsToRequest(): Array<String> {
|
||||
return arrayOf()
|
||||
}
|
||||
|
||||
protected fun setPermissionDeniedMessage(message: String) {
|
||||
permissionDeniedMessage = message
|
||||
}
|
||||
|
||||
fun getPermissionDeniedMessage(): String {
|
||||
return if (permissionDeniedMessage == null) getString(R.string.permissions_denied) else permissionDeniedMessage!!
|
||||
}
|
||||
|
||||
private val snackBarContainer: View
|
||||
get() = rootView
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
volumeControlStream = AudioManager.STREAM_MUSIC
|
||||
permissions = getPermissionsToRequest()
|
||||
hadPermissions = hasPermissions()
|
||||
permissionDeniedMessage = null
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val hasPermissions = hasPermissions()
|
||||
if (hasPermissions != hadPermissions) {
|
||||
hadPermissions = hasPermissions
|
||||
if (VersionUtils.hasMarshmallow()) {
|
||||
onHasPermissionsChanged(hasPermissions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun onHasPermissionsChanged(hasPermissions: Boolean) {
|
||||
// implemented by sub classes
|
||||
logD(hasPermissions)
|
||||
}
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
if (event.keyCode == KeyEvent.KEYCODE_MENU && event.action == KeyEvent.ACTION_UP) {
|
||||
showOverflowMenu()
|
||||
return true
|
||||
}
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
private fun showOverflowMenu() {
|
||||
}
|
||||
|
||||
protected open fun requestPermissions() {
|
||||
ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST)
|
||||
}
|
||||
|
||||
protected fun hasPermissions(): Boolean {
|
||||
for (permission in permissions) {
|
||||
if (ActivityCompat.checkSelfPermission(this,
|
||||
permission) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray,
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == PERMISSION_REQUEST) {
|
||||
for (grantResult in grantResults) {
|
||||
if (grantResult != PackageManager.PERMISSION_GRANTED) {
|
||||
if (ActivityCompat.shouldShowRequestPermissionRationale(
|
||||
this@AbsBaseActivity, Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
) || ActivityCompat.shouldShowRequestPermissionRationale(
|
||||
this@AbsBaseActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
)
|
||||
) {
|
||||
// User has deny from permission dialog
|
||||
Snackbar.make(
|
||||
snackBarContainer,
|
||||
permissionDeniedMessage!!,
|
||||
Snackbar.LENGTH_SHORT
|
||||
)
|
||||
.setAction(R.string.action_grant) { requestPermissions() }
|
||||
.setActionTextColor(accentColor()).show()
|
||||
} else {
|
||||
// User has deny permission and checked never show permission dialog so you can redirect to Application settings page
|
||||
Snackbar.make(
|
||||
snackBarContainer,
|
||||
permissionDeniedMessage!!,
|
||||
Snackbar.LENGTH_INDEFINITE
|
||||
)
|
||||
.setAction(R.string.action_settings) {
|
||||
val intent = Intent()
|
||||
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
val uri = Uri.fromParts(
|
||||
"package",
|
||||
this@AbsBaseActivity.packageName,
|
||||
null
|
||||
)
|
||||
intent.data = uri
|
||||
startActivity(intent)
|
||||
}.setActionTextColor(accentColor()).show()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
hadPermissions = true
|
||||
onHasPermissionsChanged(true)
|
||||
} else if (requestCode == BLUETOOTH_PERMISSION_REQUEST) {
|
||||
for (grantResult in grantResults) {
|
||||
if (grantResult != PackageManager.PERMISSION_GRANTED) {
|
||||
if (ActivityCompat.shouldShowRequestPermissionRationale(
|
||||
this@AbsBaseActivity, Manifest.permission.BLUETOOTH_CONNECT
|
||||
)
|
||||
) {
|
||||
// User has deny from permission dialog
|
||||
Snackbar.make(
|
||||
snackBarContainer,
|
||||
R.string.permission_bluetooth_denied,
|
||||
Snackbar.LENGTH_SHORT
|
||||
)
|
||||
.setAction(R.string.action_grant) {
|
||||
ActivityCompat.requestPermissions(this,
|
||||
arrayOf(Manifest.permission.BLUETOOTH_CONNECT),
|
||||
BLUETOOTH_PERMISSION_REQUEST)
|
||||
}
|
||||
.setActionTextColor(accentColor()).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PERMISSION_REQUEST = 100
|
||||
const val BLUETOOTH_PERMISSION_REQUEST = 101
|
||||
}
|
||||
|
||||
// this lets keyboard close when clicked in background
|
||||
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
||||
if (event.action == MotionEvent.ACTION_DOWN) {
|
||||
val v = currentFocus
|
||||
if (v is EditText) {
|
||||
val outRect = Rect()
|
||||
v.getGlobalVisibleRect(outRect)
|
||||
if (!outRect.contains(event.rawX.toInt(), event.rawY.toInt())) {
|
||||
v.clearFocus()
|
||||
getSystemService<InputMethodManager>()?.hideSoftInputFromWindow(
|
||||
v.windowToken,
|
||||
0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.dispatchTouchEvent(event)
|
||||
}
|
||||
}
|
|
@ -1,20 +1,49 @@
|
|||
package io.github.muntashirakon.music.activities.base
|
||||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities.base
|
||||
|
||||
import android.Manifest
|
||||
import android.content.*
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.helper.MusicPlayerRemote
|
||||
import io.github.muntashirakon.music.interfaces.MusicServiceEventListener
|
||||
import io.github.muntashirakon.music.service.MusicService.*
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.db.toPlayCount
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener
|
||||
import code.name.monkey.retromusic.repository.RealRepository
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.FAVORITE_STATE_CHANGED
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.MEDIA_STORE_CHANGED
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.META_CHANGED
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.PLAY_STATE_CHANGED
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.QUEUE_CHANGED
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.REPEAT_MODE_CHANGED
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.SHUFFLE_MODE_CHANGED
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.logD
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
|
||||
abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventListener {
|
||||
|
||||
private val mMusicServiceEventListeners = ArrayList<MusicServiceEventListener>()
|
||||
abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventListener {
|
||||
|
||||
private val mMusicServiceEventListeners = ArrayList<IMusicServiceEventListener>()
|
||||
private val repository: RealRepository by inject()
|
||||
private var serviceToken: MusicPlayerRemote.ServiceToken? = null
|
||||
private var musicStateReceiver: MusicStateReceiver? = null
|
||||
private var receiverRegistered: Boolean = false
|
||||
|
@ -43,15 +72,15 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventLis
|
|||
}
|
||||
}
|
||||
|
||||
fun addMusicServiceEventListener(listener: MusicServiceEventListener?) {
|
||||
if (listener != null) {
|
||||
mMusicServiceEventListeners.add(listener)
|
||||
fun addMusicServiceEventListener(listenerI: IMusicServiceEventListener?) {
|
||||
if (listenerI != null) {
|
||||
mMusicServiceEventListeners.add(listenerI)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeMusicServiceEventListener(listener: MusicServiceEventListener?) {
|
||||
if (listener != null) {
|
||||
mMusicServiceEventListeners.remove(listener)
|
||||
fun removeMusicServiceEventListener(listenerI: IMusicServiceEventListener?) {
|
||||
if (listenerI != null) {
|
||||
mMusicServiceEventListeners.remove(listenerI)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,8 +97,7 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventLis
|
|||
filter.addAction(MEDIA_STORE_CHANGED)
|
||||
filter.addAction(FAVORITE_STATE_CHANGED)
|
||||
|
||||
registerReceiver(musicStateReceiver, filter)
|
||||
|
||||
ContextCompat.registerReceiver(this, musicStateReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED)
|
||||
receiverRegistered = true
|
||||
}
|
||||
|
||||
|
@ -93,6 +121,16 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventLis
|
|||
for (listener in mMusicServiceEventListeners) {
|
||||
listener.onPlayingMetaChanged()
|
||||
}
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
if (!PreferenceUtil.pauseHistory) {
|
||||
repository.upsertSongInHistory(MusicPlayerRemote.currentSong)
|
||||
}
|
||||
val song = repository.findSongExistInPlayCount(MusicPlayerRemote.currentSong.id)
|
||||
?.apply { playCount += 1 }
|
||||
?: MusicPlayerRemote.currentSong.toPlayCount()
|
||||
|
||||
repository.upsertSongInPlayCount(song)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onQueueChanged() {
|
||||
|
@ -125,6 +163,12 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventLis
|
|||
}
|
||||
}
|
||||
|
||||
override fun onFavoriteStateChanged() {
|
||||
for (listener in mMusicServiceEventListeners) {
|
||||
listener.onFavoriteStateChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onHasPermissionsChanged(hasPermissions: Boolean) {
|
||||
super.onHasPermissionsChanged(hasPermissions)
|
||||
val intent = Intent(MEDIA_STORE_CHANGED)
|
||||
|
@ -133,15 +177,21 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventLis
|
|||
true
|
||||
) // just in case we need to know this at some point
|
||||
sendBroadcast(intent)
|
||||
println("sendBroadcast $hasPermissions")
|
||||
logD("sendBroadcast $hasPermissions")
|
||||
}
|
||||
|
||||
override fun getPermissionsToRequest(): Array<String> {
|
||||
return arrayOf(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
Manifest.permission.BLUETOOTH
|
||||
)
|
||||
return mutableListOf<String>().apply {
|
||||
if (VersionUtils.hasT()) {
|
||||
add(Manifest.permission.READ_MEDIA_AUDIO)
|
||||
add(Manifest.permission.POST_NOTIFICATIONS)
|
||||
} else {
|
||||
add(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
}
|
||||
if (!VersionUtils.hasR()) {
|
||||
add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
}
|
||||
}.toTypedArray()
|
||||
}
|
||||
|
||||
private class MusicStateReceiver(activity: AbsMusicServiceActivity) : BroadcastReceiver() {
|
||||
|
@ -153,7 +203,8 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventLis
|
|||
val activity = reference.get()
|
||||
if (activity != null && action != null) {
|
||||
when (action) {
|
||||
FAVORITE_STATE_CHANGED, META_CHANGED -> activity.onPlayingMetaChanged()
|
||||
FAVORITE_STATE_CHANGED -> activity.onFavoriteStateChanged()
|
||||
META_CHANGED -> activity.onPlayingMetaChanged()
|
||||
QUEUE_CHANGED -> activity.onQueueChanged()
|
||||
PLAY_STATE_CHANGED -> activity.onPlayStateChanged()
|
||||
REPEAT_MODE_CHANGED -> activity.onRepeatModeChanged()
|
|
@ -0,0 +1,581 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities.base
|
||||
|
||||
import android.animation.ArgbEvaluator
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver
|
||||
import android.view.animation.PathInterpolator
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.core.animation.doOnEnd
|
||||
import androidx.core.view.*
|
||||
import androidx.fragment.app.commit
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.ADAPTIVE_COLOR_APP
|
||||
import code.name.monkey.retromusic.ALBUM_COVER_STYLE
|
||||
import code.name.monkey.retromusic.ALBUM_COVER_TRANSFORM
|
||||
import code.name.monkey.retromusic.CAROUSEL_EFFECT
|
||||
import code.name.monkey.retromusic.CIRCLE_PLAY_BUTTON
|
||||
import code.name.monkey.retromusic.EXTRA_SONG_INFO
|
||||
import code.name.monkey.retromusic.KEEP_SCREEN_ON
|
||||
import code.name.monkey.retromusic.LIBRARY_CATEGORIES
|
||||
import code.name.monkey.retromusic.NOW_PLAYING_SCREEN_ID
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.SCREEN_ON_LYRICS
|
||||
import code.name.monkey.retromusic.SWIPE_ANYWHERE_NOW_PLAYING
|
||||
import code.name.monkey.retromusic.SWIPE_DOWN_DISMISS
|
||||
import code.name.monkey.retromusic.TAB_TEXT_MODE
|
||||
import code.name.monkey.retromusic.TOGGLE_ADD_CONTROLS
|
||||
import code.name.monkey.retromusic.TOGGLE_FULL_SCREEN
|
||||
import code.name.monkey.retromusic.TOGGLE_VOLUME
|
||||
import code.name.monkey.retromusic.activities.PermissionActivity
|
||||
import code.name.monkey.retromusic.databinding.SlidingMusicPanelLayoutBinding
|
||||
import code.name.monkey.retromusic.extensions.*
|
||||
import code.name.monkey.retromusic.fragments.LibraryViewModel
|
||||
import code.name.monkey.retromusic.fragments.NowPlayingScreen
|
||||
import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
|
||||
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.other.MiniPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.adaptive.AdaptiveFragment
|
||||
import code.name.monkey.retromusic.fragments.player.blur.BlurPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.card.CardFragment
|
||||
import code.name.monkey.retromusic.fragments.player.cardblur.CardBlurFragment
|
||||
import code.name.monkey.retromusic.fragments.player.circle.CirclePlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.classic.ClassicPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.color.ColorFragment
|
||||
import code.name.monkey.retromusic.fragments.player.fit.FitFragment
|
||||
import code.name.monkey.retromusic.fragments.player.flat.FlatPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.full.FullPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.gradient.GradientPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.material.MaterialFragment
|
||||
import code.name.monkey.retromusic.fragments.player.md3.MD3PlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.normal.PlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.peek.PeekPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.plain.PlainPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.simple.SimplePlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.player.tiny.TinyPlayerFragment
|
||||
import code.name.monkey.retromusic.fragments.queue.PlayingQueueFragment
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.model.CategoryInfo
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.ViewUtil
|
||||
import code.name.monkey.retromusic.util.logD
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_DRAGGING
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_SETTLING
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.from
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
|
||||
abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity(),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
companion object {
|
||||
val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName
|
||||
}
|
||||
|
||||
var fromNotification = false
|
||||
private var windowInsets: WindowInsetsCompat? = null
|
||||
protected val libraryViewModel by viewModel<LibraryViewModel>()
|
||||
private lateinit var bottomSheetBehavior: BottomSheetBehavior<FrameLayout>
|
||||
private lateinit var playerFragment: AbsPlayerFragment
|
||||
private var miniPlayerFragment: MiniPlayerFragment? = null
|
||||
private var nowPlayingScreen: NowPlayingScreen? = null
|
||||
private var taskColor: Int = 0
|
||||
private var paletteColor: Int = Color.WHITE
|
||||
private var navigationBarColor = 0
|
||||
|
||||
private val panelState: Int
|
||||
get() = bottomSheetBehavior.state
|
||||
private lateinit var binding: SlidingMusicPanelLayoutBinding
|
||||
private var isInOneTabMode = false
|
||||
|
||||
private var navigationBarColorAnimator: ValueAnimator? = null
|
||||
private val argbEvaluator: ArgbEvaluator = ArgbEvaluator()
|
||||
|
||||
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
println("Handle back press ${bottomSheetBehavior.state}")
|
||||
if (!handleBackPress()) {
|
||||
remove()
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val bottomSheetCallbackList by lazy {
|
||||
object : BottomSheetCallback() {
|
||||
|
||||
override fun onSlide(bottomSheet: View, slideOffset: Float) {
|
||||
setMiniPlayerAlphaProgress(slideOffset)
|
||||
navigationBarColorAnimator?.cancel()
|
||||
setNavigationBarColorPreOreo(
|
||||
argbEvaluator.evaluate(
|
||||
slideOffset,
|
||||
surfaceColor(),
|
||||
navigationBarColor
|
||||
) as Int
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||
onBackPressedCallback.isEnabled = newState == STATE_EXPANDED
|
||||
when (newState) {
|
||||
STATE_EXPANDED -> {
|
||||
onPanelExpanded()
|
||||
if (PreferenceUtil.lyricsScreenOn && PreferenceUtil.showLyrics) {
|
||||
keepScreenOn(true)
|
||||
}
|
||||
}
|
||||
|
||||
STATE_COLLAPSED -> {
|
||||
onPanelCollapsed()
|
||||
if ((PreferenceUtil.lyricsScreenOn && PreferenceUtil.showLyrics) || !PreferenceUtil.isScreenOnEnabled) {
|
||||
keepScreenOn(false)
|
||||
}
|
||||
}
|
||||
|
||||
STATE_SETTLING, STATE_DRAGGING -> {
|
||||
if (fromNotification) {
|
||||
binding.navigationView.bringToFront()
|
||||
fromNotification = false
|
||||
}
|
||||
}
|
||||
|
||||
STATE_HIDDEN -> {
|
||||
MusicPlayerRemote.clearQueue()
|
||||
}
|
||||
|
||||
else -> {
|
||||
logD("Do a flip")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getBottomSheetBehavior() = bottomSheetBehavior
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (!hasPermissions()) {
|
||||
startActivity(Intent(this, PermissionActivity::class.java))
|
||||
finish()
|
||||
}
|
||||
binding = SlidingMusicPanelLayoutBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
binding.root.setOnApplyWindowInsetsListener { _, insets ->
|
||||
windowInsets = WindowInsetsCompat.toWindowInsetsCompat(insets)
|
||||
insets
|
||||
}
|
||||
chooseFragmentForTheme()
|
||||
setupSlidingUpPanel()
|
||||
setupBottomSheet()
|
||||
updateColor()
|
||||
if (!PreferenceUtil.materialYou) {
|
||||
binding.slidingPanel.backgroundTintList = ColorStateList.valueOf(darkAccentColor())
|
||||
navigationView.backgroundTintList = ColorStateList.valueOf(darkAccentColor())
|
||||
}
|
||||
|
||||
navigationBarColor = surfaceColor()
|
||||
|
||||
onBackPressedDispatcher.addCallback(onBackPressedCallback)
|
||||
}
|
||||
|
||||
private fun setupBottomSheet() {
|
||||
bottomSheetBehavior = from(binding.slidingPanel)
|
||||
bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallbackList)
|
||||
bottomSheetBehavior.isHideable = PreferenceUtil.swipeDownToDismiss
|
||||
bottomSheetBehavior.significantVelocityThreshold = 300
|
||||
setMiniPlayerAlphaProgress(0F)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
PreferenceUtil.registerOnSharedPreferenceChangedListener(this)
|
||||
if (nowPlayingScreen != PreferenceUtil.nowPlayingScreen) {
|
||||
postRecreate()
|
||||
}
|
||||
if (bottomSheetBehavior.state == STATE_EXPANDED) {
|
||||
setMiniPlayerAlphaProgress(1f)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallbackList)
|
||||
PreferenceUtil.unregisterOnSharedPreferenceChangedListener(this)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||
when (key) {
|
||||
SWIPE_DOWN_DISMISS -> {
|
||||
bottomSheetBehavior.isHideable = PreferenceUtil.swipeDownToDismiss
|
||||
}
|
||||
|
||||
TOGGLE_ADD_CONTROLS -> {
|
||||
miniPlayerFragment?.setUpButtons()
|
||||
}
|
||||
|
||||
NOW_PLAYING_SCREEN_ID -> {
|
||||
chooseFragmentForTheme()
|
||||
binding.slidingPanel.updateLayoutParams<ViewGroup.LayoutParams> {
|
||||
height = if (nowPlayingScreen != Peek) {
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
} else {
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
}
|
||||
onServiceConnected()
|
||||
}
|
||||
}
|
||||
|
||||
ALBUM_COVER_TRANSFORM, CAROUSEL_EFFECT,
|
||||
ALBUM_COVER_STYLE, TOGGLE_VOLUME, EXTRA_SONG_INFO, CIRCLE_PLAY_BUTTON,
|
||||
-> {
|
||||
chooseFragmentForTheme()
|
||||
onServiceConnected()
|
||||
}
|
||||
|
||||
SWIPE_ANYWHERE_NOW_PLAYING -> {
|
||||
playerFragment.addSwipeDetector()
|
||||
}
|
||||
|
||||
ADAPTIVE_COLOR_APP -> {
|
||||
if (PreferenceUtil.nowPlayingScreen in listOf(Normal, Material, Flat)) {
|
||||
chooseFragmentForTheme()
|
||||
onServiceConnected()
|
||||
}
|
||||
}
|
||||
|
||||
LIBRARY_CATEGORIES -> {
|
||||
updateTabs()
|
||||
}
|
||||
|
||||
TAB_TEXT_MODE -> {
|
||||
navigationView.labelVisibilityMode = PreferenceUtil.tabTitleMode
|
||||
}
|
||||
|
||||
TOGGLE_FULL_SCREEN -> {
|
||||
recreate()
|
||||
}
|
||||
|
||||
SCREEN_ON_LYRICS -> {
|
||||
keepScreenOn(bottomSheetBehavior.state == STATE_EXPANDED && PreferenceUtil.lyricsScreenOn && PreferenceUtil.showLyrics || PreferenceUtil.isScreenOnEnabled)
|
||||
}
|
||||
|
||||
KEEP_SCREEN_ON -> {
|
||||
maybeSetScreenOn()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun collapsePanel() {
|
||||
bottomSheetBehavior.state = STATE_COLLAPSED
|
||||
}
|
||||
|
||||
fun expandPanel() {
|
||||
bottomSheetBehavior.state = STATE_EXPANDED
|
||||
}
|
||||
|
||||
private fun setMiniPlayerAlphaProgress(progress: Float) {
|
||||
if (progress < 0) return
|
||||
val alpha = 1 - progress
|
||||
miniPlayerFragment?.view?.alpha = 1 - (progress / 0.2F)
|
||||
miniPlayerFragment?.view?.isGone = alpha == 0f
|
||||
if (!isLandscape) {
|
||||
binding.navigationView.translationY = progress * 500
|
||||
binding.navigationView.alpha = alpha
|
||||
}
|
||||
binding.playerFragmentContainer.alpha = (progress - 0.2F) / 0.2F
|
||||
}
|
||||
|
||||
private fun animateNavigationBarColor(color: Int) {
|
||||
if (VersionUtils.hasOreo()) return
|
||||
navigationBarColorAnimator?.cancel()
|
||||
navigationBarColorAnimator = ValueAnimator
|
||||
.ofArgb(window.navigationBarColor, color).apply {
|
||||
duration = ViewUtil.RETRO_MUSIC_ANIM_TIME.toLong()
|
||||
interpolator = PathInterpolator(0.4f, 0f, 1f, 1f)
|
||||
addUpdateListener { animation: ValueAnimator ->
|
||||
setNavigationBarColorPreOreo(
|
||||
animation.animatedValue as Int
|
||||
)
|
||||
}
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
open fun onPanelCollapsed() {
|
||||
setMiniPlayerAlphaProgress(0F)
|
||||
// restore values
|
||||
animateNavigationBarColor(surfaceColor())
|
||||
setLightStatusBarAuto()
|
||||
setLightNavigationBarAuto()
|
||||
setTaskDescriptionColor(taskColor)
|
||||
//playerFragment?.onHide()
|
||||
}
|
||||
|
||||
open fun onPanelExpanded() {
|
||||
setMiniPlayerAlphaProgress(1F)
|
||||
onPaletteColorChanged()
|
||||
//playerFragment?.onShow()
|
||||
}
|
||||
|
||||
private fun setupSlidingUpPanel() {
|
||||
binding.slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object :
|
||||
ViewTreeObserver.OnGlobalLayoutListener {
|
||||
override fun onGlobalLayout() {
|
||||
binding.slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
if (nowPlayingScreen != Peek) {
|
||||
binding.slidingPanel.updateLayoutParams<ViewGroup.LayoutParams> {
|
||||
height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
}
|
||||
}
|
||||
when (panelState) {
|
||||
STATE_EXPANDED -> onPanelExpanded()
|
||||
STATE_COLLAPSED -> onPanelCollapsed()
|
||||
else -> {
|
||||
// playerFragment!!.onHide()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
val navigationView get() = binding.navigationView
|
||||
|
||||
val slidingPanel get() = binding.slidingPanel
|
||||
|
||||
val isBottomNavVisible get() = navigationView.isVisible && navigationView is BottomNavigationView
|
||||
|
||||
override fun onServiceConnected() {
|
||||
super.onServiceConnected()
|
||||
hideBottomSheet(false)
|
||||
}
|
||||
|
||||
override fun onQueueChanged() {
|
||||
super.onQueueChanged()
|
||||
// Mini player should be hidden in Playing Queue
|
||||
// it may pop up if hideBottomSheet is called
|
||||
if (currentFragment(R.id.fragment_container) !is PlayingQueueFragment) {
|
||||
hideBottomSheet(MusicPlayerRemote.playingQueue.isEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBackPress(): Boolean {
|
||||
if (panelState == STATE_EXPANDED) {
|
||||
collapsePanel()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun onPaletteColorChanged() {
|
||||
if (panelState == STATE_EXPANDED) {
|
||||
navigationBarColor = surfaceColor()
|
||||
setTaskDescColor(paletteColor)
|
||||
val isColorLight = paletteColor.isColorLight
|
||||
if (PreferenceUtil.isAdaptiveColor && (nowPlayingScreen == Normal || nowPlayingScreen == Flat || nowPlayingScreen == Material)) {
|
||||
setLightNavigationBar(true)
|
||||
setLightStatusBar(isColorLight)
|
||||
} else if (nowPlayingScreen == Card || nowPlayingScreen == Blur || nowPlayingScreen == BlurCard) {
|
||||
animateNavigationBarColor(Color.BLACK)
|
||||
navigationBarColor = Color.BLACK
|
||||
setLightStatusBar(false)
|
||||
setLightNavigationBar(true)
|
||||
} else if (nowPlayingScreen == Color || nowPlayingScreen == Tiny || nowPlayingScreen == Gradient) {
|
||||
animateNavigationBarColor(paletteColor)
|
||||
navigationBarColor = paletteColor
|
||||
setLightNavigationBar(isColorLight)
|
||||
setLightStatusBar(isColorLight)
|
||||
} else if (nowPlayingScreen == Full) {
|
||||
animateNavigationBarColor(paletteColor)
|
||||
navigationBarColor = paletteColor
|
||||
setLightNavigationBar(isColorLight)
|
||||
setLightStatusBar(false)
|
||||
} else if (nowPlayingScreen == Classic) {
|
||||
setLightStatusBar(false)
|
||||
} else if (nowPlayingScreen == Fit) {
|
||||
setLightStatusBar(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setTaskDescColor(color: Int) {
|
||||
taskColor = color
|
||||
if (panelState == STATE_COLLAPSED) {
|
||||
setTaskDescriptionColor(color)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateTabs() {
|
||||
binding.navigationView.menu.clear()
|
||||
val currentTabs: List<CategoryInfo> = PreferenceUtil.libraryCategory
|
||||
for (tab in currentTabs) {
|
||||
if (tab.visible) {
|
||||
val menu = tab.category
|
||||
binding.navigationView.menu.add(0, menu.id, 0, menu.stringRes)
|
||||
.setIcon(menu.icon)
|
||||
}
|
||||
}
|
||||
if (binding.navigationView.menu.size() == 1) {
|
||||
isInOneTabMode = true
|
||||
binding.navigationView.isVisible = false
|
||||
} else {
|
||||
isInOneTabMode = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateColor() {
|
||||
libraryViewModel.paletteColor.observe(this) { color ->
|
||||
this.paletteColor = color
|
||||
onPaletteColorChanged()
|
||||
}
|
||||
}
|
||||
|
||||
fun setBottomNavVisibility(
|
||||
visible: Boolean,
|
||||
animate: Boolean = false,
|
||||
hideBottomSheet: Boolean = MusicPlayerRemote.playingQueue.isEmpty(),
|
||||
) {
|
||||
if (!ViewCompat.isLaidOut(navigationView)) {
|
||||
return
|
||||
}
|
||||
if (isInOneTabMode) {
|
||||
hideBottomSheet(
|
||||
hide = hideBottomSheet,
|
||||
animate = animate,
|
||||
isBottomNavVisible = false
|
||||
)
|
||||
return
|
||||
}
|
||||
if (visible xor navigationView.isVisible) {
|
||||
val mAnimate = animate && bottomSheetBehavior.state == STATE_COLLAPSED
|
||||
if (mAnimate) {
|
||||
if (visible) {
|
||||
binding.navigationView.bringToFront()
|
||||
binding.navigationView.show()
|
||||
} else {
|
||||
binding.navigationView.hide()
|
||||
}
|
||||
} else {
|
||||
binding.navigationView.isVisible = visible
|
||||
if (visible && bottomSheetBehavior.state != STATE_EXPANDED) {
|
||||
binding.navigationView.bringToFront()
|
||||
}
|
||||
}
|
||||
}
|
||||
hideBottomSheet(
|
||||
hide = hideBottomSheet,
|
||||
animate = animate,
|
||||
isBottomNavVisible = visible && navigationView is BottomNavigationView
|
||||
)
|
||||
}
|
||||
|
||||
fun hideBottomSheet(
|
||||
hide: Boolean,
|
||||
animate: Boolean = false,
|
||||
isBottomNavVisible: Boolean = navigationView.isVisible && navigationView is BottomNavigationView,
|
||||
) {
|
||||
val heightOfBar = windowInsets.getBottomInsets() + dip(R.dimen.mini_player_height)
|
||||
val heightOfBarWithTabs = heightOfBar + dip(R.dimen.bottom_nav_height)
|
||||
if (hide) {
|
||||
bottomSheetBehavior.peekHeight = -windowInsets.getBottomInsets()
|
||||
bottomSheetBehavior.state = STATE_COLLAPSED
|
||||
libraryViewModel.setFabMargin(
|
||||
this,
|
||||
if (isBottomNavVisible) dip(R.dimen.bottom_nav_height) else 0
|
||||
)
|
||||
} else {
|
||||
if (MusicPlayerRemote.playingQueue.isNotEmpty()) {
|
||||
binding.slidingPanel.elevation = 0F
|
||||
binding.navigationView.elevation = 5F
|
||||
if (isBottomNavVisible) {
|
||||
logD("List")
|
||||
if (animate) {
|
||||
bottomSheetBehavior.peekHeightAnimate(heightOfBarWithTabs)
|
||||
} else {
|
||||
bottomSheetBehavior.peekHeight = heightOfBarWithTabs
|
||||
}
|
||||
libraryViewModel.setFabMargin(
|
||||
this,
|
||||
dip(R.dimen.bottom_nav_mini_player_height)
|
||||
)
|
||||
} else {
|
||||
logD("Details")
|
||||
if (animate) {
|
||||
bottomSheetBehavior.peekHeightAnimate(heightOfBar).doOnEnd {
|
||||
binding.slidingPanel.bringToFront()
|
||||
}
|
||||
} else {
|
||||
bottomSheetBehavior.peekHeight = heightOfBar
|
||||
binding.slidingPanel.bringToFront()
|
||||
}
|
||||
libraryViewModel.setFabMargin(this, dip(R.dimen.mini_player_height))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setAllowDragging(allowDragging: Boolean) {
|
||||
bottomSheetBehavior.isDraggable = allowDragging
|
||||
hideBottomSheet(false)
|
||||
}
|
||||
|
||||
private fun chooseFragmentForTheme() {
|
||||
nowPlayingScreen = PreferenceUtil.nowPlayingScreen
|
||||
|
||||
val fragment: AbsPlayerFragment = when (nowPlayingScreen) {
|
||||
Blur -> BlurPlayerFragment()
|
||||
Adaptive -> AdaptiveFragment()
|
||||
Normal -> PlayerFragment()
|
||||
Card -> CardFragment()
|
||||
BlurCard -> CardBlurFragment()
|
||||
Fit -> FitFragment()
|
||||
Flat -> FlatPlayerFragment()
|
||||
Full -> FullPlayerFragment()
|
||||
Plain -> PlainPlayerFragment()
|
||||
Simple -> SimplePlayerFragment()
|
||||
Material -> MaterialFragment()
|
||||
Color -> ColorFragment()
|
||||
Gradient -> GradientPlayerFragment()
|
||||
Tiny -> TinyPlayerFragment()
|
||||
Peek -> PeekPlayerFragment()
|
||||
Circle -> CirclePlayerFragment()
|
||||
Classic -> ClassicPlayerFragment()
|
||||
MD3 -> MD3PlayerFragment()
|
||||
else -> PlayerFragment()
|
||||
} // must extend AbsPlayerFragment
|
||||
supportFragmentManager.commit {
|
||||
replace(R.id.playerFragmentContainer, fragment)
|
||||
}
|
||||
supportFragmentManager.executePendingTransactions()
|
||||
playerFragment = whichFragment(R.id.playerFragmentContainer)
|
||||
miniPlayerFragment = whichFragment<MiniPlayerFragment>(R.id.miniPlayerFragment)
|
||||
miniPlayerFragment?.view?.setOnClickListener { expandPanel() }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities.base
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.KeyEvent
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import code.name.monkey.appthemehelper.common.ATHToolbarActivity
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.*
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.theme.getNightMode
|
||||
import code.name.monkey.retromusic.util.theme.getThemeResValue
|
||||
|
||||
abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
|
||||
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
updateLocale()
|
||||
updateTheme()
|
||||
hideStatusBar()
|
||||
super.onCreate(savedInstanceState)
|
||||
setEdgeToEdgeOrImmersive()
|
||||
maybeSetScreenOn()
|
||||
setLightNavigationBarAuto()
|
||||
setLightStatusBarAuto(surfaceColor())
|
||||
if (VersionUtils.hasQ()) {
|
||||
window.decorView.isForceDarkAllowed = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTheme() {
|
||||
setTheme(getThemeResValue())
|
||||
if (PreferenceUtil.materialYou) {
|
||||
setDefaultNightMode(getNightMode())
|
||||
}
|
||||
|
||||
if (PreferenceUtil.isCustomFont) {
|
||||
setTheme(R.style.FontThemeOverlay)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateLocale() {
|
||||
val localeCode = PreferenceUtil.languageCode
|
||||
if (PreferenceUtil.isLocaleAutoStorageEnabled) {
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(localeCode))
|
||||
PreferenceUtil.isLocaleAutoStorageEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
if (hasFocus) {
|
||||
hideStatusBar()
|
||||
handler.removeCallbacks(this)
|
||||
handler.postDelayed(this, 300)
|
||||
} else {
|
||||
handler.removeCallbacks(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
setImmersiveFullscreen()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
handler.removeCallbacks(this)
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
public override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
exitFullscreen()
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
||||
handler.removeCallbacks(this)
|
||||
handler.postDelayed(this, 500)
|
||||
}
|
||||
return super.onKeyDown(keyCode, event)
|
||||
}
|
||||
|
||||
override fun attachBaseContext(newBase: Context?) {
|
||||
super.attachBaseContext(newBase)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities.bugreport
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.net.toUri
|
||||
import code.name.monkey.appthemehelper.util.TintHelper
|
||||
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.activities.base.AbsThemeActivity
|
||||
import code.name.monkey.retromusic.activities.bugreport.model.DeviceInfo
|
||||
import code.name.monkey.retromusic.databinding.ActivityBugReportBinding
|
||||
import code.name.monkey.retromusic.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto
|
||||
import code.name.monkey.retromusic.extensions.showToast
|
||||
|
||||
open class BugReportActivity : AbsThemeActivity() {
|
||||
|
||||
private lateinit var binding: ActivityBugReportBinding
|
||||
private var deviceInfo: DeviceInfo? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityBugReportBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
setTaskDescriptionColorAuto()
|
||||
|
||||
initViews()
|
||||
|
||||
if (title.isNullOrEmpty()) setTitle(R.string.report_an_issue)
|
||||
|
||||
deviceInfo = DeviceInfo(this)
|
||||
binding.cardDeviceInfo.airTextDeviceInfo.text = deviceInfo.toString()
|
||||
}
|
||||
|
||||
private fun initViews() {
|
||||
val accentColor = accentColor()
|
||||
setSupportActionBar(binding.toolbar)
|
||||
ToolbarContentTintHelper.colorBackButton(binding.toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
binding.cardDeviceInfo.airTextDeviceInfo.setOnClickListener { copyDeviceInfoToClipBoard() }
|
||||
|
||||
TintHelper.setTintAuto(binding.sendFab, accentColor, true)
|
||||
binding.sendFab.setOnClickListener { reportIssue() }
|
||||
}
|
||||
|
||||
private fun reportIssue() {
|
||||
copyDeviceInfoToClipBoard()
|
||||
val i = Intent(Intent.ACTION_VIEW)
|
||||
i.data = ISSUE_TRACKER_LINK.toUri()
|
||||
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
startActivity(i)
|
||||
}
|
||||
|
||||
private fun copyDeviceInfoToClipBoard() {
|
||||
val clipboard = getSystemService<ClipboardManager>()
|
||||
val clip = ClipData.newPlainText(getString(R.string.device_info), deviceInfo?.toMarkdown())
|
||||
clipboard?.setPrimaryClip(clip)
|
||||
showToast(R.string.copied_device_info_to_clipboard)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ISSUE_TRACKER_LINK =
|
||||
"https://github.com/MuntashirAkon/Metro/issues/new"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package code.name.monkey.retromusic.activities.bugreport.model
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.annotation.IntRange
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil.isAdaptiveColor
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil.nowPlayingScreen
|
||||
import java.util.*
|
||||
|
||||
class DeviceInfo(context: Context) {
|
||||
@SuppressLint("NewApi")
|
||||
private val abis = Build.SUPPORTED_ABIS
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
private val abis32Bits = Build.SUPPORTED_32_BIT_ABIS
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
private val abis64Bits = Build.SUPPORTED_64_BIT_ABIS
|
||||
private val baseTheme: String
|
||||
private val brand = Build.BRAND
|
||||
private val buildID = Build.DISPLAY
|
||||
private val buildVersion = Build.VERSION.INCREMENTAL
|
||||
private val device = Build.DEVICE
|
||||
private val hardware = Build.HARDWARE
|
||||
private val isAdaptive: Boolean
|
||||
private val manufacturer = Build.MANUFACTURER
|
||||
private val model = Build.MODEL
|
||||
private val nowPlayingTheme: String
|
||||
private val product = Build.PRODUCT
|
||||
private val releaseVersion = Build.VERSION.RELEASE
|
||||
|
||||
@IntRange(from = 0)
|
||||
private val sdkVersion = Build.VERSION.SDK_INT
|
||||
private var versionCode = 0L
|
||||
private var versionName: String? = null
|
||||
private val selectedLang: String
|
||||
fun toMarkdown(): String {
|
||||
return """
|
||||
Device info:
|
||||
---
|
||||
<table>
|
||||
<tr><td><b>App version</b></td><td>$versionName</td></tr>
|
||||
<tr><td>App version code</td><td>$versionCode</td></tr>
|
||||
<tr><td>Android build version</td><td>$buildVersion</td></tr>
|
||||
<tr><td>Android release version</td><td>$releaseVersion</td></tr>
|
||||
<tr><td>Android SDK version</td><td>$sdkVersion</td></tr>
|
||||
<tr><td>Android build ID</td><td>$buildID</td></tr>
|
||||
<tr><td>Device brand</td><td>$brand</td></tr>
|
||||
<tr><td>Device manufacturer</td><td>$manufacturer</td></tr>
|
||||
<tr><td>Device name</td><td>$device</td></tr>
|
||||
<tr><td>Device model</td><td>$model</td></tr>
|
||||
<tr><td>Device product name</td><td>$product</td></tr>
|
||||
<tr><td>Device hardware name</td><td>$hardware</td></tr>
|
||||
<tr><td>ABIs</td><td>${Arrays.toString(abis)}</td></tr>
|
||||
<tr><td>ABIs (32bit)</td><td>${Arrays.toString(abis32Bits)}</td></tr>
|
||||
<tr><td>ABIs (64bit)</td><td>${Arrays.toString(abis64Bits)}</td></tr>
|
||||
<tr><td>Language</td><td>$selectedLang</td></tr>
|
||||
</table>
|
||||
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return """
|
||||
App version: $versionName
|
||||
App version code: $versionCode
|
||||
Android build version: $buildVersion
|
||||
Android release version: $releaseVersion
|
||||
Android SDK version: $sdkVersion
|
||||
Android build ID: $buildID
|
||||
Device brand: $brand
|
||||
Device manufacturer: $manufacturer
|
||||
Device name: $device
|
||||
Device model: $model
|
||||
Device product name: $product
|
||||
Device hardware name: $hardware
|
||||
ABIs: ${Arrays.toString(abis)}
|
||||
ABIs (32bit): ${Arrays.toString(abis32Bits)}
|
||||
ABIs (64bit): ${Arrays.toString(abis64Bits)}
|
||||
Base theme: $baseTheme
|
||||
Now playing theme: $nowPlayingTheme
|
||||
Adaptive: $isAdaptive
|
||||
System language: ${Locale.getDefault().toLanguageTag()}
|
||||
In-App Language: $selectedLang
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
init {
|
||||
val packageInfo = try {
|
||||
context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
null
|
||||
}
|
||||
if (packageInfo != null) {
|
||||
versionCode = PackageInfoCompat.getLongVersionCode(packageInfo)
|
||||
versionName = packageInfo.versionName
|
||||
} else {
|
||||
versionCode = -1
|
||||
versionName = null
|
||||
}
|
||||
baseTheme = PreferenceUtil.baseTheme
|
||||
nowPlayingTheme = context.getString(nowPlayingScreen.titleRes)
|
||||
isAdaptive = isAdaptiveColor
|
||||
selectedLang = AppCompatDelegate.getApplicationLocales().toLanguageTags()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.activities.saf;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.heinrichreimersoftware.materialintro.app.IntroActivity;
|
||||
import com.heinrichreimersoftware.materialintro.slide.SimpleSlide;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
|
||||
/** Created by hemanths on 2019-07-31. */
|
||||
public class SAFGuideActivity extends IntroActivity {
|
||||
|
||||
public static final int REQUEST_CODE_SAF_GUIDE = 98;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setButtonCtaVisible(false);
|
||||
setButtonNextVisible(false);
|
||||
setButtonBackVisible(false);
|
||||
|
||||
setButtonCtaTintMode(BUTTON_CTA_TINT_MODE_TEXT);
|
||||
|
||||
String title =
|
||||
String.format(getString(R.string.saf_guide_slide1_title), getString(R.string.app_name));
|
||||
|
||||
addSlide(
|
||||
new SimpleSlide.Builder()
|
||||
.title(title)
|
||||
.description(
|
||||
Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
|
||||
? R.string.saf_guide_slide1_description_before_o
|
||||
: R.string.saf_guide_slide1_description)
|
||||
.image(R.drawable.saf_guide_1)
|
||||
.background(code.name.monkey.appthemehelper.R.color.md_deep_purple_300)
|
||||
.backgroundDark(code.name.monkey.appthemehelper.R.color.md_deep_purple_400)
|
||||
.layout(R.layout.fragment_simple_slide_large_image)
|
||||
.build());
|
||||
addSlide(
|
||||
new SimpleSlide.Builder()
|
||||
.title(R.string.saf_guide_slide2_title)
|
||||
.description(R.string.saf_guide_slide2_description)
|
||||
.image(R.drawable.saf_guide_2)
|
||||
.background(code.name.monkey.appthemehelper.R.color.md_deep_purple_500)
|
||||
.backgroundDark(code.name.monkey.appthemehelper.R.color.md_deep_purple_600)
|
||||
.layout(R.layout.fragment_simple_slide_large_image)
|
||||
.build());
|
||||
addSlide(
|
||||
new SimpleSlide.Builder()
|
||||
.title(R.string.saf_guide_slide3_title)
|
||||
.description(R.string.saf_guide_slide3_description)
|
||||
.image(R.drawable.saf_guide_3)
|
||||
.background(code.name.monkey.appthemehelper.R.color.md_deep_purple_700)
|
||||
.backgroundDark(code.name.monkey.appthemehelper.R.color.md_deep_purple_800)
|
||||
.layout(R.layout.fragment_simple_slide_large_image)
|
||||
.build());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Bartlomiej Uliasz.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities.saf
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity.REQUEST_CODE_SAF_GUIDE
|
||||
import code.name.monkey.retromusic.util.SAFUtil
|
||||
|
||||
/** Created by buliasz on 2021-02-07. */
|
||||
class SAFRequestActivity : Activity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val intent = Intent(this, code.name.monkey.retromusic.activities.saf.SAFGuideActivity::class.java)
|
||||
startActivityForResult(intent, REQUEST_CODE_SAF_GUIDE)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, intent)
|
||||
when (requestCode) {
|
||||
REQUEST_CODE_SAF_GUIDE -> {
|
||||
SAFUtil.openTreePicker(this)
|
||||
}
|
||||
SAFUtil.REQUEST_SAF_PICK_TREE -> {
|
||||
if (resultCode == RESULT_OK) {
|
||||
SAFUtil.saveTreeUri(this, intent)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,52 +1,83 @@
|
|||
package io.github.muntashirakon.music.activities.tageditor
|
||||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities.tageditor
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.SearchManager
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import android.widget.ImageView
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.IntentSenderRequest
|
||||
import androidx.activity.result.PickVisualMediaRequest
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.appthemehelper.util.TintHelper
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.R.drawable
|
||||
import io.github.muntashirakon.music.activities.base.AbsBaseActivity
|
||||
import io.github.muntashirakon.music.activities.saf.SAFGuideActivity
|
||||
import io.github.muntashirakon.music.util.RetroUtil
|
||||
import io.github.muntashirakon.music.util.SAFUtil
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
|
||||
import code.name.monkey.retromusic.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.colorButtons
|
||||
import code.name.monkey.retromusic.extensions.hideSoftKeyboard
|
||||
import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto
|
||||
import code.name.monkey.retromusic.model.ArtworkInfo
|
||||
import code.name.monkey.retromusic.model.AudioTagInfo
|
||||
import code.name.monkey.retromusic.repository.Repository
|
||||
import code.name.monkey.retromusic.util.logD
|
||||
import code.name.monkey.retromusic.util.logE
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.android.synthetic.main.activity_album_tag_editor.*
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jaudiotagger.audio.AudioFile
|
||||
import org.jaudiotagger.audio.AudioFileIO
|
||||
import org.jaudiotagger.tag.FieldKey
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
||||
abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
|
||||
abstract val editorImage: ImageView
|
||||
val repository by inject<Repository>()
|
||||
|
||||
protected var id: Int = 0
|
||||
lateinit var saveFab: MaterialButton
|
||||
protected var id: Long = 0
|
||||
private set
|
||||
private var paletteColorPrimary: Int = 0
|
||||
private var isInNoImageMode: Boolean = false
|
||||
private var songPaths: List<String>? = null
|
||||
lateinit var saveFab: MaterialButton
|
||||
|
||||
private var savedSongPaths: List<String>? = null
|
||||
private val currentSongPath: String? = null
|
||||
private var savedTags: Map<FieldKey, String>? = null
|
||||
private var savedArtworkInfo: ArtworkInfo? = null
|
||||
private var _binding: VB? = null
|
||||
protected val binding: VB get() = _binding!!
|
||||
private var cacheFiles = listOf<File>()
|
||||
|
||||
abstract val bindingInflater: (LayoutInflater) -> VB
|
||||
|
||||
private lateinit var launcher: ActivityResultLauncher<IntentSenderRequest>
|
||||
|
||||
protected abstract fun loadImageFromFile(selectedFile: Uri?)
|
||||
|
||||
protected val show: AlertDialog
|
||||
get() =
|
||||
|
@ -59,14 +90,16 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
2 -> deleteImage()
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.action_cancel, null)
|
||||
.show()
|
||||
protected abstract val contentViewLayout: Int
|
||||
.colorButtons()
|
||||
|
||||
internal val albumArtist: String?
|
||||
get() {
|
||||
return try {
|
||||
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ALBUM_ARTIST)
|
||||
} catch (ignored: Exception) {
|
||||
} catch (e: Exception) {
|
||||
logE(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +108,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
get() {
|
||||
return try {
|
||||
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.TITLE)
|
||||
} catch (ignored: Exception) {
|
||||
} catch (e: Exception) {
|
||||
logE(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +117,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
get() {
|
||||
return try {
|
||||
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.COMPOSER)
|
||||
} catch (ignored: Exception) {
|
||||
} catch (e: Exception) {
|
||||
logE(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +127,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
get() {
|
||||
return try {
|
||||
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ALBUM)
|
||||
} catch (ignored: Exception) {
|
||||
} catch (e: Exception) {
|
||||
logE(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +137,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
get() {
|
||||
return try {
|
||||
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ARTIST)
|
||||
} catch (ignored: Exception) {
|
||||
} catch (e: Exception) {
|
||||
logE(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -110,7 +147,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
get() {
|
||||
return try {
|
||||
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ALBUM_ARTIST)
|
||||
} catch (ignored: Exception) {
|
||||
} catch (e: Exception) {
|
||||
logE(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +157,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
get() {
|
||||
return try {
|
||||
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.GENRE)
|
||||
} catch (ignored: Exception) {
|
||||
} catch (e: Exception) {
|
||||
logE(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -128,7 +167,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
get() {
|
||||
return try {
|
||||
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.YEAR)
|
||||
} catch (ignored: Exception) {
|
||||
} catch (e: Exception) {
|
||||
logE(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -137,7 +177,18 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
get() {
|
||||
return try {
|
||||
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.TRACK)
|
||||
} catch (ignored: Exception) {
|
||||
} catch (e: Exception) {
|
||||
logE(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
protected val discNumber: String?
|
||||
get() {
|
||||
return try {
|
||||
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.DISC_NO)
|
||||
} catch (e: Exception) {
|
||||
logE(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -146,7 +197,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
get() {
|
||||
return try {
|
||||
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.LYRICS)
|
||||
} catch (ignored: Exception) {
|
||||
} catch (e: Exception) {
|
||||
logE(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -164,41 +216,44 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
)
|
||||
}
|
||||
return null
|
||||
} catch (ignored: Exception) {
|
||||
} catch (e: Exception) {
|
||||
logE(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private val pickArtworkImage =
|
||||
registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
|
||||
loadImageFromFile(uri)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(contentViewLayout)
|
||||
_binding = bindingInflater.invoke(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
setTaskDescriptionColorAuto()
|
||||
|
||||
saveFab = findViewById(R.id.saveTags)
|
||||
getIntentExtras()
|
||||
|
||||
songPaths = getSongPaths()
|
||||
logD(songPaths?.size)
|
||||
if (songPaths!!.isEmpty()) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
setUpViews()
|
||||
|
||||
setStatusbarColorAuto()
|
||||
setNavigationbarColorAuto()
|
||||
setTaskDescriptionColorAuto()
|
||||
launcher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
writeToFiles(getSongUris(), cacheFiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUpViews() {
|
||||
setUpScrollView()
|
||||
setUpFab()
|
||||
setUpImageView()
|
||||
}
|
||||
|
||||
private fun setUpScrollView() {
|
||||
//observableScrollView.setScrollViewCallbacks(observableScrollViewCallbacks);
|
||||
}
|
||||
|
||||
private lateinit var items: List<String>
|
||||
|
||||
private fun setUpImageView() {
|
||||
|
@ -208,18 +263,11 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
getString(R.string.web_search),
|
||||
getString(R.string.remove_cover)
|
||||
)
|
||||
editorImage?.setOnClickListener { show }
|
||||
editorImage.setOnClickListener { show }
|
||||
}
|
||||
|
||||
private fun startImagePicker() {
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
intent.type = "image/*"
|
||||
startActivityForResult(
|
||||
Intent.createChooser(
|
||||
intent,
|
||||
getString(R.string.pick_from_local_storage)
|
||||
), REQUEST_CODE_SELECT_IMAGE
|
||||
)
|
||||
pickArtworkImage.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
|
||||
}
|
||||
|
||||
protected abstract fun loadCurrentImage()
|
||||
|
@ -229,26 +277,12 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
protected abstract fun deleteImage()
|
||||
|
||||
private fun setUpFab() {
|
||||
saveFab.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this))
|
||||
ColorStateList.valueOf(
|
||||
MaterialValueHelper.getPrimaryTextColor(
|
||||
this,
|
||||
ColorUtil.isColorLight(
|
||||
ThemeStore.accentColor(
|
||||
this
|
||||
)
|
||||
)
|
||||
)
|
||||
).apply {
|
||||
saveFab.setTextColor(this)
|
||||
saveFab.iconTint = this
|
||||
}
|
||||
saveFab.accentColor()
|
||||
saveFab.apply {
|
||||
scaleX = 0f
|
||||
scaleY = 0f
|
||||
isEnabled = false
|
||||
setOnClickListener { save() }
|
||||
TintHelper.setTintAuto(this, ThemeStore.accentColor(this@AbsTagEditorActivity), true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -257,12 +291,14 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
private fun getIntentExtras() {
|
||||
val intentExtras = intent.extras
|
||||
if (intentExtras != null) {
|
||||
id = intentExtras.getInt(EXTRA_ID)
|
||||
id = intentExtras.getLong(EXTRA_ID)
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun getSongPaths(): List<String>
|
||||
|
||||
protected abstract fun getSongUris(): List<Uri>
|
||||
|
||||
protected fun searchWebFor(vararg keys: String) {
|
||||
val stringBuilder = StringBuilder()
|
||||
for (key in keys) {
|
||||
|
@ -279,27 +315,13 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
super.onBackPressed()
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
protected fun setNoImageMode() {
|
||||
isInNoImageMode = true
|
||||
imageContainer?.visibility = View.GONE
|
||||
editorImage?.visibility = View.GONE
|
||||
editorImage?.isEnabled = false
|
||||
|
||||
setColors(
|
||||
intent.getIntExtra(
|
||||
EXTRA_PALETTE,
|
||||
ATHUtil.resolveColor(this, R.attr.colorPrimary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
protected fun dataChanged() {
|
||||
showFab()
|
||||
}
|
||||
|
@ -318,7 +340,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
|
||||
protected fun setImageBitmap(bitmap: Bitmap?, bgColor: Int) {
|
||||
if (bitmap == null) {
|
||||
editorImage.setImageResource(drawable.default_audio_art)
|
||||
editorImage.setImageResource(R.drawable.default_audio_art)
|
||||
} else {
|
||||
editorImage.setImageBitmap(bitmap)
|
||||
}
|
||||
|
@ -330,86 +352,106 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
|
|||
}
|
||||
|
||||
protected fun writeValuesToFiles(
|
||||
fieldKeyValueMap: Map<FieldKey, String>, artworkInfo: ArtworkInfo?
|
||||
fieldKeyValueMap: Map<FieldKey, String>,
|
||||
artworkInfo: ArtworkInfo?
|
||||
) {
|
||||
RetroUtil.hideSoftKeyboard(this)
|
||||
hideSoftKeyboard()
|
||||
|
||||
hideFab()
|
||||
|
||||
savedSongPaths = getSongPaths()
|
||||
savedTags = fieldKeyValueMap
|
||||
savedArtworkInfo = artworkInfo
|
||||
|
||||
if (!SAFUtil.isSAFRequired(savedSongPaths)) {
|
||||
writeTags(savedSongPaths)
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
if (SAFUtil.isSDCardAccessGranted(this)) {
|
||||
writeTags(savedSongPaths)
|
||||
} else {
|
||||
startActivityForResult(
|
||||
Intent(this, SAFGuideActivity::class.java),
|
||||
SAFGuideActivity.REQUEST_CODE_SAF_GUIDE
|
||||
logD(fieldKeyValueMap)
|
||||
GlobalScope.launch {
|
||||
if (VersionUtils.hasR()) {
|
||||
cacheFiles = TagWriter.writeTagsToFilesR(
|
||||
this@AbsTagEditorActivity, AudioTagInfo(
|
||||
songPaths,
|
||||
fieldKeyValueMap,
|
||||
artworkInfo
|
||||
)
|
||||
)
|
||||
|
||||
if (cacheFiles.isNotEmpty()) {
|
||||
val pendingIntent =
|
||||
MediaStore.createWriteRequest(contentResolver, getSongUris())
|
||||
launcher.launch(IntentSenderRequest.Builder(pendingIntent).build())
|
||||
}
|
||||
} else {
|
||||
TagWriter.writeTagsToFiles(
|
||||
this@AbsTagEditorActivity, AudioTagInfo(
|
||||
songPaths,
|
||||
fieldKeyValueMap,
|
||||
artworkInfo
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeTags(paths: List<String>?) {
|
||||
WriteTagsAsyncTask(this).execute(
|
||||
WriteTagsAsyncTask.LoadingInfo(
|
||||
paths,
|
||||
savedTags,
|
||||
savedArtworkInfo
|
||||
)
|
||||
)
|
||||
}
|
||||
GlobalScope.launch {
|
||||
if (VersionUtils.hasR()) {
|
||||
cacheFiles = TagWriter.writeTagsToFilesR(
|
||||
this@AbsTagEditorActivity, AudioTagInfo(
|
||||
paths,
|
||||
savedTags,
|
||||
savedArtworkInfo
|
||||
)
|
||||
)
|
||||
val pendingIntent = MediaStore.createWriteRequest(contentResolver, getSongUris())
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, intent)
|
||||
when (requestCode) {
|
||||
REQUEST_CODE_SELECT_IMAGE -> if (resultCode == Activity.RESULT_OK) {
|
||||
intent?.data?.let {
|
||||
loadImageFromFile(it)
|
||||
}
|
||||
}
|
||||
SAFGuideActivity.REQUEST_CODE_SAF_GUIDE -> {
|
||||
SAFUtil.openTreePicker(this)
|
||||
}
|
||||
SAFUtil.REQUEST_SAF_PICK_TREE -> {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
SAFUtil.saveTreeUri(this, intent)
|
||||
writeTags(savedSongPaths)
|
||||
}
|
||||
}
|
||||
SAFUtil.REQUEST_SAF_PICK_FILE -> {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
writeTags(Collections.singletonList(currentSongPath + SAFUtil.SEPARATOR + intent!!.dataString))
|
||||
}
|
||||
launcher.launch(IntentSenderRequest.Builder(pendingIntent).build())
|
||||
} else {
|
||||
TagWriter.writeTagsToFiles(
|
||||
this@AbsTagEditorActivity, AudioTagInfo(
|
||||
paths,
|
||||
savedTags,
|
||||
savedArtworkInfo
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun loadImageFromFile(selectedFile: Uri?)
|
||||
private lateinit var audioFile: AudioFile
|
||||
|
||||
private fun getAudioFile(path: String): AudioFile {
|
||||
return try {
|
||||
AudioFileIO.read(File(path))
|
||||
if (!this::audioFile.isInitialized) {
|
||||
audioFile = AudioFileIO.read(File(path))
|
||||
}
|
||||
audioFile
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Could not read audio file $path", e)
|
||||
AudioFile()
|
||||
}
|
||||
}
|
||||
|
||||
class ArtworkInfo constructor(val albumId: Int, val artwork: Bitmap?)
|
||||
private fun writeToFiles(songUris: List<Uri>, cacheFiles: List<File>) {
|
||||
if (cacheFiles.size == songUris.size) {
|
||||
for (i in cacheFiles.indices) {
|
||||
contentResolver.openOutputStream(songUris[i])?.use { output ->
|
||||
cacheFiles[i].inputStream().use { input ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
TagWriter.scan(this@AbsTagEditorActivity, getSongPaths())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
// Delete Cache Files
|
||||
cacheFiles.forEach { file ->
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val EXTRA_ID = "extra_id"
|
||||
const val EXTRA_PALETTE = "extra_palette"
|
||||
private val TAG = AbsTagEditorActivity::class.java.simpleName
|
||||
private const val REQUEST_CODE_SELECT_IMAGE = 1000
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities.tageditor
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.transition.Slide
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.databinding.ActivityAlbumTagEditorBinding
|
||||
import code.name.monkey.retromusic.extensions.*
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
|
||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
||||
import code.name.monkey.retromusic.model.ArtworkInfo
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.ImageUtil
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.RetroColorUtil.generatePalette
|
||||
import code.name.monkey.retromusic.util.RetroColorUtil.getColor
|
||||
import code.name.monkey.retromusic.util.logD
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.request.target.ImageViewTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import org.jaudiotagger.tag.FieldKey
|
||||
import java.util.*
|
||||
|
||||
class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBinding>() {
|
||||
|
||||
override val bindingInflater: (LayoutInflater) -> ActivityAlbumTagEditorBinding =
|
||||
ActivityAlbumTagEditorBinding::inflate
|
||||
|
||||
private fun windowEnterTransition() {
|
||||
val slide = Slide()
|
||||
slide.excludeTarget(R.id.appBarLayout, true)
|
||||
slide.excludeTarget(R.id.status_bar, true)
|
||||
slide.excludeTarget(android.R.id.statusBarBackground, true)
|
||||
slide.excludeTarget(android.R.id.navigationBarBackground, true)
|
||||
|
||||
window.enterTransition = slide
|
||||
}
|
||||
|
||||
private var albumArtBitmap: Bitmap? = null
|
||||
private var deleteAlbumArt: Boolean = false
|
||||
|
||||
private fun setupToolbar() {
|
||||
setSupportActionBar(binding.toolbar)
|
||||
binding.appBarLayout?.statusBarForeground =
|
||||
MaterialShapeDrawable.createWithElevationOverlay(this)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
window.sharedElementsUseOverlay = true
|
||||
binding.imageContainer.transitionName = getString(R.string.transition_album_art)
|
||||
windowEnterTransition()
|
||||
setUpViews()
|
||||
setupToolbar()
|
||||
}
|
||||
|
||||
private fun setUpViews() {
|
||||
fillViewsWithFileTags()
|
||||
|
||||
binding.yearContainer.setTint(false)
|
||||
binding.genreContainer.setTint(false)
|
||||
binding.albumTitleContainer.setTint(false)
|
||||
binding.albumArtistContainer.setTint(false)
|
||||
|
||||
binding.albumText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.albumArtistText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.genreTitle.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.yearTitle.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
}
|
||||
|
||||
private fun fillViewsWithFileTags() {
|
||||
binding.albumText.setText(albumTitle)
|
||||
binding.albumArtistText.setText(albumArtistName)
|
||||
binding.genreTitle.setText(genreName)
|
||||
binding.yearTitle.setText(songYear)
|
||||
logD(albumTitle + albumArtistName)
|
||||
}
|
||||
|
||||
override fun loadCurrentImage() {
|
||||
val bitmap = albumArt
|
||||
setImageBitmap(
|
||||
bitmap,
|
||||
getColor(
|
||||
generatePalette(bitmap),
|
||||
defaultFooterColor()
|
||||
)
|
||||
)
|
||||
deleteAlbumArt = false
|
||||
}
|
||||
|
||||
private fun toastLoadingFailed() {
|
||||
showToast(R.string.could_not_download_album_cover)
|
||||
}
|
||||
|
||||
override fun searchImageOnWeb() {
|
||||
searchWebFor(binding.albumText.text.toString(), binding.albumArtistText.text.toString())
|
||||
}
|
||||
|
||||
override fun deleteImage() {
|
||||
setImageBitmap(
|
||||
BitmapFactory.decodeResource(resources, R.drawable.default_audio_art),
|
||||
defaultFooterColor()
|
||||
)
|
||||
deleteAlbumArt = true
|
||||
dataChanged()
|
||||
}
|
||||
|
||||
override fun loadImageFromFile(selectedFile: Uri?) {
|
||||
Glide.with(this@AlbumTagEditorActivity)
|
||||
.asBitmapPalette()
|
||||
.load(selectedFile)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
|
||||
.into(object : ImageViewTarget<BitmapPaletteWrapper>(binding.editorImage) {
|
||||
override fun onResourceReady(
|
||||
resource: BitmapPaletteWrapper,
|
||||
transition: Transition<in BitmapPaletteWrapper>?
|
||||
) {
|
||||
getColor(resource.palette, Color.TRANSPARENT)
|
||||
albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
|
||||
setImageBitmap(
|
||||
albumArtBitmap,
|
||||
getColor(
|
||||
resource.palette,
|
||||
defaultFooterColor()
|
||||
)
|
||||
)
|
||||
deleteAlbumArt = false
|
||||
dataChanged()
|
||||
setResult(Activity.RESULT_OK)
|
||||
}
|
||||
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(errorDrawable)
|
||||
showToast(R.string.error_load_failed, Toast.LENGTH_LONG)
|
||||
}
|
||||
|
||||
override fun setResource(resource: BitmapPaletteWrapper?) {}
|
||||
})
|
||||
}
|
||||
|
||||
override fun save() {
|
||||
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
|
||||
fieldKeyValueMap[FieldKey.ALBUM] = binding.albumText.text.toString()
|
||||
// android seems not to recognize album_artist field so we additionally write the normal artist field
|
||||
fieldKeyValueMap[FieldKey.ARTIST] = binding.albumArtistText.text.toString()
|
||||
fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = binding.albumArtistText.text.toString()
|
||||
fieldKeyValueMap[FieldKey.GENRE] = binding.genreTitle.text.toString()
|
||||
fieldKeyValueMap[FieldKey.YEAR] = binding.yearTitle.text.toString()
|
||||
|
||||
writeValuesToFiles(
|
||||
fieldKeyValueMap,
|
||||
when {
|
||||
deleteAlbumArt -> ArtworkInfo(id, null)
|
||||
albumArtBitmap == null -> null
|
||||
else -> ArtworkInfo(id, albumArtBitmap!!)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun getSongPaths(): List<String> {
|
||||
return repository.albumById(id).songs
|
||||
.map(Song::data)
|
||||
}
|
||||
|
||||
override fun getSongUris(): List<Uri> = repository.albumById(id).songs.map {
|
||||
MusicUtil.getSongFileUri(it.id)
|
||||
}
|
||||
|
||||
override fun setColors(color: Int) {
|
||||
super.setColors(color)
|
||||
saveFab.backgroundTintList = ColorStateList.valueOf(color)
|
||||
saveFab.backgroundTintList = ColorStateList.valueOf(color)
|
||||
ColorStateList.valueOf(
|
||||
MaterialValueHelper.getPrimaryTextColor(
|
||||
this,
|
||||
color.isColorLight
|
||||
)
|
||||
).also {
|
||||
saveFab.iconTint = it
|
||||
saveFab.setTextColor(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override val editorImage: ImageView
|
||||
get() = binding.editorImage
|
||||
|
||||
companion object {
|
||||
|
||||
val TAG: String = AlbumTagEditorActivity::class.java.simpleName
|
||||
}
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.activities.tageditor
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.databinding.ActivitySongTagEditorBinding
|
||||
import code.name.monkey.retromusic.extensions.*
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
|
||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
||||
import code.name.monkey.retromusic.model.ArtworkInfo
|
||||
import code.name.monkey.retromusic.repository.SongRepository
|
||||
import code.name.monkey.retromusic.util.ImageUtil
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.RetroColorUtil
|
||||
import code.name.monkey.retromusic.util.logD
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.request.target.ImageViewTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import org.jaudiotagger.tag.FieldKey
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.util.*
|
||||
|
||||
class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>() {
|
||||
|
||||
override val bindingInflater: (LayoutInflater) -> ActivitySongTagEditorBinding =
|
||||
ActivitySongTagEditorBinding::inflate
|
||||
|
||||
|
||||
private val songRepository by inject<SongRepository>()
|
||||
|
||||
private var albumArtBitmap: Bitmap? = null
|
||||
private var deleteAlbumArt: Boolean = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setUpViews()
|
||||
setSupportActionBar(binding.toolbar)
|
||||
binding.appBarLayout?.statusBarForeground =
|
||||
MaterialShapeDrawable.createWithElevationOverlay(this)
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private fun setUpViews() {
|
||||
fillViewsWithFileTags()
|
||||
binding.songTextContainer.setTint(false)
|
||||
binding.composerContainer.setTint(false)
|
||||
binding.albumTextContainer.setTint(false)
|
||||
binding.artistContainer.setTint(false)
|
||||
binding.albumArtistContainer.setTint(false)
|
||||
binding.yearContainer.setTint(false)
|
||||
binding.genreContainer.setTint(false)
|
||||
binding.trackNumberContainer.setTint(false)
|
||||
binding.discNumberContainer.setTint(false)
|
||||
binding.lyricsContainer.setTint(false)
|
||||
|
||||
binding.songText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.albumText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.albumArtistText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.artistText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.genreText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.yearText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.trackNumberText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.discNumberText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.lyricsText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
binding.songComposerText.appHandleColor().doAfterTextChanged { dataChanged() }
|
||||
}
|
||||
|
||||
private fun fillViewsWithFileTags() {
|
||||
binding.songText.setText(songTitle)
|
||||
binding.albumArtistText.setText(albumArtist)
|
||||
binding.albumText.setText(albumTitle)
|
||||
binding.artistText.setText(artistName)
|
||||
binding.genreText.setText(genreName)
|
||||
binding.yearText.setText(songYear)
|
||||
binding.trackNumberText.setText(trackNumber)
|
||||
binding.discNumberText.setText(discNumber)
|
||||
binding.lyricsText.setText(lyrics)
|
||||
binding.songComposerText.setText(composer)
|
||||
logD(songTitle + songYear)
|
||||
}
|
||||
|
||||
override fun loadCurrentImage() {
|
||||
val bitmap = albumArt
|
||||
setImageBitmap(
|
||||
bitmap,
|
||||
RetroColorUtil.getColor(
|
||||
RetroColorUtil.generatePalette(bitmap),
|
||||
defaultFooterColor()
|
||||
)
|
||||
)
|
||||
deleteAlbumArt = false
|
||||
}
|
||||
|
||||
override fun searchImageOnWeb() {
|
||||
searchWebFor(binding.songText.text.toString(), binding.artistText.text.toString())
|
||||
}
|
||||
|
||||
override fun deleteImage() {
|
||||
setImageBitmap(
|
||||
BitmapFactory.decodeResource(resources, R.drawable.default_audio_art),
|
||||
defaultFooterColor()
|
||||
)
|
||||
deleteAlbumArt = true
|
||||
dataChanged()
|
||||
}
|
||||
|
||||
override fun setColors(color: Int) {
|
||||
super.setColors(color)
|
||||
saveFab.backgroundTintList = ColorStateList.valueOf(color)
|
||||
ColorStateList.valueOf(
|
||||
MaterialValueHelper.getPrimaryTextColor(
|
||||
this,
|
||||
color.isColorLight
|
||||
)
|
||||
).also {
|
||||
saveFab.iconTint = it
|
||||
saveFab.setTextColor(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun save() {
|
||||
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
|
||||
fieldKeyValueMap[FieldKey.TITLE] = binding.songText.text.toString()
|
||||
fieldKeyValueMap[FieldKey.ALBUM] = binding.albumText.text.toString()
|
||||
fieldKeyValueMap[FieldKey.ARTIST] = binding.artistText.text.toString()
|
||||
fieldKeyValueMap[FieldKey.GENRE] = binding.genreText.text.toString()
|
||||
fieldKeyValueMap[FieldKey.YEAR] = binding.yearText.text.toString()
|
||||
fieldKeyValueMap[FieldKey.TRACK] = binding.trackNumberText.text.toString()
|
||||
fieldKeyValueMap[FieldKey.DISC_NO] = binding.discNumberText.text.toString()
|
||||
fieldKeyValueMap[FieldKey.LYRICS] = binding.lyricsText.text.toString()
|
||||
fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = binding.albumArtistText.text.toString()
|
||||
fieldKeyValueMap[FieldKey.COMPOSER] = binding.songComposerText.text.toString()
|
||||
writeValuesToFiles(
|
||||
fieldKeyValueMap, when {
|
||||
deleteAlbumArt -> ArtworkInfo(id, null)
|
||||
albumArtBitmap == null -> null
|
||||
else -> ArtworkInfo(id, albumArtBitmap!!)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun getSongPaths(): List<String> = listOf(songRepository.song(id).data)
|
||||
|
||||
override fun getSongUris(): List<Uri> = listOf(MusicUtil.getSongFileUri(id))
|
||||
|
||||
override fun loadImageFromFile(selectedFile: Uri?) {
|
||||
Glide.with(this@SongTagEditorActivity)
|
||||
.asBitmapPalette()
|
||||
.load(selectedFile)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(true)
|
||||
.into(object : ImageViewTarget<BitmapPaletteWrapper>(binding.editorImage) {
|
||||
override fun onResourceReady(
|
||||
resource: BitmapPaletteWrapper,
|
||||
transition: Transition<in BitmapPaletteWrapper>?
|
||||
) {
|
||||
RetroColorUtil.getColor(resource.palette, Color.TRANSPARENT)
|
||||
albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
|
||||
setImageBitmap(
|
||||
albumArtBitmap,
|
||||
RetroColorUtil.getColor(
|
||||
resource.palette,
|
||||
defaultFooterColor()
|
||||
)
|
||||
)
|
||||
deleteAlbumArt = false
|
||||
dataChanged()
|
||||
setResult(Activity.RESULT_OK)
|
||||
}
|
||||
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(errorDrawable)
|
||||
showToast(R.string.error_load_failed, Toast.LENGTH_LONG)
|
||||
}
|
||||
|
||||
override fun setResource(resource: BitmapPaletteWrapper?) {}
|
||||
})
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG: String = SongTagEditorActivity::class.java.simpleName
|
||||
}
|
||||
|
||||
override val editorImage: ImageView
|
||||
get() = binding.editorImage
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
package code.name.monkey.retromusic.activities.tageditor
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.media.MediaScannerConnection
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.showToast
|
||||
import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener
|
||||
import code.name.monkey.retromusic.model.AudioTagInfo
|
||||
import code.name.monkey.retromusic.util.MusicUtil.createAlbumArtFile
|
||||
import code.name.monkey.retromusic.util.MusicUtil.deleteAlbumArt
|
||||
import code.name.monkey.retromusic.util.MusicUtil.insertAlbumArt
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jaudiotagger.audio.AudioFileIO
|
||||
import org.jaudiotagger.audio.exceptions.CannotReadException
|
||||
import org.jaudiotagger.audio.exceptions.CannotWriteException
|
||||
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
|
||||
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException
|
||||
import org.jaudiotagger.tag.FieldDataInvalidException
|
||||
import org.jaudiotagger.tag.TagException
|
||||
import org.jaudiotagger.tag.images.AndroidArtwork
|
||||
import org.jaudiotagger.tag.images.Artwork
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
class TagWriter {
|
||||
|
||||
companion object {
|
||||
|
||||
suspend fun scan(context: Context, toBeScanned: List<String?>?) {
|
||||
if (toBeScanned.isNullOrEmpty()) {
|
||||
Log.i("scan", "scan: Empty")
|
||||
context.showToast("Scan file from folder")
|
||||
return
|
||||
}
|
||||
MediaScannerConnection.scanFile(
|
||||
context,
|
||||
toBeScanned.toTypedArray(),
|
||||
null,
|
||||
withContext(Dispatchers.Main) {
|
||||
if (context is Activity) UpdateToastMediaScannerCompletionListener(
|
||||
context, toBeScanned
|
||||
) else null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun writeTagsToFiles(context: Context, info: AudioTagInfo) {
|
||||
withContext(Dispatchers.IO) {
|
||||
var artwork: Artwork? = null
|
||||
var albumArtFile: File? = null
|
||||
if (info.artworkInfo?.artwork != null) {
|
||||
try {
|
||||
albumArtFile = createAlbumArtFile(context).canonicalFile
|
||||
info.artworkInfo.artwork.compress(
|
||||
Bitmap.CompressFormat.JPEG,
|
||||
100,
|
||||
albumArtFile.outputStream()
|
||||
)
|
||||
artwork = AndroidArtwork.createArtworkFromFile(albumArtFile)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
var wroteArtwork = false
|
||||
var deletedArtwork = false
|
||||
for (filePath in info.filePaths!!) {
|
||||
try {
|
||||
val audioFile = AudioFileIO.read(File(filePath))
|
||||
val tag = audioFile.tagOrCreateAndSetDefault
|
||||
if (info.fieldKeyValueMap != null) {
|
||||
for ((key, value) in info.fieldKeyValueMap) {
|
||||
try {
|
||||
tag.setField(key, value)
|
||||
} catch (e: FieldDataInvalidException) {
|
||||
withContext(Dispatchers.Main) {
|
||||
context.showToast(R.string.could_not_write_tags_to_file)
|
||||
}
|
||||
return@withContext listOf<File>()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (info.artworkInfo != null) {
|
||||
if (info.artworkInfo.artwork == null) {
|
||||
tag.deleteArtworkField()
|
||||
deletedArtwork = true
|
||||
} else if (artwork != null) {
|
||||
tag.deleteArtworkField()
|
||||
tag.setField(artwork)
|
||||
wroteArtwork = true
|
||||
}
|
||||
}
|
||||
audioFile.commit()
|
||||
} catch (e: CannotReadException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: CannotWriteException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: TagException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: ReadOnlyFileException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: InvalidAudioFrameException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
if (wroteArtwork) {
|
||||
insertAlbumArt(context, info.artworkInfo!!.albumId, albumArtFile!!.path)
|
||||
} else if (deletedArtwork) {
|
||||
deleteAlbumArt(context, info.artworkInfo!!.albumId)
|
||||
}
|
||||
scan(context, info.filePaths)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
suspend fun writeTagsToFilesR(context: Context, info: AudioTagInfo): List<File> =
|
||||
withContext(Dispatchers.IO) {
|
||||
val cacheFiles = mutableListOf<File>()
|
||||
var artwork: Artwork? = null
|
||||
var albumArtFile: File? = null
|
||||
if (info.artworkInfo?.artwork != null) {
|
||||
try {
|
||||
albumArtFile = createAlbumArtFile(context).canonicalFile
|
||||
info.artworkInfo.artwork.compress(
|
||||
Bitmap.CompressFormat.JPEG,
|
||||
100,
|
||||
albumArtFile.outputStream()
|
||||
)
|
||||
artwork = AndroidArtwork.createArtworkFromFile(albumArtFile)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
var wroteArtwork = false
|
||||
var deletedArtwork = false
|
||||
for (filePath in info.filePaths!!) {
|
||||
try {
|
||||
val originFile = File(filePath)
|
||||
val cacheFile = File(context.cacheDir, originFile.name)
|
||||
cacheFiles.add(cacheFile)
|
||||
originFile.inputStream().use { input ->
|
||||
cacheFile.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
val audioFile = AudioFileIO.read(cacheFile)
|
||||
val tag = audioFile.tagOrCreateAndSetDefault
|
||||
if (info.fieldKeyValueMap != null) {
|
||||
for ((key, value) in info.fieldKeyValueMap) {
|
||||
try {
|
||||
tag.setField(key, value)
|
||||
} catch (e: FieldDataInvalidException) {
|
||||
withContext(Dispatchers.Main) {
|
||||
context.showToast(R.string.could_not_write_tags_to_file)
|
||||
}
|
||||
return@withContext listOf<File>()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (info.artworkInfo != null) {
|
||||
if (info.artworkInfo.artwork == null) {
|
||||
tag.deleteArtworkField()
|
||||
deletedArtwork = true
|
||||
} else if (artwork != null) {
|
||||
tag.deleteArtworkField()
|
||||
tag.setField(artwork)
|
||||
wroteArtwork = true
|
||||
}
|
||||
}
|
||||
audioFile.commit()
|
||||
} catch (e: CannotReadException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: CannotWriteException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: TagException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: ReadOnlyFileException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: InvalidAudioFrameException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
if (wroteArtwork) {
|
||||
insertAlbumArt(context, info.artworkInfo!!.albumId, albumArtFile!!.path)
|
||||
} else if (deletedArtwork) {
|
||||
deleteAlbumArt(context, info.artworkInfo!!.albumId)
|
||||
}
|
||||
cacheFiles
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*/
|
||||
package code.name.monkey.retromusic.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.ColorStateList
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.databinding.PreferenceDialogLibraryCategoriesListitemBinding
|
||||
import code.name.monkey.retromusic.extensions.showToast
|
||||
import code.name.monkey.retromusic.model.CategoryInfo
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.SwipeAndDragHelper
|
||||
import code.name.monkey.retromusic.util.SwipeAndDragHelper.ActionCompletionContract
|
||||
|
||||
class CategoryInfoAdapter : RecyclerView.Adapter<code.name.monkey.retromusic.adapter.CategoryInfoAdapter.ViewHolder>(),
|
||||
ActionCompletionContract {
|
||||
var categoryInfos: MutableList<CategoryInfo> =
|
||||
PreferenceUtil.libraryCategory.toMutableList()
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
set(value) {
|
||||
field = value
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
private val touchHelper: ItemTouchHelper
|
||||
fun attachToRecyclerView(recyclerView: RecyclerView?) {
|
||||
touchHelper.attachToRecyclerView(recyclerView)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return categoryInfos.size
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val categoryInfo = categoryInfos[position]
|
||||
holder.binding.checkbox.isChecked = categoryInfo.visible
|
||||
holder.binding.title.text =
|
||||
holder.binding.title.resources.getString(categoryInfo.category.stringRes)
|
||||
holder.itemView.setOnClickListener {
|
||||
if (!(categoryInfo.visible && isLastCheckedCategory(categoryInfo))) {
|
||||
categoryInfo.visible = !categoryInfo.visible
|
||||
holder.binding.checkbox.isChecked = categoryInfo.visible
|
||||
} else {
|
||||
holder.itemView.context.showToast(R.string.you_have_to_select_at_least_one_category)
|
||||
}
|
||||
}
|
||||
holder.binding.dragView.setOnTouchListener { _: View?, event: MotionEvent ->
|
||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||
touchHelper.startDrag(holder)
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup, viewType: Int
|
||||
): ViewHolder {
|
||||
return ViewHolder(
|
||||
PreferenceDialogLibraryCategoriesListitemBinding.inflate(
|
||||
LayoutInflater.from(
|
||||
parent.context
|
||||
), parent, false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onViewMoved(oldPosition: Int, newPosition: Int) {
|
||||
val categoryInfo = categoryInfos[oldPosition]
|
||||
categoryInfos.removeAt(oldPosition)
|
||||
categoryInfos.add(newPosition, categoryInfo)
|
||||
notifyItemMoved(oldPosition, newPosition)
|
||||
}
|
||||
|
||||
private fun isLastCheckedCategory(categoryInfo: CategoryInfo): Boolean {
|
||||
if (categoryInfo.visible) {
|
||||
for (c in categoryInfos) {
|
||||
if (c !== categoryInfo && c.visible) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
class ViewHolder(val binding: PreferenceDialogLibraryCategoriesListitemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
init {
|
||||
binding.checkbox.buttonTintList =
|
||||
ColorStateList.valueOf(accentColor(binding.checkbox.context))
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
val swipeAndDragHelper = SwipeAndDragHelper(this)
|
||||
touchHelper = ItemTouchHelper(swipeAndDragHelper)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewOutlineProvider
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.databinding.ItemGenreBinding
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
|
||||
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
|
||||
import code.name.monkey.retromusic.interfaces.IGenreClickListener
|
||||
import code.name.monkey.retromusic.model.Genre
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
|
||||
import com.bumptech.glide.Glide
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* @author Hemanth S (h4h13).
|
||||
*/
|
||||
|
||||
class GenreAdapter(
|
||||
private val activity: FragmentActivity,
|
||||
var dataSet: List<Genre>,
|
||||
private val listener: IGenreClickListener
|
||||
) : RecyclerView.Adapter<GenreAdapter.ViewHolder>() {
|
||||
|
||||
init {
|
||||
this.setHasStableIds(true)
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return dataSet[position].id
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return ViewHolder(ItemGenreBinding.inflate(LayoutInflater.from(activity), parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val genre = dataSet[position]
|
||||
holder.binding.title.text = genre.name
|
||||
holder.binding.text.text = String.format(
|
||||
Locale.getDefault(),
|
||||
"%d %s",
|
||||
genre.songCount,
|
||||
if (genre.songCount > 1) activity.getString(R.string.songs) else activity.getString(R.string.song)
|
||||
)
|
||||
loadGenreImage(genre, holder)
|
||||
}
|
||||
|
||||
private fun loadGenreImage(genre: Genre, holder: GenreAdapter.ViewHolder) {
|
||||
val genreSong = MusicUtil.songByGenre(genre.id)
|
||||
Glide.with(activity)
|
||||
.asBitmapPalette()
|
||||
.songCoverOptions(genreSong)
|
||||
.load(RetroGlideExtension.getSongModel(genreSong))
|
||||
.into(object : RetroMusicColoredTarget(holder.binding.image) {
|
||||
override fun onColorReady(colors: MediaNotificationProcessor) {
|
||||
setColors(holder, colors)
|
||||
}
|
||||
})
|
||||
// Just for a bit of shadow around image
|
||||
holder.binding.image.outlineProvider = ViewOutlineProvider.BOUNDS
|
||||
}
|
||||
|
||||
private fun setColors(holder: ViewHolder, color: MediaNotificationProcessor) {
|
||||
holder.binding.imageContainerCard.setCardBackgroundColor(color.backgroundColor)
|
||||
holder.binding.title.setTextColor(color.primaryTextColor)
|
||||
holder.binding.text.setTextColor(color.secondaryTextColor)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return dataSet.size
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun swapDataSet(list: List<Genre>) {
|
||||
dataSet = list
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
inner class ViewHolder(val binding: ItemGenreBinding) : RecyclerView.ViewHolder(binding.root),
|
||||
View.OnClickListener {
|
||||
override fun onClick(v: View?) {
|
||||
listener.onClickGenre(dataSet[layoutPosition], itemView)
|
||||
}
|
||||
|
||||
init {
|
||||
itemView.setOnClickListener(this)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.findFragment
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.FragmentNavigatorExtras
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import code.name.monkey.retromusic.*
|
||||
import code.name.monkey.retromusic.adapter.album.AlbumAdapter
|
||||
import code.name.monkey.retromusic.adapter.artist.ArtistAdapter
|
||||
import code.name.monkey.retromusic.adapter.song.SongAdapter
|
||||
import code.name.monkey.retromusic.fragments.home.HomeFragment
|
||||
import code.name.monkey.retromusic.interfaces.IAlbumClickListener
|
||||
import code.name.monkey.retromusic.interfaces.IArtistClickListener
|
||||
import code.name.monkey.retromusic.model.Album
|
||||
import code.name.monkey.retromusic.model.Artist
|
||||
import code.name.monkey.retromusic.model.Home
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
|
||||
class HomeAdapter(private val activity: AppCompatActivity) :
|
||||
RecyclerView.Adapter<RecyclerView.ViewHolder>(), IArtistClickListener, IAlbumClickListener {
|
||||
|
||||
private var list = listOf<Home>()
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return list[position].homeSection
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val layout =
|
||||
LayoutInflater.from(activity).inflate(R.layout.section_recycler_view, parent, false)
|
||||
return when (viewType) {
|
||||
RECENT_ARTISTS, TOP_ARTISTS -> ArtistViewHolder(layout)
|
||||
FAVOURITES -> PlaylistViewHolder(layout)
|
||||
TOP_ALBUMS, RECENT_ALBUMS -> AlbumViewHolder(layout)
|
||||
else -> {
|
||||
ArtistViewHolder(layout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val home = list[position]
|
||||
when (getItemViewType(position)) {
|
||||
RECENT_ALBUMS -> {
|
||||
val viewHolder = holder as AlbumViewHolder
|
||||
viewHolder.bindView(home)
|
||||
viewHolder.clickableArea.setOnClickListener {
|
||||
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
|
||||
activity.findNavController(R.id.fragment_container).navigate(
|
||||
R.id.detailListFragment,
|
||||
bundleOf("type" to RECENT_ALBUMS)
|
||||
)
|
||||
}
|
||||
}
|
||||
TOP_ALBUMS -> {
|
||||
val viewHolder = holder as AlbumViewHolder
|
||||
viewHolder.bindView(home)
|
||||
viewHolder.clickableArea.setOnClickListener {
|
||||
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
|
||||
activity.findNavController(R.id.fragment_container).navigate(
|
||||
R.id.detailListFragment,
|
||||
bundleOf("type" to TOP_ALBUMS)
|
||||
)
|
||||
}
|
||||
}
|
||||
RECENT_ARTISTS -> {
|
||||
val viewHolder = holder as ArtistViewHolder
|
||||
viewHolder.bindView(home)
|
||||
viewHolder.clickableArea.setOnClickListener {
|
||||
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
|
||||
activity.findNavController(R.id.fragment_container).navigate(
|
||||
R.id.detailListFragment,
|
||||
bundleOf("type" to RECENT_ARTISTS)
|
||||
)
|
||||
}
|
||||
}
|
||||
TOP_ARTISTS -> {
|
||||
val viewHolder = holder as ArtistViewHolder
|
||||
viewHolder.bindView(home)
|
||||
viewHolder.clickableArea.setOnClickListener {
|
||||
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
|
||||
activity.findNavController(R.id.fragment_container).navigate(
|
||||
R.id.detailListFragment,
|
||||
bundleOf("type" to TOP_ARTISTS)
|
||||
)
|
||||
}
|
||||
}
|
||||
FAVOURITES -> {
|
||||
val viewHolder = holder as PlaylistViewHolder
|
||||
viewHolder.bindView(home)
|
||||
viewHolder.clickableArea.setOnClickListener {
|
||||
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
|
||||
activity.findNavController(R.id.fragment_container).navigate(
|
||||
R.id.detailListFragment,
|
||||
bundleOf("type" to FAVOURITES)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return list.size
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun swapData(sections: List<Home>) {
|
||||
list = sections
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private inner class AlbumViewHolder(view: View) : AbsHomeViewItem(view) {
|
||||
fun bindView(home: Home) {
|
||||
title.setText(home.titleRes)
|
||||
recyclerView.apply {
|
||||
adapter = albumAdapter(home.arrayList as List<Album>)
|
||||
layoutManager = gridLayoutManager()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private inner class ArtistViewHolder(view: View) : AbsHomeViewItem(view) {
|
||||
fun bindView(home: Home) {
|
||||
title.setText(home.titleRes)
|
||||
recyclerView.apply {
|
||||
layoutManager = linearLayoutManager()
|
||||
adapter = artistsAdapter(home.arrayList as List<Artist>)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) {
|
||||
fun bindView(home: Home) {
|
||||
title.setText(home.titleRes)
|
||||
recyclerView.apply {
|
||||
val songAdapter = SongAdapter(
|
||||
activity,
|
||||
home.arrayList as MutableList<Song>,
|
||||
R.layout.item_favourite_card
|
||||
)
|
||||
layoutManager = linearLayoutManager()
|
||||
adapter = songAdapter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class AbsHomeViewItem(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val recyclerView: RecyclerView = itemView.findViewById(R.id.recyclerView)
|
||||
val title: AppCompatTextView = itemView.findViewById(R.id.title)
|
||||
val clickableArea: ViewGroup = itemView.findViewById(R.id.clickable_area)
|
||||
}
|
||||
|
||||
private fun artistsAdapter(artists: List<Artist>) =
|
||||
ArtistAdapter(activity, artists, PreferenceUtil.homeArtistGridStyle, this)
|
||||
|
||||
private fun albumAdapter(albums: List<Album>) =
|
||||
AlbumAdapter(activity, albums, PreferenceUtil.homeAlbumGridStyle, this)
|
||||
|
||||
private fun gridLayoutManager() =
|
||||
GridLayoutManager(activity, 1, GridLayoutManager.HORIZONTAL, false)
|
||||
|
||||
private fun linearLayoutManager() =
|
||||
LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
|
||||
|
||||
override fun onArtist(artistId: Long, view: View) {
|
||||
activity.findNavController(R.id.fragment_container).navigate(
|
||||
R.id.artistDetailsFragment,
|
||||
bundleOf(EXTRA_ARTIST_ID to artistId),
|
||||
null,
|
||||
FragmentNavigatorExtras(
|
||||
view to artistId.toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onAlbumClick(albumId: Long, view: View) {
|
||||
activity.findNavController(R.id.fragment_container).navigate(
|
||||
R.id.albumDetailsFragment,
|
||||
bundleOf(EXTRA_ALBUM_ID to albumId),
|
||||
null,
|
||||
FragmentNavigatorExtras(
|
||||
view to albumId.toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.retromusic.*
|
||||
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
|
||||
import code.name.monkey.retromusic.db.PlaylistWithSongs
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.albumCoverOptions
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.artistImageOptions
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.menu.SongMenuHelper
|
||||
import code.name.monkey.retromusic.model.Album
|
||||
import code.name.monkey.retromusic.model.Artist
|
||||
import code.name.monkey.retromusic.model.Genre
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import com.bumptech.glide.Glide
|
||||
import java.util.*
|
||||
|
||||
class SearchAdapter(
|
||||
private val activity: FragmentActivity,
|
||||
private var dataSet: List<Any>
|
||||
) : RecyclerView.Adapter<SearchAdapter.ViewHolder>() {
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun swapDataSet(dataSet: List<Any>) {
|
||||
this.dataSet = dataSet
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
if (dataSet[position] is Album) return ALBUM
|
||||
if (dataSet[position] is Artist) return if ((dataSet[position] as Artist).isAlbumArtist) ALBUM_ARTIST else ARTIST
|
||||
if (dataSet[position] is Genre) return GENRE
|
||||
if (dataSet[position] is PlaylistWithSongs) return PLAYLIST
|
||||
return if (dataSet[position] is Song) SONG else HEADER
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return when (viewType) {
|
||||
HEADER -> ViewHolder(
|
||||
LayoutInflater.from(activity).inflate(
|
||||
R.layout.sub_header,
|
||||
parent,
|
||||
false
|
||||
), viewType
|
||||
)
|
||||
|
||||
ALBUM, ARTIST, ALBUM_ARTIST -> ViewHolder(
|
||||
LayoutInflater.from(activity).inflate(
|
||||
R.layout.item_list_big,
|
||||
parent,
|
||||
false
|
||||
), viewType
|
||||
)
|
||||
|
||||
else -> ViewHolder(
|
||||
LayoutInflater.from(activity).inflate(R.layout.item_list, parent, false),
|
||||
viewType
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
when (getItemViewType(position)) {
|
||||
ALBUM -> {
|
||||
holder.imageTextContainer?.isVisible = true
|
||||
val album = dataSet[position] as Album
|
||||
holder.title?.text = album.title
|
||||
holder.text?.text = album.artistName
|
||||
Glide.with(activity).asDrawable().albumCoverOptions(album.safeGetFirstSong())
|
||||
.load(RetroGlideExtension.getSongModel(album.safeGetFirstSong()))
|
||||
.into(holder.image!!)
|
||||
}
|
||||
|
||||
ARTIST -> {
|
||||
holder.imageTextContainer?.isVisible = true
|
||||
val artist = dataSet[position] as Artist
|
||||
holder.title?.text = artist.name
|
||||
holder.text?.text = MusicUtil.getArtistInfoString(activity, artist)
|
||||
Glide.with(activity).asDrawable().artistImageOptions(artist).load(
|
||||
RetroGlideExtension.getArtistModel(artist)
|
||||
).into(holder.image!!)
|
||||
}
|
||||
|
||||
SONG -> {
|
||||
holder.imageTextContainer?.isVisible = true
|
||||
val song = dataSet[position] as Song
|
||||
holder.title?.text = song.title
|
||||
holder.text?.text = song.albumName
|
||||
Glide.with(activity).asDrawable().songCoverOptions(song)
|
||||
.load(RetroGlideExtension.getSongModel(song)).into(holder.image!!)
|
||||
}
|
||||
|
||||
GENRE -> {
|
||||
val genre = dataSet[position] as Genre
|
||||
holder.title?.text = genre.name
|
||||
holder.text?.text = String.format(
|
||||
Locale.getDefault(),
|
||||
"%d %s",
|
||||
genre.songCount,
|
||||
if (genre.songCount > 1) activity.getString(R.string.songs) else activity.getString(
|
||||
R.string.song
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
PLAYLIST -> {
|
||||
val playlist = dataSet[position] as PlaylistWithSongs
|
||||
holder.title?.text = playlist.playlistEntity.playlistName
|
||||
//holder.text?.text = MusicUtil.playlistInfoString(activity, playlist.songs)
|
||||
}
|
||||
|
||||
ALBUM_ARTIST -> {
|
||||
holder.imageTextContainer?.isVisible = true
|
||||
val artist = dataSet[position] as Artist
|
||||
holder.title?.text = artist.name
|
||||
holder.text?.text = MusicUtil.getArtistInfoString(activity, artist)
|
||||
Glide.with(activity).asDrawable().artistImageOptions(artist).load(
|
||||
RetroGlideExtension.getArtistModel(artist)
|
||||
).into(holder.image!!)
|
||||
}
|
||||
|
||||
else -> {
|
||||
holder.title?.text = dataSet[position].toString()
|
||||
holder.title?.setTextColor(ThemeStore.accentColor(activity))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return dataSet.size
|
||||
}
|
||||
|
||||
inner class ViewHolder(itemView: View, itemViewType: Int) : MediaEntryViewHolder(itemView) {
|
||||
init {
|
||||
itemView.setOnLongClickListener(null)
|
||||
imageTextContainer?.isInvisible = true
|
||||
if (itemViewType == SONG) {
|
||||
imageTextContainer?.isGone = true
|
||||
menu?.isVisible = true
|
||||
menu?.setOnClickListener(object : SongMenuHelper.OnClickSongMenu(activity) {
|
||||
override val song: Song
|
||||
get() = dataSet[layoutPosition] as Song
|
||||
})
|
||||
} else {
|
||||
menu?.isVisible = false
|
||||
}
|
||||
|
||||
when (itemViewType) {
|
||||
ALBUM -> setImageTransitionName(activity.getString(R.string.transition_album_art))
|
||||
ARTIST -> setImageTransitionName(activity.getString(R.string.transition_artist_image))
|
||||
else -> {
|
||||
val container = itemView.findViewById<View>(R.id.imageContainer)
|
||||
container?.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
val item = dataSet[layoutPosition]
|
||||
when (itemViewType) {
|
||||
ALBUM -> {
|
||||
activity.findNavController(R.id.fragment_container).navigate(
|
||||
R.id.albumDetailsFragment,
|
||||
bundleOf(EXTRA_ALBUM_ID to (item as Album).id)
|
||||
)
|
||||
}
|
||||
|
||||
ARTIST -> {
|
||||
activity.findNavController(R.id.fragment_container).navigate(
|
||||
R.id.artistDetailsFragment,
|
||||
bundleOf(EXTRA_ARTIST_ID to (item as Artist).id)
|
||||
)
|
||||
}
|
||||
|
||||
ALBUM_ARTIST -> {
|
||||
activity.findNavController(R.id.fragment_container).navigate(
|
||||
R.id.albumArtistDetailsFragment,
|
||||
bundleOf(EXTRA_ARTIST_NAME to (item as Artist).name)
|
||||
)
|
||||
}
|
||||
|
||||
GENRE -> {
|
||||
activity.findNavController(R.id.fragment_container).navigate(
|
||||
R.id.genreDetailsFragment,
|
||||
bundleOf(EXTRA_GENRE to (item as Genre))
|
||||
)
|
||||
}
|
||||
|
||||
PLAYLIST -> {
|
||||
activity.findNavController(R.id.fragment_container).navigate(
|
||||
R.id.playlistDetailsFragment,
|
||||
bundleOf(EXTRA_PLAYLIST_ID to (item as PlaylistWithSongs).playlistEntity.playListId)
|
||||
)
|
||||
}
|
||||
|
||||
SONG -> {
|
||||
MusicPlayerRemote.playNext(item as Song)
|
||||
MusicPlayerRemote.playNextSong()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val HEADER = 0
|
||||
private const val ALBUM = 1
|
||||
private const val ARTIST = 2
|
||||
private const val SONG = 3
|
||||
private const val GENRE = 4
|
||||
private const val PLAYLIST = 5
|
||||
private const val ALBUM_ARTIST = 6
|
||||
}
|
||||
}
|
|
@ -1,19 +1,18 @@
|
|||
/*
|
||||
* Copyright 2019 Google LLC
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* 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
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* 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 io.github.muntashirakon.music.adapter
|
||||
package code.name.monkey.retromusic.adapter
|
||||
|
||||
import android.graphics.PorterDuff
|
||||
import android.view.LayoutInflater
|
||||
|
@ -21,32 +20,31 @@ import android.view.MenuItem
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isVisible
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter
|
||||
import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder
|
||||
import io.github.muntashirakon.music.glide.audiocover.AudioFileCover
|
||||
import io.github.muntashirakon.music.interfaces.CabHolder
|
||||
import io.github.muntashirakon.music.util.MusicUtil
|
||||
import io.github.muntashirakon.music.util.RetroUtil
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
|
||||
import code.name.monkey.retromusic.extensions.getTintedDrawable
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.audiocover.AudioFileCover
|
||||
import code.name.monkey.retromusic.interfaces.ICallbacks
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.signature.MediaStoreSignature
|
||||
import me.zhanghai.android.fastscroll.PopupTextProvider
|
||||
import java.io.File
|
||||
import java.text.DecimalFormat
|
||||
import java.util.*
|
||||
import kotlin.math.log10
|
||||
import kotlin.math.pow
|
||||
|
||||
class SongFileAdapter(
|
||||
private val activity: AppCompatActivity,
|
||||
override val activity: AppCompatActivity,
|
||||
private var dataSet: List<File>,
|
||||
private val itemLayoutRes: Int,
|
||||
private val callbacks: Callbacks?,
|
||||
cabHolder: CabHolder?
|
||||
private val iCallbacks: ICallbacks?
|
||||
) : AbsMultiSelectAdapter<SongFileAdapter.ViewHolder, File>(
|
||||
activity, cabHolder, R.menu.menu_media_selection
|
||||
activity, R.menu.menu_media_selection
|
||||
), PopupTextProvider {
|
||||
|
||||
init {
|
||||
|
@ -78,7 +76,7 @@ class SongFileAdapter(
|
|||
if (holder.itemViewType == FILE) {
|
||||
holder.text?.text = getFileText(file)
|
||||
} else {
|
||||
holder.text?.visibility = View.GONE
|
||||
holder.text?.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +94,7 @@ class SongFileAdapter(
|
|||
}
|
||||
|
||||
private fun loadFileImage(file: File, holder: ViewHolder) {
|
||||
val iconColor = ATHUtil.resolveColor(activity, R.attr.colorControlNormal)
|
||||
val iconColor = ATHUtil.resolveColor(activity, androidx.appcompat.R.attr.colorControlNormal)
|
||||
if (file.isDirectory) {
|
||||
holder.image?.let {
|
||||
it.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN)
|
||||
|
@ -105,21 +103,19 @@ class SongFileAdapter(
|
|||
holder.imageTextContainer?.setCardBackgroundColor(
|
||||
ATHUtil.resolveColor(
|
||||
activity,
|
||||
R.attr.colorSurface
|
||||
com.google.android.material.R.attr.colorSurface
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val error = RetroUtil.getTintedVectorDrawable(
|
||||
activity, R.drawable.ic_file_music, iconColor
|
||||
)
|
||||
val error = activity.getTintedDrawable(R.drawable.ic_audio_file, iconColor)
|
||||
Glide.with(activity)
|
||||
.load(AudioFileCover(file.path))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.error(error)
|
||||
.placeholder(error)
|
||||
.animate(android.R.anim.fade_in)
|
||||
.transition(RetroGlideExtension.getDefaultTransition())
|
||||
.signature(MediaStoreSignature("", file.lastModified(), 0))
|
||||
.into(holder.image)
|
||||
.into(holder.image!!)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,43 +123,35 @@ class SongFileAdapter(
|
|||
return dataSet.size
|
||||
}
|
||||
|
||||
override fun getIdentifier(position: Int): File? {
|
||||
override fun getIdentifier(position: Int): File {
|
||||
return dataSet[position]
|
||||
}
|
||||
|
||||
override fun getName(`object`: File): String {
|
||||
return getFileTitle(`object`)
|
||||
override fun getName(model: File): String {
|
||||
return getFileTitle(model)
|
||||
}
|
||||
|
||||
override fun onMultipleItemAction(menuItem: MenuItem, selection: ArrayList<File>) {
|
||||
if (callbacks == null) return
|
||||
callbacks.onMultipleItemAction(menuItem, selection)
|
||||
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<File>) {
|
||||
if (iCallbacks == null) return
|
||||
iCallbacks.onMultipleItemAction(menuItem, selection as ArrayList<File>)
|
||||
}
|
||||
|
||||
override fun getPopupText(position: Int): String {
|
||||
return getSectionName(position)
|
||||
return if (position >= dataSet.lastIndex) "" else getSectionName(position)
|
||||
}
|
||||
|
||||
private fun getSectionName(position: Int): String {
|
||||
return MusicUtil.getSectionName(dataSet[position].name)
|
||||
}
|
||||
|
||||
interface Callbacks {
|
||||
fun onFileSelected(file: File)
|
||||
|
||||
fun onFileMenuClicked(file: File, view: View)
|
||||
|
||||
fun onMultipleItemAction(item: MenuItem, files: ArrayList<File>)
|
||||
}
|
||||
|
||||
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
|
||||
inner class ViewHolder(itemView: View) : code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder(itemView) {
|
||||
|
||||
init {
|
||||
if (menu != null && callbacks != null) {
|
||||
if (menu != null && iCallbacks != null) {
|
||||
menu?.setOnClickListener { v ->
|
||||
val position = layoutPosition
|
||||
if (isPositionInRange(position)) {
|
||||
callbacks.onFileMenuClicked(dataSet[position], v)
|
||||
iCallbacks.onFileMenuClicked(dataSet[position], v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -178,7 +166,7 @@ class SongFileAdapter(
|
|||
if (isInQuickSelectMode) {
|
||||
toggleChecked(position)
|
||||
} else {
|
||||
callbacks?.onFileSelected(dataSet[position])
|
||||
iCallbacks?.onFileSelected(dataSet[position])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -205,4 +193,4 @@ class SongFileAdapter(
|
|||
return DecimalFormat("#,##0.##").format(size / 1024.0.pow(digitGroups.toDouble())) + " " + units[digitGroups]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package code.name.monkey.retromusic.adapter
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import code.name.monkey.retromusic.R
|
||||
import java.io.File
|
||||
|
||||
class StorageAdapter(
|
||||
val storageList: List<Storage>,
|
||||
val storageClickListener: StorageClickListener
|
||||
) :
|
||||
RecyclerView.Adapter<StorageAdapter.ViewHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return ViewHolder(
|
||||
LayoutInflater.from(parent.context).inflate(
|
||||
R.layout.item_storage,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bindData(storageList[position])
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return storageList.size
|
||||
}
|
||||
|
||||
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val title: TextView = itemView.findViewById(R.id.title)
|
||||
|
||||
fun bindData(storage: Storage) {
|
||||
title.text = storage.title
|
||||
}
|
||||
|
||||
init {
|
||||
itemView.setOnClickListener { storageClickListener.onStorageClicked(storageList[bindingAdapterPosition]) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface StorageClickListener {
|
||||
fun onStorageClicked(storage: Storage)
|
||||
}
|
||||
|
||||
class Storage {
|
||||
lateinit var title: String
|
||||
lateinit var file: File
|
||||
}
|
|
@ -1,39 +1,51 @@
|
|||
package io.github.muntashirakon.music.adapter.album
|
||||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.adapter.album
|
||||
|
||||
import android.app.ActivityOptions
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.Resources
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter
|
||||
import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder
|
||||
import io.github.muntashirakon.music.glide.AlbumGlideRequest
|
||||
import io.github.muntashirakon.music.glide.RetroMusicColoredTarget
|
||||
import io.github.muntashirakon.music.helper.MusicPlayerRemote
|
||||
import io.github.muntashirakon.music.helper.SortOrder
|
||||
import io.github.muntashirakon.music.helper.menu.SongsMenuHelper
|
||||
import io.github.muntashirakon.music.interfaces.CabHolder
|
||||
import io.github.muntashirakon.music.model.Album
|
||||
import io.github.muntashirakon.music.model.Song
|
||||
import io.github.muntashirakon.music.util.MusicUtil
|
||||
import io.github.muntashirakon.music.util.NavigationUtil
|
||||
import io.github.muntashirakon.music.util.PreferenceUtil
|
||||
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
|
||||
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.albumCoverOptions
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
|
||||
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
|
||||
import code.name.monkey.retromusic.helper.SortOrder
|
||||
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
|
||||
import code.name.monkey.retromusic.interfaces.IAlbumClickListener
|
||||
import code.name.monkey.retromusic.model.Album
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
|
||||
import com.bumptech.glide.Glide
|
||||
import me.zhanghai.android.fastscroll.PopupTextProvider
|
||||
|
||||
open class AlbumAdapter(
|
||||
protected val activity: AppCompatActivity,
|
||||
override val activity: FragmentActivity,
|
||||
var dataSet: List<Album>,
|
||||
protected var itemLayoutRes: Int,
|
||||
cabHolder: CabHolder?
|
||||
var itemLayoutRes: Int,
|
||||
val listener: IAlbumClickListener?
|
||||
) : AbsMultiSelectAdapter<AlbumAdapter.ViewHolder, Album>(
|
||||
activity,
|
||||
cabHolder,
|
||||
R.menu.menu_media_selection
|
||||
), PopupTextProvider {
|
||||
|
||||
|
@ -47,12 +59,7 @@ open class AlbumAdapter(
|
|||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view =
|
||||
try {
|
||||
LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
LayoutInflater.from(activity).inflate(R.layout.item_grid, parent, false)
|
||||
}
|
||||
val view = LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)
|
||||
return createViewHolder(view, viewType)
|
||||
}
|
||||
|
||||
|
@ -60,12 +67,18 @@ open class AlbumAdapter(
|
|||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
private fun getAlbumTitle(album: Album): String? {
|
||||
private fun getAlbumTitle(album: Album): String {
|
||||
return album.title
|
||||
}
|
||||
|
||||
protected open fun getAlbumText(album: Album): String? {
|
||||
return album.artistName
|
||||
return album.albumArtist.let {
|
||||
if (it.isNullOrEmpty()) {
|
||||
album.artistName
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
|
@ -74,14 +87,12 @@ open class AlbumAdapter(
|
|||
holder.itemView.isActivated = isChecked
|
||||
holder.title?.text = getAlbumTitle(album)
|
||||
holder.text?.text = getAlbumText(album)
|
||||
holder.playSongs?.setOnClickListener {
|
||||
album.songs?.let { songs ->
|
||||
MusicPlayerRemote.openQueue(
|
||||
songs,
|
||||
0,
|
||||
true
|
||||
)
|
||||
}
|
||||
// Check if imageContainer exists so we can have a smooth transition without
|
||||
// CardView clipping, if it doesn't exist in current layout set transition name to image instead.
|
||||
if (holder.imageContainer != null) {
|
||||
holder.imageContainer?.transitionName = album.id.toString()
|
||||
} else {
|
||||
holder.image?.transitionName = album.id.toString()
|
||||
}
|
||||
loadAlbumCover(album, holder)
|
||||
}
|
||||
|
@ -93,19 +104,20 @@ open class AlbumAdapter(
|
|||
holder.paletteColorContainer?.setBackgroundColor(color.backgroundColor)
|
||||
}
|
||||
holder.mask?.backgroundTintList = ColorStateList.valueOf(color.primaryTextColor)
|
||||
holder.imageContainerCard?.setCardBackgroundColor(color.backgroundColor)
|
||||
}
|
||||
|
||||
protected open fun loadAlbumCover(album: Album, holder: ViewHolder) {
|
||||
if (holder.image == null) {
|
||||
return
|
||||
}
|
||||
|
||||
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong())
|
||||
.checkIgnoreMediaStore(activity)
|
||||
.generatePalette(activity)
|
||||
.build()
|
||||
val song = album.safeGetFirstSong()
|
||||
Glide.with(activity)
|
||||
.asBitmapPalette()
|
||||
.albumCoverOptions(song)
|
||||
//.checkIgnoreMediaStore()
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.into(object : RetroMusicColoredTarget(holder.image!!) {
|
||||
|
||||
override fun onColorReady(colors: MediaNotificationProcessor) {
|
||||
setColors(colors, holder)
|
||||
}
|
||||
|
@ -117,27 +129,28 @@ open class AlbumAdapter(
|
|||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return dataSet[position].id.toLong()
|
||||
return dataSet[position].id
|
||||
}
|
||||
|
||||
override fun getIdentifier(position: Int): Album? {
|
||||
return dataSet[position]
|
||||
}
|
||||
|
||||
override fun getName(album: Album): String {
|
||||
return album.title!!
|
||||
override fun getName(model: Album): String {
|
||||
return model.title
|
||||
}
|
||||
|
||||
override fun onMultipleItemAction(
|
||||
menuItem: MenuItem, selection: ArrayList<Album>
|
||||
menuItem: MenuItem,
|
||||
selection: List<Album>
|
||||
) {
|
||||
SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.itemId)
|
||||
}
|
||||
|
||||
private fun getSongList(albums: List<Album>): ArrayList<Song> {
|
||||
private fun getSongList(albums: List<Album>): List<Song> {
|
||||
val songs = ArrayList<Song>()
|
||||
for (album in albums) {
|
||||
songs.addAll(album.songs!!)
|
||||
songs.addAll(album.songs)
|
||||
}
|
||||
return songs
|
||||
}
|
||||
|
@ -151,20 +164,19 @@ open class AlbumAdapter(
|
|||
when (PreferenceUtil.albumSortOrder) {
|
||||
SortOrder.AlbumSortOrder.ALBUM_A_Z, SortOrder.AlbumSortOrder.ALBUM_Z_A -> sectionName =
|
||||
dataSet[position].title
|
||||
SortOrder.AlbumSortOrder.ALBUM_ARTIST -> sectionName = dataSet[position].artistName
|
||||
|
||||
SortOrder.AlbumSortOrder.ALBUM_ARTIST -> sectionName = dataSet[position].albumArtist
|
||||
SortOrder.AlbumSortOrder.ALBUM_YEAR -> return MusicUtil.getYearString(
|
||||
dataSet[position].year
|
||||
)
|
||||
}
|
||||
|
||||
return MusicUtil.getSectionName(sectionName)
|
||||
}
|
||||
|
||||
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
|
||||
|
||||
init {
|
||||
setImageTransitionName(activity.getString(R.string.transition_album_art))
|
||||
menu?.visibility = View.GONE
|
||||
menu?.isVisible = false
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
|
@ -172,22 +184,14 @@ open class AlbumAdapter(
|
|||
if (isInQuickSelectMode) {
|
||||
toggleChecked(layoutPosition)
|
||||
} else {
|
||||
val activityOptions = ActivityOptions.makeSceneTransitionAnimation(
|
||||
activity,
|
||||
imageContainerCard ?: image,
|
||||
activity.getString(R.string.transition_album_art)
|
||||
)
|
||||
NavigationUtil.goToAlbumOptions(
|
||||
activity,
|
||||
dataSet[layoutPosition].id,
|
||||
activityOptions
|
||||
)
|
||||
image?.let {
|
||||
listener?.onAlbumClick(dataSet[layoutPosition].id, imageContainer ?: it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLongClick(v: View?): Boolean {
|
||||
toggleChecked(layoutPosition)
|
||||
return super.onLongClick(v)
|
||||
return toggleChecked(layoutPosition)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,45 @@
|
|||
package io.github.muntashirakon.music.adapter.album
|
||||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.adapter.album
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.core.os.BundleCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.fragments.AlbumCoverStyle
|
||||
import io.github.muntashirakon.music.fragments.NowPlayingScreen.*
|
||||
import io.github.muntashirakon.music.glide.RetroMusicColoredTarget
|
||||
import io.github.muntashirakon.music.glide.SongGlideRequest
|
||||
import io.github.muntashirakon.music.misc.CustomFragmentStatePagerAdapter
|
||||
import io.github.muntashirakon.music.model.Song
|
||||
import io.github.muntashirakon.music.util.MusicUtil
|
||||
import io.github.muntashirakon.music.util.NavigationUtil
|
||||
import io.github.muntashirakon.music.util.PreferenceUtil
|
||||
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.activities.MainActivity
|
||||
import code.name.monkey.retromusic.fragments.AlbumCoverStyle
|
||||
import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
|
||||
import code.name.monkey.retromusic.fragments.base.goToLyrics
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
|
||||
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
|
||||
import code.name.monkey.retromusic.misc.CustomFragmentStatePagerAdapter
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
|
||||
import com.bumptech.glide.Glide
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -68,17 +88,17 @@ class AlbumCoverPagerAdapter(
|
|||
|
||||
class AlbumCoverFragment : Fragment() {
|
||||
|
||||
private lateinit var albumCover: ImageView
|
||||
private var isColorReady: Boolean = false
|
||||
private lateinit var color: MediaNotificationProcessor
|
||||
private lateinit var song: Song
|
||||
private var colorReceiver: ColorReceiver? = null
|
||||
private var request: Int = 0
|
||||
private val mainActivity get() = activity as MainActivity
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (arguments != null) {
|
||||
song = requireArguments().getParcelable(SONG_ARG)!!
|
||||
song = BundleCompat.getParcelable(requireArguments(), SONG_ARG, Song::class.java)!!
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,25 +108,26 @@ class AlbumCoverPagerAdapter(
|
|||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false)
|
||||
albumCover = view.findViewById(R.id.player_image)
|
||||
albumCover.setOnClickListener {
|
||||
showLyricsDialog()
|
||||
view.setOnClickListener {
|
||||
if (mainActivity.getBottomSheetBehavior().state == STATE_EXPANDED) {
|
||||
showLyricsDialog()
|
||||
}
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
private fun showLyricsDialog() {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val data = MusicUtil.getLyrics(song)
|
||||
val data: String? = MusicUtil.getLyrics(song)
|
||||
withContext(Dispatchers.Main) {
|
||||
MaterialAlertDialogBuilder(
|
||||
requireContext(),
|
||||
R.style.ThemeOverlay_MaterialComponents_Dialog_Alert
|
||||
com.google.android.material.R.style.ThemeOverlay_MaterialComponents_Dialog_Alert
|
||||
).apply {
|
||||
setTitle(song.title)
|
||||
setMessage(data)
|
||||
setMessage(if (data.isNullOrEmpty()) "No lyrics found" else data)
|
||||
setNegativeButton(R.string.synced_lyrics) { _, _ ->
|
||||
NavigationUtil.goToLyrics(requireActivity())
|
||||
goToLyrics(requireActivity())
|
||||
}
|
||||
show()
|
||||
}
|
||||
|
@ -117,6 +138,7 @@ class AlbumCoverPagerAdapter(
|
|||
private fun getLayoutWithPlayerTheme(): Int {
|
||||
return when (PreferenceUtil.nowPlayingScreen) {
|
||||
Card, Fit, Tiny, Classic, Gradient, Full -> R.layout.fragment_album_full_cover
|
||||
Peek -> R.layout.fragment_peek_album_cover
|
||||
else -> {
|
||||
if (PreferenceUtil.isCarouselEffect) {
|
||||
R.layout.fragment_album_carousel_cover
|
||||
|
@ -126,7 +148,6 @@ class AlbumCoverPagerAdapter(
|
|||
AlbumCoverStyle.Flat -> R.layout.fragment_album_flat_cover
|
||||
AlbumCoverStyle.Circle -> R.layout.fragment_album_circle_cover
|
||||
AlbumCoverStyle.Card -> R.layout.fragment_album_card_cover
|
||||
AlbumCoverStyle.Material -> R.layout.fragment_album_material_cover
|
||||
AlbumCoverStyle.Full -> R.layout.fragment_album_full_cover
|
||||
AlbumCoverStyle.FullCard -> R.layout.fragment_album_full_card_cover
|
||||
}
|
||||
|
@ -137,7 +158,7 @@ class AlbumCoverPagerAdapter(
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
loadAlbumCover()
|
||||
loadAlbumCover(albumCover = view.findViewById(R.id.player_image))
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
@ -145,10 +166,13 @@ class AlbumCoverPagerAdapter(
|
|||
colorReceiver = null
|
||||
}
|
||||
|
||||
private fun loadAlbumCover() {
|
||||
SongGlideRequest.Builder.from(Glide.with(requireContext()), song)
|
||||
.checkIgnoreMediaStore(requireContext())
|
||||
.generatePalette(requireContext()).build()
|
||||
private fun loadAlbumCover(albumCover: ImageView) {
|
||||
Glide.with(this)
|
||||
.asBitmapPalette()
|
||||
.songCoverOptions(song)
|
||||
//.checkIgnoreMediaStore()
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.dontAnimate()
|
||||
.into(object : RetroMusicColoredTarget(albumCover) {
|
||||
override fun onColorReady(colors: MediaNotificationProcessor) {
|
||||
setColor(colors)
|
||||
|
@ -184,9 +208,7 @@ class AlbumCoverPagerAdapter(
|
|||
|
||||
fun newInstance(song: Song): AlbumCoverFragment {
|
||||
val frag = AlbumCoverFragment()
|
||||
val args = Bundle()
|
||||
args.putParcelable(SONG_ARG, song)
|
||||
frag.arguments = args
|
||||
frag.arguments = bundleOf(SONG_ARG to song)
|
||||
return frag
|
||||
}
|
||||
}
|
||||
|
@ -196,4 +218,3 @@ class AlbumCoverPagerAdapter(
|
|||
val TAG: String = AlbumCoverPagerAdapter::class.java.simpleName
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.adapter.album
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.albumCoverOptions
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
|
||||
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
|
||||
import code.name.monkey.retromusic.helper.HorizontalAdapterHelper
|
||||
import code.name.monkey.retromusic.interfaces.IAlbumClickListener
|
||||
import code.name.monkey.retromusic.model.Album
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
|
||||
import com.bumptech.glide.Glide
|
||||
|
||||
class HorizontalAlbumAdapter(
|
||||
activity: FragmentActivity,
|
||||
dataSet: List<Album>,
|
||||
albumClickListener: IAlbumClickListener
|
||||
) : AlbumAdapter(
|
||||
activity, dataSet, HorizontalAdapterHelper.LAYOUT_RES, albumClickListener
|
||||
) {
|
||||
|
||||
override fun createViewHolder(view: View, viewType: Int): ViewHolder {
|
||||
val params = view.layoutParams as ViewGroup.MarginLayoutParams
|
||||
HorizontalAdapterHelper.applyMarginToLayoutParams(activity, params, viewType)
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) {
|
||||
// holder.title?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorPrimary))
|
||||
// holder.text?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorSecondary))
|
||||
}
|
||||
|
||||
override fun loadAlbumCover(album: Album, holder: ViewHolder) {
|
||||
if (holder.image == null) return
|
||||
Glide.with(activity)
|
||||
.asBitmapPalette()
|
||||
.albumCoverOptions(album.safeGetFirstSong())
|
||||
.load(RetroGlideExtension.getSongModel(album.safeGetFirstSong()))
|
||||
.into(object : RetroMusicColoredTarget(holder.image!!) {
|
||||
override fun onColorReady(colors: MediaNotificationProcessor) {
|
||||
setColors(colors, holder)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun getAlbumText(album: Album): String {
|
||||
return MusicUtil.getYearString(album.year)
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return HorizontalAdapterHelper.getItemViewType(position, itemCount)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return dataSet.size
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG: String = AlbumAdapter::class.java.simpleName
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.adapter.artist
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.Resources
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
|
||||
import code.name.monkey.retromusic.extensions.hide
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.artistImageOptions
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
|
||||
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
|
||||
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
|
||||
import code.name.monkey.retromusic.interfaces.IAlbumArtistClickListener
|
||||
import code.name.monkey.retromusic.interfaces.IArtistClickListener
|
||||
import code.name.monkey.retromusic.model.Artist
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
|
||||
import com.bumptech.glide.Glide
|
||||
import me.zhanghai.android.fastscroll.PopupTextProvider
|
||||
|
||||
class ArtistAdapter(
|
||||
override val activity: FragmentActivity,
|
||||
var dataSet: List<Artist>,
|
||||
var itemLayoutRes: Int,
|
||||
val IArtistClickListener: IArtistClickListener,
|
||||
val IAlbumArtistClickListener: IAlbumArtistClickListener? = null
|
||||
) : AbsMultiSelectAdapter<ArtistAdapter.ViewHolder, Artist>(activity, R.menu.menu_media_selection),
|
||||
PopupTextProvider {
|
||||
|
||||
var albumArtistsOnly = false
|
||||
|
||||
init {
|
||||
this.setHasStableIds(true)
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun swapDataSet(dataSet: List<Artist>) {
|
||||
this.dataSet = dataSet
|
||||
notifyDataSetChanged()
|
||||
albumArtistsOnly = PreferenceUtil.albumArtistsOnly
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return dataSet[position].id
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view =
|
||||
try {
|
||||
LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
LayoutInflater.from(activity).inflate(R.layout.item_grid_circle, parent, false)
|
||||
}
|
||||
return createViewHolder(view)
|
||||
}
|
||||
|
||||
private fun createViewHolder(view: View): ViewHolder {
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val artist = dataSet[position]
|
||||
val isChecked = isChecked(artist)
|
||||
holder.itemView.isActivated = isChecked
|
||||
holder.title?.text = artist.name
|
||||
holder.text?.hide()
|
||||
val transitionName =
|
||||
if (albumArtistsOnly) artist.name else artist.id.toString()
|
||||
if (holder.imageContainer != null) {
|
||||
holder.imageContainer?.transitionName = transitionName
|
||||
} else {
|
||||
holder.image?.transitionName = transitionName
|
||||
}
|
||||
loadArtistImage(artist, holder)
|
||||
}
|
||||
|
||||
private fun setColors(processor: MediaNotificationProcessor, holder: ViewHolder) {
|
||||
holder.mask?.backgroundTintList = ColorStateList.valueOf(processor.primaryTextColor)
|
||||
if (holder.paletteColorContainer != null) {
|
||||
holder.paletteColorContainer?.setBackgroundColor(processor.backgroundColor)
|
||||
holder.title?.setTextColor(processor.primaryTextColor)
|
||||
}
|
||||
holder.imageContainerCard?.setCardBackgroundColor(processor.backgroundColor)
|
||||
}
|
||||
|
||||
private fun loadArtistImage(artist: Artist, holder: ViewHolder) {
|
||||
if (holder.image == null) {
|
||||
return
|
||||
}
|
||||
Glide.with(activity)
|
||||
.asBitmapPalette()
|
||||
.artistImageOptions(artist)
|
||||
.load(RetroGlideExtension.getArtistModel(artist))
|
||||
.transition(RetroGlideExtension.getDefaultTransition())
|
||||
.into(object : RetroMusicColoredTarget(holder.image!!) {
|
||||
override fun onColorReady(colors: MediaNotificationProcessor) {
|
||||
setColors(colors, holder)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return dataSet.size
|
||||
}
|
||||
|
||||
override fun getIdentifier(position: Int): Artist {
|
||||
return dataSet[position]
|
||||
}
|
||||
|
||||
override fun getName(model: Artist): String {
|
||||
return model.name
|
||||
}
|
||||
|
||||
override fun onMultipleItemAction(
|
||||
menuItem: MenuItem,
|
||||
selection: List<Artist>
|
||||
) {
|
||||
SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.itemId)
|
||||
}
|
||||
|
||||
private fun getSongList(artists: List<Artist>): List<Song> {
|
||||
val songs = ArrayList<Song>()
|
||||
for (artist in artists) {
|
||||
songs.addAll(artist.songs) // maybe async in future?
|
||||
}
|
||||
return songs
|
||||
}
|
||||
|
||||
override fun getPopupText(position: Int): String {
|
||||
return getSectionName(position)
|
||||
}
|
||||
|
||||
private fun getSectionName(position: Int): String {
|
||||
return MusicUtil.getSectionName(dataSet[position].name)
|
||||
}
|
||||
|
||||
inner class ViewHolder(itemView: View) : code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder(itemView) {
|
||||
|
||||
init {
|
||||
menu?.isVisible = false
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
super.onClick(v)
|
||||
if (isInQuickSelectMode) {
|
||||
toggleChecked(layoutPosition)
|
||||
} else {
|
||||
val artist = dataSet[layoutPosition]
|
||||
image?.let {
|
||||
if (albumArtistsOnly && IAlbumArtistClickListener != null) {
|
||||
IAlbumArtistClickListener.onAlbumArtist(artist.name, imageContainer ?: it)
|
||||
} else {
|
||||
IArtistClickListener.onArtist(artist.id, imageContainer ?: it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLongClick(v: View?): Boolean {
|
||||
return toggleChecked(layoutPosition)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package code.name.monkey.retromusic.adapter.backup
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.databinding.ItemListBackupBinding
|
||||
import java.io.File
|
||||
|
||||
|
||||
class BackupAdapter(
|
||||
val activity: FragmentActivity,
|
||||
var dataSet: MutableList<File>,
|
||||
val backupClickedListener: BackupClickedListener
|
||||
) : RecyclerView.Adapter<BackupAdapter.ViewHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return ViewHolder(
|
||||
ItemListBackupBinding.inflate(LayoutInflater.from(activity), parent, false)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.binding.title.text = dataSet[position].nameWithoutExtension
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = dataSet.size
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun swapDataset(dataSet: List<File>) {
|
||||
this.dataSet = ArrayList(dataSet)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
inner class ViewHolder(val binding: ItemListBackupBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
init {
|
||||
binding.menu.setOnClickListener { view ->
|
||||
val popupMenu = PopupMenu(activity, view)
|
||||
popupMenu.inflate(R.menu.menu_backup)
|
||||
popupMenu.setOnMenuItemClickListener { menuItem ->
|
||||
return@setOnMenuItemClickListener backupClickedListener.onBackupMenuClicked(
|
||||
dataSet[bindingAdapterPosition],
|
||||
menuItem
|
||||
)
|
||||
}
|
||||
popupMenu.show()
|
||||
}
|
||||
itemView.setOnClickListener {
|
||||
backupClickedListener.onBackupClicked(dataSet[bindingAdapterPosition])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface BackupClickedListener {
|
||||
fun onBackupClicked(file: File)
|
||||
|
||||
fun onBackupMenuClicked(file: File, menuItem: MenuItem): Boolean
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
package code.name.monkey.retromusic.adapter.base
|
||||
|
||||
import android.graphics.Color
|
||||
import android.view.ActionMode
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.MenuRes
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.databinding.NumberRollViewBinding
|
||||
import code.name.monkey.retromusic.views.NumberRollView
|
||||
|
||||
abstract class AbsMultiSelectAdapter<V : RecyclerView.ViewHolder?, I>(
|
||||
open val activity: FragmentActivity, @MenuRes menuRes: Int,
|
||||
) : RecyclerView.Adapter<V>(), ActionMode.Callback {
|
||||
var actionMode: ActionMode? = null
|
||||
private val checked: MutableList<I>
|
||||
private var menuRes: Int
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
val inflater = mode?.menuInflater
|
||||
inflater?.inflate(menuRes, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||
if (item?.itemId == R.id.action_multi_select_adapter_check_all) {
|
||||
checkAll()
|
||||
} else {
|
||||
onMultipleItemAction(item!!, ArrayList(checked))
|
||||
actionMode?.finish()
|
||||
clearChecked()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
clearChecked()
|
||||
activity.window.statusBarColor = when {
|
||||
VersionUtils.hasMarshmallow() -> Color.TRANSPARENT
|
||||
else -> Color.BLACK
|
||||
}
|
||||
actionMode = null
|
||||
onBackPressedCallback.remove()
|
||||
}
|
||||
|
||||
private fun checkAll() {
|
||||
if (actionMode != null) {
|
||||
checked.clear()
|
||||
for (i in 0 until itemCount) {
|
||||
val identifier = getIdentifier(i)
|
||||
if (identifier != null) {
|
||||
checked.add(identifier)
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
updateCab()
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun getIdentifier(position: Int): I?
|
||||
|
||||
protected abstract fun getName(model: I): String?
|
||||
|
||||
protected fun isChecked(identifier: I): Boolean {
|
||||
return checked.contains(identifier)
|
||||
}
|
||||
|
||||
protected val isInQuickSelectMode: Boolean
|
||||
get() = actionMode != null
|
||||
|
||||
protected abstract fun onMultipleItemAction(menuItem: MenuItem, selection: List<I>)
|
||||
protected fun setMultiSelectMenuRes(@MenuRes menuRes: Int) {
|
||||
this.menuRes = menuRes
|
||||
}
|
||||
|
||||
protected fun toggleChecked(position: Int): Boolean {
|
||||
val identifier = getIdentifier(position) ?: return false
|
||||
if (!checked.remove(identifier)) {
|
||||
checked.add(identifier)
|
||||
}
|
||||
notifyItemChanged(position)
|
||||
updateCab()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun clearChecked() {
|
||||
checked.clear()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private fun updateCab() {
|
||||
if (actionMode == null) {
|
||||
actionMode = activity.startActionMode(this)?.apply {
|
||||
customView = NumberRollViewBinding.inflate(activity.layoutInflater).root
|
||||
}
|
||||
activity.onBackPressedDispatcher.addCallback(onBackPressedCallback)
|
||||
}
|
||||
val size = checked.size
|
||||
when {
|
||||
size <= 0 -> {
|
||||
actionMode?.finish()
|
||||
}
|
||||
else -> {
|
||||
actionMode?.customView?.findViewById<NumberRollView>(R.id.selection_mode_number)
|
||||
?.setNumber(size, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
checked = ArrayList()
|
||||
this.menuRes = menuRes
|
||||
}
|
||||
|
||||
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (actionMode != null) {
|
||||
actionMode?.finish()
|
||||
remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,23 +12,22 @@
|
|||
* See the GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
package io.github.muntashirakon.music.adapter.base;
|
||||
package code.name.monkey.retromusic.adapter.base;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
|
||||
import com.google.android.material.card.MaterialCardView;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableSwipeableItemViewHolder;
|
||||
|
||||
import io.github.muntashirakon.music.R;
|
||||
import code.name.monkey.retromusic.R;
|
||||
|
||||
public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHolder
|
||||
implements View.OnLongClickListener, View.OnClickListener {
|
||||
|
@ -41,18 +40,13 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
|
|||
|
||||
@Nullable
|
||||
public ImageView image;
|
||||
@Nullable
|
||||
public ImageView artistImage;
|
||||
|
||||
@Nullable
|
||||
public ImageView playerImage;
|
||||
|
||||
@Nullable
|
||||
public ViewGroup imageContainer;
|
||||
|
||||
@Nullable
|
||||
public MaterialCardView imageContainerCard;
|
||||
|
||||
@Nullable
|
||||
public FrameLayout imageContainer;
|
||||
|
||||
@Nullable
|
||||
public TextView imageText;
|
||||
|
||||
|
@ -63,20 +57,17 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
|
|||
public View mask;
|
||||
|
||||
@Nullable
|
||||
public View menu;
|
||||
public AppCompatImageView menu;
|
||||
|
||||
@Nullable
|
||||
public View paletteColorContainer;
|
||||
|
||||
@Nullable
|
||||
public ImageButton playSongs;
|
||||
|
||||
@Nullable
|
||||
public RecyclerView recyclerView;
|
||||
|
||||
@Nullable
|
||||
public TextView text;
|
||||
|
||||
@Nullable
|
||||
public TextView text2;
|
||||
|
||||
@Nullable
|
||||
public TextView time;
|
||||
|
||||
|
@ -87,23 +78,20 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
|
|||
super(itemView);
|
||||
title = itemView.findViewById(R.id.title);
|
||||
text = itemView.findViewById(R.id.text);
|
||||
text2 = itemView.findViewById(R.id.text2);
|
||||
|
||||
image = itemView.findViewById(R.id.image);
|
||||
artistImage = itemView.findViewById(R.id.artistImage);
|
||||
playerImage = itemView.findViewById(R.id.player_image);
|
||||
time = itemView.findViewById(R.id.time);
|
||||
|
||||
imageText = itemView.findViewById(R.id.imageText);
|
||||
imageContainer = itemView.findViewById(R.id.imageContainer);
|
||||
imageTextContainer = itemView.findViewById(R.id.imageTextContainer);
|
||||
imageContainerCard = itemView.findViewById(R.id.imageContainerCard);
|
||||
imageContainer = itemView.findViewById(R.id.imageContainer);
|
||||
|
||||
menu = itemView.findViewById(R.id.menu);
|
||||
dragView = itemView.findViewById(R.id.drag_view);
|
||||
paletteColorContainer = itemView.findViewById(R.id.paletteColorContainer);
|
||||
recyclerView = itemView.findViewById(R.id.recycler_view);
|
||||
mask = itemView.findViewById(R.id.mask);
|
||||
playSongs = itemView.findViewById(R.id.playSongs);
|
||||
dummyContainer = itemView.findViewById(R.id.dummy_view);
|
||||
|
||||
if (imageContainerCard != null) {
|
||||
|
@ -113,6 +101,7 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
|
|||
itemView.setOnLongClickListener(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View getSwipeableContainerView() {
|
||||
return null;
|
||||
|
@ -120,7 +109,6 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
|
|||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -129,11 +117,12 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
|
|||
}
|
||||
|
||||
public void setImageTransitionName(@NonNull String transitionName) {
|
||||
if (imageContainerCard != null) {
|
||||
imageContainerCard.setTransitionName(transitionName);
|
||||
}
|
||||
if (image != null) {
|
||||
image.setTransitionName(transitionName);
|
||||
}
|
||||
itemView.setTransitionName(transitionName);
|
||||
/* if (imageContainerCard != null) {
|
||||
imageContainerCard.setTransitionName(transitionName);
|
||||
}
|
||||
if (image != null) {
|
||||
image.setTransitionName(transitionName);
|
||||
}*/
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.adapter.playlist
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import code.name.monkey.retromusic.model.Playlist
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
|
||||
class LegacyPlaylistAdapter(
|
||||
private val activity: FragmentActivity,
|
||||
private var list: List<Playlist>,
|
||||
private val layoutRes: Int,
|
||||
private val playlistClickListener: PlaylistClickListener
|
||||
) :
|
||||
RecyclerView.Adapter<LegacyPlaylistAdapter.ViewHolder>() {
|
||||
|
||||
fun swapData(list: List<Playlist>) {
|
||||
this.list = list
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
class ViewHolder(itemView: View) : code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder(itemView)
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): ViewHolder {
|
||||
return ViewHolder(
|
||||
LayoutInflater.from(parent.context).inflate(layoutRes, parent, false)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val playlist: Playlist = list[position]
|
||||
holder.title?.text = playlist.name
|
||||
holder.text?.text = MusicUtil.getPlaylistInfoString(activity, playlist.getSongs())
|
||||
holder.itemView.setOnClickListener {
|
||||
playlistClickListener.onPlaylistClick(playlist)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return list.size
|
||||
}
|
||||
|
||||
interface PlaylistClickListener {
|
||||
fun onPlaylistClick(playlist: Playlist)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.adapter.playlist
|
||||
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.setPadding
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import code.name.monkey.appthemehelper.util.TintHelper
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
|
||||
import code.name.monkey.retromusic.db.PlaylistEntity
|
||||
import code.name.monkey.retromusic.db.PlaylistWithSongs
|
||||
import code.name.monkey.retromusic.db.toSongs
|
||||
import code.name.monkey.retromusic.extensions.dipToPix
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.playlistOptions
|
||||
import code.name.monkey.retromusic.glide.playlistPreview.PlaylistPreview
|
||||
import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder
|
||||
import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper
|
||||
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
|
||||
import code.name.monkey.retromusic.interfaces.IPlaylistClickListener
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import com.bumptech.glide.Glide
|
||||
import me.zhanghai.android.fastscroll.PopupTextProvider
|
||||
|
||||
class PlaylistAdapter(
|
||||
override val activity: FragmentActivity,
|
||||
var dataSet: List<PlaylistWithSongs>,
|
||||
private var itemLayoutRes: Int,
|
||||
private val listener: IPlaylistClickListener
|
||||
) : AbsMultiSelectAdapter<PlaylistAdapter.ViewHolder, PlaylistWithSongs>(
|
||||
activity,
|
||||
R.menu.menu_playlists_selection
|
||||
), PopupTextProvider {
|
||||
|
||||
init {
|
||||
setHasStableIds(true)
|
||||
}
|
||||
|
||||
fun swapDataSet(dataSet: List<PlaylistWithSongs>) {
|
||||
this.dataSet = dataSet
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return dataSet[position].playlistEntity.playListId
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)
|
||||
return createViewHolder(view)
|
||||
}
|
||||
|
||||
private fun createViewHolder(view: View): ViewHolder {
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
private fun getPlaylistTitle(playlist: PlaylistEntity): String {
|
||||
return playlist.playlistName.ifEmpty { "-" }
|
||||
}
|
||||
|
||||
private fun getPlaylistText(playlist: PlaylistWithSongs): String {
|
||||
return MusicUtil.getPlaylistInfoString(activity, playlist.songs.toSongs())
|
||||
}
|
||||
|
||||
override fun getPopupText(position: Int): String {
|
||||
val sectionName: String = when (PreferenceUtil.playlistSortOrder) {
|
||||
PlaylistSortOrder.PLAYLIST_A_Z, PlaylistSortOrder.PLAYLIST_Z_A -> dataSet[position].playlistEntity.playlistName
|
||||
PlaylistSortOrder.PLAYLIST_SONG_COUNT, PlaylistSortOrder.PLAYLIST_SONG_COUNT_DESC -> dataSet[position].songs.size.toString()
|
||||
else -> {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return MusicUtil.getSectionName(sectionName)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val playlist = dataSet[position]
|
||||
holder.itemView.isActivated = isChecked(playlist)
|
||||
holder.title?.text = getPlaylistTitle(playlist.playlistEntity)
|
||||
holder.text?.text = getPlaylistText(playlist)
|
||||
holder.menu?.isGone = isChecked(playlist)
|
||||
if (itemLayoutRes == R.layout.item_list) {
|
||||
holder.image?.setPadding(activity.dipToPix(8F).toInt())
|
||||
holder.image?.setImageDrawable(getIconRes())
|
||||
} else {
|
||||
Glide.with(activity)
|
||||
.load(PlaylistPreview(playlist))
|
||||
.playlistOptions()
|
||||
.into(holder.image!!)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIconRes(): Drawable = TintHelper.createTintedDrawable(
|
||||
activity,
|
||||
R.drawable.ic_playlist_play,
|
||||
ATHUtil.resolveColor(activity, android.R.attr.colorControlNormal)
|
||||
)
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return dataSet.size
|
||||
}
|
||||
|
||||
override fun getIdentifier(position: Int): PlaylistWithSongs {
|
||||
return dataSet[position]
|
||||
}
|
||||
|
||||
override fun getName(model: PlaylistWithSongs): String {
|
||||
return model.playlistEntity.playlistName
|
||||
}
|
||||
|
||||
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<PlaylistWithSongs>) {
|
||||
when (menuItem.itemId) {
|
||||
else -> SongsMenuHelper.handleMenuClick(
|
||||
activity,
|
||||
getSongList(selection),
|
||||
menuItem.itemId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSongList(playlists: List<PlaylistWithSongs>): List<Song> {
|
||||
val songs = mutableListOf<Song>()
|
||||
playlists.forEach {
|
||||
songs.addAll(it.songs.toSongs())
|
||||
}
|
||||
return songs
|
||||
}
|
||||
|
||||
inner class ViewHolder(itemView: View) : code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder(itemView) {
|
||||
init {
|
||||
menu?.setOnClickListener { view ->
|
||||
val popupMenu = PopupMenu(activity, view)
|
||||
popupMenu.inflate(R.menu.menu_item_playlist)
|
||||
popupMenu.setOnMenuItemClickListener { item ->
|
||||
PlaylistMenuHelper.handleMenuClick(activity, dataSet[layoutPosition], item)
|
||||
}
|
||||
popupMenu.show()
|
||||
}
|
||||
|
||||
imageTextContainer?.apply {
|
||||
cardElevation = 0f
|
||||
setCardBackgroundColor(Color.TRANSPARENT)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
if (isInQuickSelectMode) {
|
||||
toggleChecked(layoutPosition)
|
||||
} else {
|
||||
itemView.transitionName = "playlist"
|
||||
listener.onPlaylistClick(dataSet[layoutPosition], itemView)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLongClick(v: View?): Boolean {
|
||||
toggleChecked(layoutPosition)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG: String = PlaylistAdapter::class.java.simpleName
|
||||
}
|
||||
}
|
|
@ -1,21 +1,33 @@
|
|||
package io.github.muntashirakon.music.adapter.song
|
||||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.adapter.song
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.helper.MusicPlayerRemote
|
||||
import io.github.muntashirakon.music.interfaces.CabHolder
|
||||
import io.github.muntashirakon.music.model.Song
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
|
||||
abstract class AbsOffsetSongAdapter(
|
||||
activity: AppCompatActivity,
|
||||
activity: FragmentActivity,
|
||||
dataSet: MutableList<Song>,
|
||||
@LayoutRes itemLayoutRes: Int,
|
||||
cabHolder: CabHolder?
|
||||
) : SongAdapter(activity, dataSet, itemLayoutRes, cabHolder) {
|
||||
@LayoutRes itemLayoutRes: Int
|
||||
) : SongAdapter(activity, dataSet, itemLayoutRes) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SongAdapter.ViewHolder {
|
||||
if (viewType == OFFSET_ITEM) {
|
||||
|
@ -76,4 +88,4 @@ abstract class AbsOffsetSongAdapter(
|
|||
const val OFFSET_ITEM = 0
|
||||
const val SONG = 1
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.adapter.song
|
||||
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.db.PlaylistEntity
|
||||
import code.name.monkey.retromusic.db.toSongEntity
|
||||
import code.name.monkey.retromusic.db.toSongsEntity
|
||||
import code.name.monkey.retromusic.dialogs.RemoveSongFromPlaylistDialog
|
||||
import code.name.monkey.retromusic.fragments.LibraryViewModel
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.ViewUtil
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class OrderablePlaylistSongAdapter(
|
||||
private val playlistId: Long,
|
||||
activity: FragmentActivity,
|
||||
dataSet: MutableList<Song>,
|
||||
itemLayoutRes: Int,
|
||||
) : SongAdapter(activity, dataSet, itemLayoutRes),
|
||||
DraggableItemAdapter<OrderablePlaylistSongAdapter.ViewHolder> {
|
||||
|
||||
val libraryViewModel: LibraryViewModel by activity.viewModel()
|
||||
|
||||
init {
|
||||
this.setHasStableIds(true)
|
||||
this.setMultiSelectMenuRes(R.menu.menu_playlists_songs_selection)
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
// requires static value, it means need to keep the same value
|
||||
// even if the item position has been changed.
|
||||
return dataSet[position].id
|
||||
}
|
||||
|
||||
override fun createViewHolder(view: View): SongAdapter.ViewHolder {
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Song>) {
|
||||
when (menuItem.itemId) {
|
||||
R.id.action_remove_from_playlist -> RemoveSongFromPlaylistDialog.create(
|
||||
selection.toSongsEntity(
|
||||
playlistId
|
||||
)
|
||||
)
|
||||
.show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST")
|
||||
|
||||
else -> super.onMultipleItemAction(menuItem, selection)
|
||||
}
|
||||
}
|
||||
|
||||
inner class ViewHolder(itemView: View) : SongAdapter.ViewHolder(itemView) {
|
||||
|
||||
override var songMenuRes: Int
|
||||
get() = R.menu.menu_item_playlist_song
|
||||
set(value) {
|
||||
super.songMenuRes = value
|
||||
}
|
||||
|
||||
override fun onSongMenuItemClick(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_remove_from_playlist -> {
|
||||
RemoveSongFromPlaylistDialog.create(song.toSongEntity(playlistId))
|
||||
.show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST")
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onSongMenuItemClick(item)
|
||||
}
|
||||
|
||||
init {
|
||||
dragView?.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCheckCanStartDrag(holder: ViewHolder, position: Int, x: Int, y: Int): Boolean {
|
||||
if (isInQuickSelectMode) {
|
||||
return false
|
||||
}
|
||||
return ViewUtil.hitTest(holder.imageText!!, x, y) || ViewUtil.hitTest(
|
||||
holder.dragView!!,
|
||||
x,
|
||||
y
|
||||
)
|
||||
}
|
||||
|
||||
override fun onMoveItem(fromPosition: Int, toPosition: Int) {
|
||||
dataSet.add(toPosition, dataSet.removeAt(fromPosition))
|
||||
}
|
||||
|
||||
override fun onGetItemDraggableRange(holder: ViewHolder, position: Int): ItemDraggableRange? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onCheckCanDrop(draggingPosition: Int, dropPosition: Int): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onItemDragStarted(position: Int) {
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onItemDragFinished(fromPosition: Int, toPosition: Int, result: Boolean) {
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun saveSongs(playlistEntity: PlaylistEntity) {
|
||||
activity.lifecycleScope.launch(Dispatchers.IO) {
|
||||
libraryViewModel.insertSongs(dataSet.toSongsEntity(playlistEntity))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +1,34 @@
|
|||
package io.github.muntashirakon.music.adapter.song
|
||||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.adapter.song
|
||||
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.helper.MusicPlayerRemote
|
||||
import io.github.muntashirakon.music.helper.MusicPlayerRemote.isPlaying
|
||||
import io.github.muntashirakon.music.helper.MusicPlayerRemote.playNextSong
|
||||
import io.github.muntashirakon.music.helper.MusicPlayerRemote.removeFromQueue
|
||||
import io.github.muntashirakon.music.model.Song
|
||||
import io.github.muntashirakon.music.util.MusicUtil
|
||||
import io.github.muntashirakon.music.util.ViewUtil
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote.isPlaying
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote.playNextSong
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote.removeFromQueue
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.ViewUtil
|
||||
import com.bumptech.glide.Glide
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags
|
||||
|
@ -19,17 +37,15 @@ import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstant
|
|||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAction
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionDefault
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionRemoveItem
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.annotation.SwipeableItemResults
|
||||
import me.zhanghai.android.fastscroll.PopupTextProvider
|
||||
|
||||
class PlayingQueueAdapter(
|
||||
activity: AppCompatActivity,
|
||||
activity: FragmentActivity,
|
||||
dataSet: MutableList<Song>,
|
||||
private var current: Int,
|
||||
itemLayoutRes: Int
|
||||
) : SongAdapter(
|
||||
activity, dataSet, itemLayoutRes, null
|
||||
), DraggableItemAdapter<PlayingQueueAdapter.ViewHolder>,
|
||||
itemLayoutRes: Int,
|
||||
) : SongAdapter(activity, dataSet, itemLayoutRes),
|
||||
DraggableItemAdapter<PlayingQueueAdapter.ViewHolder>,
|
||||
SwipeableItemAdapter<PlayingQueueAdapter.ViewHolder>,
|
||||
PopupTextProvider {
|
||||
|
||||
|
@ -41,8 +57,8 @@ class PlayingQueueAdapter(
|
|||
|
||||
override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) {
|
||||
super.onBindViewHolder(holder, position)
|
||||
holder.imageText?.text = (position - current).toString()
|
||||
holder.time?.text = MusicUtil.getReadableDurationString(dataSet[position].duration)
|
||||
val song = dataSet[position]
|
||||
holder.time?.text = MusicUtil.getReadableDurationString(song.duration)
|
||||
if (holder.itemViewType == HISTORY || holder.itemViewType == CURRENT) {
|
||||
setAlpha(holder, 0.5f)
|
||||
}
|
||||
|
@ -58,7 +74,13 @@ class PlayingQueueAdapter(
|
|||
}
|
||||
|
||||
override fun loadAlbumCover(song: Song, holder: SongAdapter.ViewHolder) {
|
||||
// We don't want to load it in this adapter
|
||||
if (holder.image == null) {
|
||||
return
|
||||
}
|
||||
Glide.with(activity)
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.songCoverOptions(song)
|
||||
.into(holder.image!!)
|
||||
}
|
||||
|
||||
fun swapDataSet(dataSet: List<Song>, position: Int) {
|
||||
|
@ -76,7 +98,6 @@ class PlayingQueueAdapter(
|
|||
holder.image?.alpha = alpha
|
||||
holder.title?.alpha = alpha
|
||||
holder.text?.alpha = alpha
|
||||
holder.imageText?.alpha = alpha
|
||||
holder.paletteColorContainer?.alpha = alpha
|
||||
holder.dragView?.alpha = alpha
|
||||
holder.menu?.alpha = alpha
|
||||
|
@ -129,8 +150,15 @@ class PlayingQueueAdapter(
|
|||
}
|
||||
|
||||
init {
|
||||
imageText?.visibility = View.VISIBLE
|
||||
dragView?.visibility = View.VISIBLE
|
||||
dragView?.isVisible = true
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
if (isInQuickSelectMode) {
|
||||
toggleChecked(layoutPosition)
|
||||
} else {
|
||||
MusicPlayerRemote.playSongAt(layoutPosition)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSongMenuItemClick(item: MenuItem): Boolean {
|
||||
|
@ -152,8 +180,8 @@ class PlayingQueueAdapter(
|
|||
mDragStateFlags = flags
|
||||
}
|
||||
|
||||
override fun getSwipeableContainerView(): View? {
|
||||
return dummyContainer
|
||||
override fun getSwipeableContainerView(): View {
|
||||
return dummyContainer!!
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,52 +192,46 @@ class PlayingQueueAdapter(
|
|||
private const val UP_NEXT = 2
|
||||
}
|
||||
|
||||
override fun onSwipeItem(
|
||||
holder: ViewHolder?,
|
||||
position: Int, @SwipeableItemResults result: Int
|
||||
): SwipeResultAction {
|
||||
return if (result === SwipeableItemConstants.RESULT_CANCELED) {
|
||||
override fun onSwipeItem(holder: ViewHolder, position: Int, result: Int): SwipeResultAction {
|
||||
return if (result == SwipeableItemConstants.RESULT_CANCELED) {
|
||||
SwipeResultActionDefault()
|
||||
} else {
|
||||
SwipedResultActionRemoveItem(this, position, activity)
|
||||
SwipedResultActionRemoveItem(this, position)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGetSwipeReactionType(holder: ViewHolder?, position: Int, x: Int, y: Int): Int {
|
||||
return if (onCheckCanStartDrag(holder!!, position, x, y)) {
|
||||
override fun onGetSwipeReactionType(holder: ViewHolder, position: Int, x: Int, y: Int): Int {
|
||||
return if (onCheckCanStartDrag(holder, position, x, y)) {
|
||||
SwipeableItemConstants.REACTION_CAN_NOT_SWIPE_BOTH_H
|
||||
} else {
|
||||
SwipeableItemConstants.REACTION_CAN_SWIPE_BOTH_H
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSwipeItemStarted(p0: ViewHolder?, p1: Int) {
|
||||
override fun onSwipeItemStarted(holder: ViewHolder, p1: Int) {
|
||||
}
|
||||
|
||||
override fun onSetSwipeBackground(holder: ViewHolder?, position: Int, result: Int) {
|
||||
override fun onSetSwipeBackground(holder: ViewHolder, position: Int, result: Int) {
|
||||
}
|
||||
|
||||
internal class SwipedResultActionRemoveItem(
|
||||
private val adapter: PlayingQueueAdapter,
|
||||
private val position: Int,
|
||||
private val activity: AppCompatActivity
|
||||
) : SwipeResultActionRemoveItem() {
|
||||
|
||||
private var songToRemove: Song? = null
|
||||
private val isPlaying: Boolean = MusicPlayerRemote.isPlaying
|
||||
private val songProgressMillis = 0
|
||||
override fun onPerformAction() {
|
||||
//currentlyShownSnackbar = null
|
||||
// currentlyShownSnackbar = null
|
||||
}
|
||||
|
||||
override fun onSlideAnimationEnd() {
|
||||
//initializeSnackBar(adapter, position, activity, isPlaying)
|
||||
// initializeSnackBar(adapter, position, activity, isPlaying)
|
||||
songToRemove = adapter.dataSet[position]
|
||||
//If song removed was the playing song, then play the next song
|
||||
// If song removed was the playing song, then play the next song
|
||||
if (isPlaying(songToRemove!!)) {
|
||||
playNextSong()
|
||||
}
|
||||
//Swipe animation is much smoother when we do the heavy lifting after it's completed
|
||||
// Swipe animation is much smoother when we do the heavy lifting after it's completed
|
||||
adapter.setSongToRemove(songToRemove!!)
|
||||
removeFromQueue(songToRemove!!)
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.adapter.song
|
||||
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.accentColor
|
||||
import code.name.monkey.retromusic.extensions.accentOutlineColor
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
import com.google.android.material.button.MaterialButton
|
||||
|
||||
class ShuffleButtonSongAdapter(
|
||||
activity: FragmentActivity,
|
||||
dataSet: MutableList<Song>,
|
||||
itemLayoutRes: Int
|
||||
) : AbsOffsetSongAdapter(activity, dataSet, itemLayoutRes) {
|
||||
|
||||
|
||||
override fun createViewHolder(view: View): SongAdapter.ViewHolder {
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return if (position == 0) OFFSET_ITEM else SONG
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) {
|
||||
if (holder.itemViewType == OFFSET_ITEM) {
|
||||
val viewHolder = holder as ViewHolder
|
||||
viewHolder.playAction?.let {
|
||||
it.setOnClickListener {
|
||||
MusicPlayerRemote.openQueue(dataSet, 0, true)
|
||||
}
|
||||
it.accentOutlineColor()
|
||||
}
|
||||
viewHolder.shuffleAction?.let {
|
||||
it.setOnClickListener {
|
||||
MusicPlayerRemote.openAndShuffleQueue(dataSet, true)
|
||||
}
|
||||
it.accentColor()
|
||||
}
|
||||
} else {
|
||||
super.onBindViewHolder(holder, position - 1)
|
||||
val landscape = RetroUtil.isLandscape
|
||||
if ((PreferenceUtil.songGridSize > 2 && !landscape) || (PreferenceUtil.songGridSizeLand > 5 && landscape)) {
|
||||
holder.menu?.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class ViewHolder(itemView: View) : AbsOffsetSongAdapter.ViewHolder(itemView) {
|
||||
val playAction: MaterialButton? = itemView.findViewById(R.id.playAction)
|
||||
val shuffleAction: MaterialButton? = itemView.findViewById(R.id.shuffleAction)
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
if (itemViewType == OFFSET_ITEM) {
|
||||
MusicPlayerRemote.openAndShuffleQueue(dataSet, true)
|
||||
return
|
||||
}
|
||||
super.onClick(v)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.adapter.song
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
|
||||
class SimpleSongAdapter(
|
||||
context: FragmentActivity,
|
||||
songs: ArrayList<Song>,
|
||||
layoutRes: Int
|
||||
) : SongAdapter(context, songs, layoutRes) {
|
||||
|
||||
override fun swapDataSet(dataSet: List<Song>) {
|
||||
this.dataSet = dataSet.toMutableList()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return ViewHolder(LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
super.onBindViewHolder(holder, position)
|
||||
val fixedTrackNumber = MusicUtil.getFixedTrackNumber(dataSet[position].trackNumber)
|
||||
val trackAndTime = (if (fixedTrackNumber > 0) "$fixedTrackNumber | " else "") +
|
||||
MusicUtil.getReadableDurationString(dataSet[position].duration)
|
||||
|
||||
holder.time?.text = trackAndTime
|
||||
holder.text2?.text = dataSet[position].artistName
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return dataSet.size
|
||||
}
|
||||
}
|
|
@ -1,31 +1,47 @@
|
|||
package io.github.muntashirakon.music.adapter.song
|
||||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.adapter.song
|
||||
|
||||
import android.app.ActivityOptions
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.Resources
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.adapter.base.AbsMultiSelectAdapter
|
||||
import io.github.muntashirakon.music.adapter.base.MediaEntryViewHolder
|
||||
import io.github.muntashirakon.music.extensions.hide
|
||||
import io.github.muntashirakon.music.extensions.show
|
||||
import io.github.muntashirakon.music.glide.RetroMusicColoredTarget
|
||||
import io.github.muntashirakon.music.glide.SongGlideRequest
|
||||
import io.github.muntashirakon.music.helper.MusicPlayerRemote
|
||||
import io.github.muntashirakon.music.helper.SortOrder
|
||||
import io.github.muntashirakon.music.helper.menu.SongMenuHelper
|
||||
import io.github.muntashirakon.music.helper.menu.SongsMenuHelper
|
||||
import io.github.muntashirakon.music.interfaces.CabHolder
|
||||
import io.github.muntashirakon.music.model.Song
|
||||
import io.github.muntashirakon.music.util.MusicUtil
|
||||
import io.github.muntashirakon.music.util.NavigationUtil
|
||||
import io.github.muntashirakon.music.util.PreferenceUtil
|
||||
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
|
||||
import com.afollestad.materialcab.MaterialCab
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.navigation.findNavController
|
||||
import code.name.monkey.retromusic.EXTRA_ALBUM_ID
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
|
||||
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
|
||||
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.helper.SortOrder
|
||||
import code.name.monkey.retromusic.helper.menu.SongMenuHelper
|
||||
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
|
||||
import com.bumptech.glide.Glide
|
||||
import me.zhanghai.android.fastscroll.PopupTextProvider
|
||||
|
||||
|
@ -34,16 +50,14 @@ import me.zhanghai.android.fastscroll.PopupTextProvider
|
|||
*/
|
||||
|
||||
open class SongAdapter(
|
||||
protected val activity: AppCompatActivity,
|
||||
override val activity: FragmentActivity,
|
||||
var dataSet: MutableList<Song>,
|
||||
protected var itemLayoutRes: Int,
|
||||
cabHolder: CabHolder?,
|
||||
showSectionName: Boolean = true
|
||||
) : AbsMultiSelectAdapter<SongAdapter.ViewHolder, Song>(
|
||||
activity,
|
||||
cabHolder,
|
||||
R.menu.menu_media_selection
|
||||
), MaterialCab.Callback, PopupTextProvider {
|
||||
), PopupTextProvider {
|
||||
|
||||
private var showSectionName = true
|
||||
|
||||
|
@ -58,7 +72,7 @@ open class SongAdapter(
|
|||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return dataSet[position].id.toLong()
|
||||
return dataSet[position].id
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
|
@ -79,14 +93,15 @@ open class SongAdapter(
|
|||
val song = dataSet[position]
|
||||
val isChecked = isChecked(song)
|
||||
holder.itemView.isActivated = isChecked
|
||||
if (isChecked) {
|
||||
holder.menu?.hide()
|
||||
} else {
|
||||
holder.menu?.show()
|
||||
}
|
||||
holder.menu?.isGone = isChecked
|
||||
holder.title?.text = getSongTitle(song)
|
||||
holder.text?.text = getSongText(song)
|
||||
holder.text2?.text = getSongText(song)
|
||||
loadAlbumCover(song, holder)
|
||||
val landscape = RetroUtil.isLandscape
|
||||
if ((PreferenceUtil.songGridSize > 2 && !landscape) || (PreferenceUtil.songGridSizeLand > 5 && landscape)) {
|
||||
holder.menu?.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) {
|
||||
|
@ -94,6 +109,7 @@ open class SongAdapter(
|
|||
holder.title?.setTextColor(color.primaryTextColor)
|
||||
holder.text?.setTextColor(color.secondaryTextColor)
|
||||
holder.paletteColorContainer?.setBackgroundColor(color.backgroundColor)
|
||||
holder.menu?.imageTintList = ColorStateList.valueOf(color.primaryTextColor)
|
||||
}
|
||||
holder.mask?.backgroundTintList = ColorStateList.valueOf(color.primaryTextColor)
|
||||
}
|
||||
|
@ -102,9 +118,10 @@ open class SongAdapter(
|
|||
if (holder.image == null) {
|
||||
return
|
||||
}
|
||||
SongGlideRequest.Builder.from(Glide.with(activity), song)
|
||||
.checkIgnoreMediaStore(activity)
|
||||
.generatePalette(activity).build()
|
||||
Glide.with(activity)
|
||||
.asBitmapPalette()
|
||||
.songCoverOptions(song)
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.into(object : RetroMusicColoredTarget(holder.image!!) {
|
||||
override fun onColorReady(colors: MediaNotificationProcessor) {
|
||||
setColors(colors, holder)
|
||||
|
@ -112,14 +129,18 @@ open class SongAdapter(
|
|||
})
|
||||
}
|
||||
|
||||
private fun getSongTitle(song: Song): String? {
|
||||
private fun getSongTitle(song: Song): String {
|
||||
return song.title
|
||||
}
|
||||
|
||||
private fun getSongText(song: Song): String? {
|
||||
private fun getSongText(song: Song): String {
|
||||
return song.artistName
|
||||
}
|
||||
|
||||
private fun getSongText2(song: Song): String {
|
||||
return song.albumName
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return dataSet.size
|
||||
}
|
||||
|
@ -128,26 +149,31 @@ open class SongAdapter(
|
|||
return dataSet[position]
|
||||
}
|
||||
|
||||
override fun getName(song: Song): String {
|
||||
return song.title
|
||||
override fun getName(model: Song): String {
|
||||
return model.title
|
||||
}
|
||||
|
||||
override fun onMultipleItemAction(menuItem: MenuItem, selection: ArrayList<Song>) {
|
||||
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Song>) {
|
||||
SongsMenuHelper.handleMenuClick(activity, selection, menuItem.itemId)
|
||||
}
|
||||
|
||||
override fun getPopupText(position: Int): String {
|
||||
val sectionName: String? = when (PreferenceUtil.songSortOrder) {
|
||||
SortOrder.SongSortOrder.SONG_DEFAULT -> return MusicUtil.getSectionName(
|
||||
dataSet[position].title,
|
||||
true
|
||||
)
|
||||
|
||||
SortOrder.SongSortOrder.SONG_A_Z, SortOrder.SongSortOrder.SONG_Z_A -> dataSet[position].title
|
||||
SortOrder.SongSortOrder.SONG_ALBUM -> dataSet[position].albumName
|
||||
SortOrder.SongSortOrder.SONG_ARTIST -> dataSet[position].artistName
|
||||
SortOrder.SongSortOrder.SONG_YEAR -> return MusicUtil.getYearString(dataSet[position].year)
|
||||
SortOrder.SongSortOrder.COMPOSER -> dataSet[position].composer
|
||||
SortOrder.SongSortOrder.SONG_ALBUM_ARTIST -> dataSet[position].albumArtist
|
||||
else -> {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
return MusicUtil.getSectionName(sectionName)
|
||||
}
|
||||
|
||||
|
@ -157,7 +183,6 @@ open class SongAdapter(
|
|||
get() = dataSet[layoutPosition]
|
||||
|
||||
init {
|
||||
setImageTransitionName(activity.getString(R.string.transition_album_art))
|
||||
menu?.setOnClickListener(object : SongMenuHelper.OnClickSongMenu(activity) {
|
||||
override val song: Song
|
||||
get() = this@ViewHolder.song
|
||||
|
@ -172,15 +197,14 @@ open class SongAdapter(
|
|||
}
|
||||
|
||||
protected open fun onSongMenuItemClick(item: MenuItem): Boolean {
|
||||
if (image != null && image!!.visibility == View.VISIBLE) {
|
||||
if (image != null && image!!.isVisible) {
|
||||
when (item.itemId) {
|
||||
R.id.action_go_to_album -> {
|
||||
val activityOptions = ActivityOptions.makeSceneTransitionAnimation(
|
||||
activity,
|
||||
imageContainerCard ?: image,
|
||||
activity.getString(R.string.transition_album_art)
|
||||
)
|
||||
NavigationUtil.goToAlbumOptions(activity, song.albumId, activityOptions)
|
||||
activity.findNavController(R.id.fragment_container)
|
||||
.navigate(
|
||||
R.id.albumDetailsFragment,
|
||||
bundleOf(EXTRA_ALBUM_ID to song.albumId)
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -197,6 +221,7 @@ open class SongAdapter(
|
|||
}
|
||||
|
||||
override fun onLongClick(v: View?): Boolean {
|
||||
println("Long click")
|
||||
return toggleChecked(layoutPosition)
|
||||
}
|
||||
}
|
|
@ -1,32 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.muntashirakon.music.appshortcuts
|
||||
package code.name.monkey.retromusic.appshortcuts
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.Icon
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.os.Build
|
||||
import android.util.TypedValue
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.util.PreferenceUtil
|
||||
import io.github.muntashirakon.music.util.RetroUtil
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.extensions.getTintedDrawable
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N_MR1)
|
||||
object AppShortcutIconGenerator {
|
||||
|
@ -60,28 +58,20 @@ object AppShortcutIconGenerator {
|
|||
}
|
||||
|
||||
private fun generateThemedIcon(
|
||||
context: Context, iconId: Int, foregroundColor: Int, backgroundColor: Int
|
||||
context: Context,
|
||||
iconId: Int,
|
||||
foregroundColor: Int,
|
||||
backgroundColor: Int,
|
||||
): Icon {
|
||||
// Get and tint foreground and background drawables
|
||||
val vectorDrawable = RetroUtil.getTintedVectorDrawable(context, iconId, foregroundColor)
|
||||
val backgroundDrawable = RetroUtil.getTintedVectorDrawable(
|
||||
context, R.drawable.ic_app_shortcut_background, backgroundColor
|
||||
)
|
||||
val vectorDrawable = context.getTintedDrawable(iconId, foregroundColor)
|
||||
val backgroundDrawable =
|
||||
context.getTintedDrawable(R.drawable.ic_app_shortcut_background, backgroundColor)
|
||||
|
||||
// Squash the two drawables together
|
||||
val layerDrawable = LayerDrawable(arrayOf(backgroundDrawable, vectorDrawable))
|
||||
|
||||
// Return as an Icon
|
||||
return Icon.createWithBitmap(drawableToBitmap(layerDrawable))
|
||||
}
|
||||
|
||||
private fun drawableToBitmap(drawable: Drawable): Bitmap {
|
||||
val bitmap = Bitmap.createBitmap(
|
||||
drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888
|
||||
)
|
||||
val canvas = Canvas(bitmap)
|
||||
drawable.setBounds(0, 0, canvas.width, canvas.height)
|
||||
drawable.draw(canvas)
|
||||
return bitmap
|
||||
return Icon.createWithBitmap(layerDrawable.toBitmap())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.appshortcuts
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.core.os.bundleOf
|
||||
import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType
|
||||
import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType
|
||||
import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType
|
||||
import code.name.monkey.retromusic.extensions.extraNotNull
|
||||
import code.name.monkey.retromusic.model.Playlist
|
||||
import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist
|
||||
import code.name.monkey.retromusic.model.smartplaylist.ShuffleAllPlaylist
|
||||
import code.name.monkey.retromusic.model.smartplaylist.TopTracksPlaylist
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_PLAY_PLAYLIST
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.INTENT_EXTRA_PLAYLIST
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.INTENT_EXTRA_SHUFFLE_MODE
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.SHUFFLE_MODE_NONE
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.SHUFFLE_MODE_SHUFFLE
|
||||
|
||||
class AppShortcutLauncherActivity : Activity() {
|
||||
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
when (extraNotNull(KEY_SHORTCUT_TYPE, SHORTCUT_TYPE_NONE).value) {
|
||||
SHORTCUT_TYPE_SHUFFLE_ALL -> {
|
||||
startServiceWithPlaylist(
|
||||
SHUFFLE_MODE_SHUFFLE, ShuffleAllPlaylist()
|
||||
)
|
||||
DynamicShortcutManager.reportShortcutUsed(this, ShuffleAllShortcutType.id)
|
||||
}
|
||||
SHORTCUT_TYPE_TOP_TRACKS -> {
|
||||
startServiceWithPlaylist(
|
||||
SHUFFLE_MODE_NONE, TopTracksPlaylist()
|
||||
)
|
||||
DynamicShortcutManager.reportShortcutUsed(this, TopTracksShortcutType.id)
|
||||
}
|
||||
SHORTCUT_TYPE_LAST_ADDED -> {
|
||||
startServiceWithPlaylist(
|
||||
SHUFFLE_MODE_NONE, LastAddedPlaylist()
|
||||
)
|
||||
DynamicShortcutManager.reportShortcutUsed(this, LastAddedShortcutType.id)
|
||||
}
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun startServiceWithPlaylist(shuffleMode: Int, playlist: Playlist) {
|
||||
val intent = Intent(this, MusicService::class.java)
|
||||
intent.action = ACTION_PLAY_PLAYLIST
|
||||
|
||||
val bundle = bundleOf(
|
||||
INTENT_EXTRA_PLAYLIST to playlist,
|
||||
INTENT_EXTRA_SHUFFLE_MODE to shuffleMode
|
||||
)
|
||||
|
||||
intent.putExtras(bundle)
|
||||
|
||||
startService(intent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEY_SHORTCUT_TYPE = "io.github.muntashirakon.Music.appshortcuts.ShortcutType"
|
||||
const val SHORTCUT_TYPE_SHUFFLE_ALL = 0L
|
||||
const val SHORTCUT_TYPE_TOP_TRACKS = 1L
|
||||
const val SHORTCUT_TYPE_LAST_ADDED = 2L
|
||||
const val SHORTCUT_TYPE_NONE = 4L
|
||||
}
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.muntashirakon.music.appshortcuts
|
||||
package code.name.monkey.retromusic.appshortcuts
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
|
@ -21,34 +21,31 @@ import android.content.pm.ShortcutInfo
|
|||
import android.content.pm.ShortcutManager
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Build
|
||||
import io.github.muntashirakon.music.appshortcuts.shortcuttype.LastAddedShortcutType
|
||||
import io.github.muntashirakon.music.appshortcuts.shortcuttype.SearchShortCutType
|
||||
import io.github.muntashirakon.music.appshortcuts.shortcuttype.ShuffleAllShortcutType
|
||||
import io.github.muntashirakon.music.appshortcuts.shortcuttype.TopTracksShortcutType
|
||||
import java.util.*
|
||||
import androidx.core.content.getSystemService
|
||||
import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType
|
||||
import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType
|
||||
import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
class DynamicShortcutManager(private val context: Context) {
|
||||
private val shortcutManager: ShortcutManager =
|
||||
this.context.getSystemService(ShortcutManager::class.java)
|
||||
private val shortcutManager: ShortcutManager? =
|
||||
this.context.getSystemService()
|
||||
|
||||
private val defaultShortcuts: List<ShortcutInfo>
|
||||
get() = Arrays.asList(
|
||||
SearchShortCutType(context).shortcutInfo,
|
||||
get() = listOf(
|
||||
ShuffleAllShortcutType(context).shortcutInfo,
|
||||
TopTracksShortcutType(context).shortcutInfo,
|
||||
LastAddedShortcutType(context).shortcutInfo
|
||||
|
||||
)
|
||||
|
||||
fun initDynamicShortcuts() {
|
||||
//if (shortcutManager.dynamicShortcuts.size == 0) {
|
||||
shortcutManager.dynamicShortcuts = defaultShortcuts
|
||||
//}
|
||||
// if (shortcutManager.dynamicShortcuts.size == 0) {
|
||||
shortcutManager?.dynamicShortcuts = defaultShortcuts
|
||||
// }
|
||||
}
|
||||
|
||||
fun updateDynamicShortcuts() {
|
||||
shortcutManager.updateShortcuts(defaultShortcuts)
|
||||
shortcutManager?.updateShortcuts(defaultShortcuts)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -61,12 +58,16 @@ class DynamicShortcutManager(private val context: Context) {
|
|||
icon: Icon,
|
||||
intent: Intent
|
||||
): ShortcutInfo {
|
||||
return ShortcutInfo.Builder(context, id).setShortLabel(shortLabel)
|
||||
.setLongLabel(longLabel).setIcon(icon).setIntent(intent).build()
|
||||
return ShortcutInfo.Builder(context, id)
|
||||
.setShortLabel(shortLabel)
|
||||
.setLongLabel(longLabel)
|
||||
.setIcon(icon)
|
||||
.setIntent(intent)
|
||||
.build()
|
||||
}
|
||||
|
||||
fun reportShortcutUsed(context: Context, shortcutId: String) {
|
||||
context.getSystemService(ShortcutManager::class.java).reportShortcutUsed(shortcutId)
|
||||
context.getSystemService<ShortcutManager>()?.reportShortcutUsed(shortcutId)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.muntashirakon.music.appshortcuts.shortcuttype
|
||||
package code.name.monkey.retromusic.appshortcuts.shortcuttype
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import io.github.muntashirakon.music.appshortcuts.AppShortcutLauncherActivity
|
||||
import androidx.core.os.bundleOf
|
||||
import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
abstract class BaseShortcutType(internal var context: Context) {
|
||||
|
@ -33,11 +33,10 @@ abstract class BaseShortcutType(internal var context: Context) {
|
|||
* @param shortcutType Describes the type of shortcut to create (ShuffleAll, TopTracks, custom playlist, etc.)
|
||||
* @return
|
||||
*/
|
||||
internal fun getPlaySongsIntent(shortcutType: Int): Intent {
|
||||
internal fun getPlaySongsIntent(shortcutType: Long): Intent {
|
||||
val intent = Intent(context, AppShortcutLauncherActivity::class.java)
|
||||
intent.action = Intent.ACTION_VIEW
|
||||
val b = Bundle()
|
||||
b.putInt(AppShortcutLauncherActivity.KEY_SHORTCUT_TYPE, shortcutType)
|
||||
val b = bundleOf(AppShortcutLauncherActivity.KEY_SHORTCUT_TYPE to shortcutType)
|
||||
intent.putExtras(b)
|
||||
return intent
|
||||
}
|
|
@ -1,26 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.muntashirakon.music.appshortcuts.shortcuttype
|
||||
package code.name.monkey.retromusic.appshortcuts.shortcuttype
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.os.Build
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.appshortcuts.AppShortcutIconGenerator
|
||||
import io.github.muntashirakon.music.appshortcuts.AppShortcutLauncherActivity
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator
|
||||
import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
class LastAddedShortcutType(context: Context) : BaseShortcutType(context) {
|
||||
|
@ -42,6 +42,6 @@ class LastAddedShortcutType(context: Context) : BaseShortcutType(context) {
|
|||
companion object {
|
||||
|
||||
val id: String
|
||||
get() = BaseShortcutType.ID_PREFIX + "last_added"
|
||||
get() = ID_PREFIX + "last_added"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.appshortcuts.shortcuttype
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.os.Build
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator
|
||||
import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
class ShuffleAllShortcutType(context: Context) : BaseShortcutType(context) {
|
||||
|
||||
override val shortcutInfo: ShortcutInfo
|
||||
get() = ShortcutInfo.Builder(context, id)
|
||||
.setShortLabel(context.getString(R.string.app_shortcut_shuffle_all_short))
|
||||
.setLongLabel(context.getString(R.string.app_shortcut_shuffle_all_long))
|
||||
.setIcon(AppShortcutIconGenerator.generateThemedIcon(context, R.drawable.ic_app_shortcut_shuffle_all))
|
||||
.setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_SHUFFLE_ALL))
|
||||
.build()
|
||||
|
||||
companion object {
|
||||
|
||||
val id: String
|
||||
get() = ID_PREFIX + "shuffle_all"
|
||||
}
|
||||
}
|
|
@ -1,26 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
package io.github.muntashirakon.music.appshortcuts.shortcuttype
|
||||
package code.name.monkey.retromusic.appshortcuts.shortcuttype
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.os.Build
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.appshortcuts.AppShortcutIconGenerator
|
||||
import io.github.muntashirakon.music.appshortcuts.AppShortcutLauncherActivity
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator
|
||||
import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
class TopTracksShortcutType(context: Context) : BaseShortcutType(context) {
|
||||
|
@ -40,6 +39,6 @@ class TopTracksShortcutType(context: Context) : BaseShortcutType(context) {
|
|||
companion object {
|
||||
|
||||
val id: String
|
||||
get() = BaseShortcutType.ID_PREFIX + "top_tracks"
|
||||
get() = ID_PREFIX + "top_tracks"
|
||||
}
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.muntashirakon.music.appwidgets
|
||||
package code.name.monkey.retromusic.appwidgets
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.ComponentName
|
||||
|
@ -20,21 +20,26 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.activities.MainActivity
|
||||
import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget
|
||||
import io.github.muntashirakon.music.glide.SongGlideRequest
|
||||
import io.github.muntashirakon.music.service.MusicService
|
||||
import io.github.muntashirakon.music.service.MusicService.*
|
||||
import io.github.muntashirakon.music.util.RetroUtil
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.activities.MainActivity
|
||||
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
|
||||
import code.name.monkey.retromusic.extensions.getTintedDrawable
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.animation.GlideAnimation
|
||||
import com.bumptech.glide.request.target.SimpleTarget
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
|
||||
class AppWidgetBig : BaseAppWidget() {
|
||||
private var target: Target<Bitmap>? = null // for cancellation
|
||||
|
@ -54,31 +59,24 @@ class AppWidgetBig : BaseAppWidget() {
|
|||
)
|
||||
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_skip_next,
|
||||
MaterialValueHelper.getPrimaryTextColor(context, false)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_next, context.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
MaterialValueHelper.getPrimaryTextColor(context, false)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_skip_previous,
|
||||
MaterialValueHelper.getPrimaryTextColor(context, false)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_prev,
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
MaterialValueHelper.getPrimaryTextColor(context, false)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause, BaseAppWidget.Companion.createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_play_arrow_white_32dp,
|
||||
MaterialValueHelper.getPrimaryTextColor(context, false)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_toggle_play_pause,
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_play_arrow_white_32dp,
|
||||
MaterialValueHelper.getPrimaryTextColor(context, false)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
linkButtons(context, appWidgetView)
|
||||
|
@ -97,7 +95,7 @@ class AppWidgetBig : BaseAppWidget() {
|
|||
val song = service.currentSong
|
||||
|
||||
// Set the titles and artwork
|
||||
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
|
||||
if (song.title.isEmpty() && song.artistName.isEmpty()) {
|
||||
appWidgetView.setViewVisibility(
|
||||
R.id.media_titles,
|
||||
View.INVISIBLE
|
||||
|
@ -119,33 +117,27 @@ class AppWidgetBig : BaseAppWidget() {
|
|||
val playPauseRes =
|
||||
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow_white_32dp
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
service,
|
||||
playPauseRes,
|
||||
primaryColor
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_toggle_play_pause,
|
||||
service.getTintedDrawable(
|
||||
playPauseRes,
|
||||
primaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
// Set prev/next button drawables
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
service,
|
||||
R.drawable.ic_skip_next,
|
||||
primaryColor
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_next,
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
primaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
service,
|
||||
R.drawable.ic_skip_previous,
|
||||
primaryColor
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_prev,
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
primaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
// Link actions buttons to intents
|
||||
|
@ -153,27 +145,31 @@ class AppWidgetBig : BaseAppWidget() {
|
|||
|
||||
// Load the album cover async and push the update on completion
|
||||
val p = RetroUtil.getScreenSize(service)
|
||||
val widgetImageSize = Math.min(p.x, p.y)
|
||||
val widgetImageSize = p.x.coerceAtMost(p.y)
|
||||
val appContext = service.applicationContext
|
||||
service.runOnUiThread {
|
||||
if (target != null) {
|
||||
Glide.clear(target)
|
||||
Glide.with(service).clear(target)
|
||||
}
|
||||
target = SongGlideRequest.Builder.from(Glide.with(appContext), song)
|
||||
.checkIgnoreMediaStore(appContext).asBitmap().build()
|
||||
.into(object : SimpleTarget<Bitmap>(widgetImageSize, widgetImageSize) {
|
||||
target = Glide.with(appContext)
|
||||
.asBitmap()
|
||||
//.checkIgnoreMediaStore()
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.into(object : CustomTarget<Bitmap>(widgetImageSize, widgetImageSize) {
|
||||
override fun onResourceReady(
|
||||
resource: Bitmap,
|
||||
glideAnimation: GlideAnimation<in Bitmap>
|
||||
transition: Transition<in Bitmap>?,
|
||||
) {
|
||||
update(resource)
|
||||
}
|
||||
|
||||
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(e, errorDrawable)
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(errorDrawable)
|
||||
update(null)
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {}
|
||||
|
||||
private fun update(bitmap: Bitmap?) {
|
||||
if (bitmap == null) {
|
||||
appWidgetView.setImageViewResource(
|
||||
|
@ -193,15 +189,22 @@ class AppWidgetBig : BaseAppWidget() {
|
|||
* Link up various button actions using [PendingIntent].
|
||||
*/
|
||||
private fun linkButtons(context: Context, views: RemoteViews) {
|
||||
val action =
|
||||
Intent(context, MainActivity::class.java).putExtra(MainActivity.EXPAND_PANEL, true)
|
||||
var pendingIntent: PendingIntent
|
||||
val action = Intent(context, MainActivity::class.java)
|
||||
.putExtra(
|
||||
MainActivity.EXPAND_PANEL,
|
||||
PreferenceUtil.isExpandPanel
|
||||
)
|
||||
|
||||
val serviceName = ComponentName(context, MusicService::class.java)
|
||||
|
||||
// Home
|
||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
|
||||
var pendingIntent =
|
||||
PendingIntent.getActivity(
|
||||
context, 0, action, if (VersionUtils.hasMarshmallow())
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
else 0
|
||||
)
|
||||
views.setOnClickPendingIntent(R.id.clickable_area, pendingIntent)
|
||||
|
||||
// Previous track
|
||||
|
@ -229,6 +232,5 @@ class AppWidgetBig : BaseAppWidget() {
|
|||
}
|
||||
return mInstance!!
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.muntashirakon.music.appwidgets
|
||||
package code.name.monkey.retromusic.appwidgets
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.ComponentName
|
||||
|
@ -20,23 +20,28 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.activities.MainActivity
|
||||
import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget
|
||||
import io.github.muntashirakon.music.glide.SongGlideRequest
|
||||
import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper
|
||||
import io.github.muntashirakon.music.service.MusicService
|
||||
import io.github.muntashirakon.music.service.MusicService.*
|
||||
import io.github.muntashirakon.music.util.ImageUtil
|
||||
import io.github.muntashirakon.music.util.RetroUtil
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.activities.MainActivity
|
||||
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
|
||||
import code.name.monkey.retromusic.extensions.getTintedDrawable
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
|
||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.animation.GlideAnimation
|
||||
import com.bumptech.glide.request.target.SimpleTarget
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
|
||||
class AppWidgetCard : BaseAppWidget() {
|
||||
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
|
||||
|
@ -52,31 +57,25 @@ class AppWidgetCard : BaseAppWidget() {
|
|||
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
|
||||
val secondaryColor = MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_skip_next,
|
||||
secondaryColor
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_next,
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
secondaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_skip_previous,
|
||||
secondaryColor
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_prev,
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
secondaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_play_arrow_white_32dp,
|
||||
secondaryColor
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_toggle_play_pause,
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_play_arrow_white_32dp,
|
||||
secondaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
linkButtons(context, appWidgetView)
|
||||
|
@ -93,7 +92,7 @@ class AppWidgetCard : BaseAppWidget() {
|
|||
val song = service.currentSong
|
||||
|
||||
// Set the titles and artwork
|
||||
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
|
||||
if (song.title.isEmpty() && song.artistName.isEmpty()) {
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||
} else {
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
|
||||
|
@ -105,33 +104,27 @@ class AppWidgetCard : BaseAppWidget() {
|
|||
val playPauseRes =
|
||||
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow_white_32dp
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
service,
|
||||
playPauseRes,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_toggle_play_pause,
|
||||
service.getTintedDrawable(
|
||||
playPauseRes,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
// Set prev/next button drawables
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
service,
|
||||
R.drawable.ic_skip_next,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_next,
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
service,
|
||||
R.drawable.ic_skip_previous,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_prev,
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
// Link actions buttons to intents
|
||||
|
@ -149,14 +142,17 @@ class AppWidgetCard : BaseAppWidget() {
|
|||
// Load the album cover async and push the update on completion
|
||||
service.runOnUiThread {
|
||||
if (target != null) {
|
||||
Glide.clear(target)
|
||||
Glide.with(service).clear(target)
|
||||
}
|
||||
target = SongGlideRequest.Builder.from(Glide.with(service), song)
|
||||
.checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop()
|
||||
.into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
|
||||
target = Glide.with(service)
|
||||
.asBitmapPalette()
|
||||
.songCoverOptions(song)
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.centerCrop()
|
||||
.into(object : CustomTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
|
||||
override fun onResourceReady(
|
||||
resource: BitmapPaletteWrapper,
|
||||
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>
|
||||
transition: Transition<in BitmapPaletteWrapper>?,
|
||||
) {
|
||||
val palette = resource.palette
|
||||
update(
|
||||
|
@ -170,38 +166,31 @@ class AppWidgetCard : BaseAppWidget() {
|
|||
)
|
||||
}
|
||||
|
||||
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(e, errorDrawable)
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(errorDrawable)
|
||||
update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {}
|
||||
|
||||
private fun update(bitmap: Bitmap?, color: Int) {
|
||||
// Set correct drawable for pause state
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause, ImageUtil.createBitmap(
|
||||
ImageUtil.getTintedVectorDrawable(
|
||||
service, playPauseRes, color
|
||||
)
|
||||
)
|
||||
R.id.button_toggle_play_pause,
|
||||
service.getTintedDrawable(playPauseRes, color).toBitmap()
|
||||
)
|
||||
|
||||
// Set prev/next button drawables
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next, ImageUtil.createBitmap(
|
||||
ImageUtil.getTintedVectorDrawable(
|
||||
service, R.drawable.ic_skip_next, color
|
||||
)
|
||||
)
|
||||
R.id.button_next,
|
||||
service.getTintedDrawable(R.drawable.ic_skip_next, color).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev, ImageUtil.createBitmap(
|
||||
ImageUtil.getTintedVectorDrawable(
|
||||
service, R.drawable.ic_skip_previous, color
|
||||
)
|
||||
)
|
||||
R.id.button_prev,
|
||||
service.getTintedDrawable(R.drawable.ic_skip_previous, color).toBitmap()
|
||||
)
|
||||
|
||||
val image = getAlbumArtDrawable(service.resources, bitmap)
|
||||
val image = getAlbumArtDrawable(service, bitmap)
|
||||
val roundedBitmap = createRoundedBitmap(
|
||||
image, imageSize, imageSize, cardRadius, 0F, cardRadius, 0F
|
||||
)
|
||||
|
@ -217,14 +206,22 @@ class AppWidgetCard : BaseAppWidget() {
|
|||
* Link up various button actions using [PendingIntent].
|
||||
*/
|
||||
private fun linkButtons(context: Context, views: RemoteViews) {
|
||||
val action: Intent = Intent(context, MainActivity::class.java).putExtra("expand", true)
|
||||
var pendingIntent: PendingIntent
|
||||
val action = Intent(context, MainActivity::class.java)
|
||||
.putExtra(
|
||||
MainActivity.EXPAND_PANEL,
|
||||
PreferenceUtil.isExpandPanel
|
||||
)
|
||||
|
||||
val serviceName = ComponentName(context, MusicService::class.java)
|
||||
|
||||
// Home
|
||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
|
||||
var pendingIntent =
|
||||
PendingIntent.getActivity(
|
||||
context, 0, action, if (VersionUtils.hasMarshmallow())
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
else 0
|
||||
)
|
||||
views.setOnClickPendingIntent(R.id.image, pendingIntent)
|
||||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.appwidgets
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.activities.MainActivity
|
||||
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
|
||||
import code.name.monkey.retromusic.extensions.getTintedDrawable
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
|
||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.TOGGLE_FAVORITE
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class AppWidgetCircle : BaseAppWidget() {
|
||||
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
|
||||
|
||||
/**
|
||||
* Initialize given widgets to default state, where we launch Music on default click and hide
|
||||
* actions if service not running.
|
||||
*/
|
||||
override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
|
||||
val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_circle)
|
||||
|
||||
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
|
||||
val secondaryColor = MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause,
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_play_arrow,
|
||||
secondaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
linkButtons(context, appWidgetView)
|
||||
pushUpdate(context, appWidgetIds, appWidgetView)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all active widget instances by pushing changes
|
||||
*/
|
||||
override fun performUpdate(service: MusicService, appWidgetIds: IntArray?) {
|
||||
val appWidgetView = RemoteViews(service.packageName, R.layout.app_widget_circle)
|
||||
|
||||
val isPlaying = service.isPlaying
|
||||
val song = service.currentSong
|
||||
|
||||
// Set correct drawable for pause state
|
||||
val playPauseRes =
|
||||
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause,
|
||||
service.getTintedDrawable(
|
||||
playPauseRes,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
).toBitmap()
|
||||
)
|
||||
val isFavorite = runBlocking(Dispatchers.IO) {
|
||||
return@runBlocking MusicUtil.isFavorite(song)
|
||||
}
|
||||
val favoriteRes =
|
||||
if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_favorite,
|
||||
service.getTintedDrawable(
|
||||
favoriteRes,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
// Link actions buttons to intents
|
||||
linkButtons(service, appWidgetView)
|
||||
|
||||
if (imageSize == 0) {
|
||||
val p = RetroUtil.getScreenSize(service)
|
||||
imageSize = p.x.coerceAtMost(p.y)
|
||||
}
|
||||
|
||||
// Load the album cover async and push the update on completion
|
||||
service.runOnUiThread {
|
||||
if (target != null) {
|
||||
Glide.with(service).clear(target)
|
||||
}
|
||||
target = Glide.with(service)
|
||||
.asBitmapPalette()
|
||||
.songCoverOptions(song)
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.apply(RequestOptions.circleCropTransform())
|
||||
.into(object : CustomTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
|
||||
override fun onResourceReady(
|
||||
resource: BitmapPaletteWrapper,
|
||||
transition: Transition<in BitmapPaletteWrapper>?,
|
||||
) {
|
||||
val palette = resource.palette
|
||||
update(
|
||||
resource.bitmap, palette.getVibrantColor(
|
||||
palette.getMutedColor(
|
||||
MaterialValueHelper.getSecondaryTextColor(
|
||||
service, true
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(errorDrawable)
|
||||
update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
|
||||
}
|
||||
|
||||
private fun update(bitmap: Bitmap?, color: Int) {
|
||||
// Set correct drawable for pause state
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause,
|
||||
service.getTintedDrawable(
|
||||
playPauseRes, color
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
// Set favorite button drawables
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_favorite,
|
||||
service.getTintedDrawable(
|
||||
favoriteRes, color
|
||||
).toBitmap()
|
||||
)
|
||||
if (bitmap != null) {
|
||||
appWidgetView.setImageViewBitmap(R.id.image, bitmap)
|
||||
}
|
||||
|
||||
pushUpdate(service, appWidgetIds, appWidgetView)
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Link up various button actions using [PendingIntent].
|
||||
*/
|
||||
private fun linkButtons(context: Context, views: RemoteViews) {
|
||||
val action = Intent(context, MainActivity::class.java)
|
||||
.putExtra(
|
||||
MainActivity.EXPAND_PANEL,
|
||||
PreferenceUtil.isExpandPanel
|
||||
)
|
||||
|
||||
val serviceName = ComponentName(context, MusicService::class.java)
|
||||
|
||||
// Home
|
||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
var pendingIntent =
|
||||
PendingIntent.getActivity(
|
||||
context, 0, action, if (VersionUtils.hasMarshmallow())
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
else 0
|
||||
)
|
||||
views.setOnClickPendingIntent(R.id.image, pendingIntent)
|
||||
// Favorite track
|
||||
pendingIntent = buildPendingIntent(context, TOGGLE_FAVORITE, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_toggle_favorite, pendingIntent)
|
||||
|
||||
// Play and pause
|
||||
pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val NAME = "app_widget_circle"
|
||||
|
||||
private var mInstance: AppWidgetCircle? = null
|
||||
private var imageSize = 0
|
||||
|
||||
val instance: AppWidgetCircle
|
||||
@Synchronized get() {
|
||||
if (mInstance == null) {
|
||||
mInstance = AppWidgetCircle()
|
||||
}
|
||||
return mInstance!!
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.muntashirakon.music.appwidgets
|
||||
package code.name.monkey.retromusic.appwidgets
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.ComponentName
|
||||
|
@ -21,23 +21,28 @@ import android.content.Intent
|
|||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.activities.MainActivity
|
||||
import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget
|
||||
import io.github.muntashirakon.music.glide.SongGlideRequest
|
||||
import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper
|
||||
import io.github.muntashirakon.music.service.MusicService
|
||||
import io.github.muntashirakon.music.service.MusicService.*
|
||||
import io.github.muntashirakon.music.util.ImageUtil
|
||||
import io.github.muntashirakon.music.util.RetroUtil
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.activities.MainActivity
|
||||
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
|
||||
import code.name.monkey.retromusic.extensions.getTintedDrawable
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
|
||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.animation.GlideAnimation
|
||||
import com.bumptech.glide.request.target.SimpleTarget
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
|
||||
class AppWidgetClassic : BaseAppWidget() {
|
||||
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
|
||||
|
@ -49,38 +54,31 @@ class AppWidgetClassic : BaseAppWidget() {
|
|||
override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
|
||||
val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_classic)
|
||||
|
||||
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next,
|
||||
createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_skip_next,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
)!!, 1f
|
||||
)
|
||||
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev,
|
||||
createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_skip_previous,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
)!!, 1f
|
||||
)
|
||||
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause,
|
||||
createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_play_arrow_white_32dp,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
)!!, 1f
|
||||
)
|
||||
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_play_arrow_white_32dp,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
linkButtons(context, appWidgetView)
|
||||
|
@ -97,7 +95,7 @@ class AppWidgetClassic : BaseAppWidget() {
|
|||
val song = service.currentSong
|
||||
|
||||
// Set the titles and artwork
|
||||
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
|
||||
if (song.title.isEmpty() && song.artistName.isEmpty()) {
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||
} else {
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
|
||||
|
@ -120,14 +118,18 @@ class AppWidgetClassic : BaseAppWidget() {
|
|||
val appContext = service.applicationContext
|
||||
service.runOnUiThread {
|
||||
if (target != null) {
|
||||
Glide.clear(target)
|
||||
Glide.with(service).clear(target)
|
||||
}
|
||||
target = SongGlideRequest.Builder.from(Glide.with(service), song)
|
||||
.checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop()
|
||||
.into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
|
||||
target = Glide.with(service)
|
||||
.asBitmapPalette()
|
||||
.songCoverOptions(song)
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
//.checkIgnoreMediaStore()
|
||||
.centerCrop()
|
||||
.into(object : CustomTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
|
||||
override fun onResourceReady(
|
||||
resource: BitmapPaletteWrapper,
|
||||
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>
|
||||
transition: Transition<in BitmapPaletteWrapper>?,
|
||||
) {
|
||||
val palette = resource.palette
|
||||
update(
|
||||
|
@ -143,49 +145,42 @@ class AppWidgetClassic : BaseAppWidget() {
|
|||
)
|
||||
}
|
||||
|
||||
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(e, errorDrawable)
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(errorDrawable)
|
||||
update(null, Color.WHITE)
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {}
|
||||
|
||||
private fun update(bitmap: Bitmap?, color: Int) {
|
||||
// Set correct drawable for pause state
|
||||
val playPauseRes =
|
||||
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause,
|
||||
ImageUtil.createBitmap(
|
||||
ImageUtil.getTintedVectorDrawable(
|
||||
service,
|
||||
playPauseRes,
|
||||
color
|
||||
)
|
||||
)
|
||||
service.getTintedDrawable(
|
||||
playPauseRes,
|
||||
color
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
// Set prev/next button drawables
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next,
|
||||
ImageUtil.createBitmap(
|
||||
ImageUtil.getTintedVectorDrawable(
|
||||
service,
|
||||
R.drawable.ic_skip_next,
|
||||
color
|
||||
)
|
||||
)
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
color
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev,
|
||||
ImageUtil.createBitmap(
|
||||
ImageUtil.getTintedVectorDrawable(
|
||||
service,
|
||||
R.drawable.ic_skip_previous,
|
||||
color
|
||||
)
|
||||
)
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
color
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
val image = getAlbumArtDrawable(service.resources, bitmap)
|
||||
val image = getAlbumArtDrawable(service, bitmap)
|
||||
val roundedBitmap =
|
||||
createRoundedBitmap(
|
||||
image,
|
||||
|
@ -208,14 +203,21 @@ class AppWidgetClassic : BaseAppWidget() {
|
|||
* Link up various button actions using [PendingIntent].
|
||||
*/
|
||||
private fun linkButtons(context: Context, views: RemoteViews) {
|
||||
val action = Intent(context, MainActivity::class.java).putExtra("expand", true)
|
||||
var pendingIntent: PendingIntent
|
||||
val action = Intent(context, MainActivity::class.java)
|
||||
.putExtra(
|
||||
MainActivity.EXPAND_PANEL,
|
||||
PreferenceUtil.isExpandPanel
|
||||
)
|
||||
|
||||
val serviceName = ComponentName(context, MusicService::class.java)
|
||||
|
||||
// Home
|
||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
|
||||
var pendingIntent = PendingIntent.getActivity(
|
||||
context, 0, action, if (VersionUtils.hasMarshmallow())
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
else 0
|
||||
)
|
||||
views.setOnClickPendingIntent(R.id.image, pendingIntent)
|
||||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.appwidgets
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.activities.MainActivity
|
||||
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
|
||||
import code.name.monkey.retromusic.extensions.getTintedDrawable
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
|
||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
|
||||
import code.name.monkey.retromusic.util.DensityUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
|
||||
class AppWidgetMD3 : BaseAppWidget() {
|
||||
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
|
||||
|
||||
/**
|
||||
* Initialize given widgets to default state, where we launch Music on default click and hide
|
||||
* actions if service not running.
|
||||
*/
|
||||
override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
|
||||
val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_md3)
|
||||
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
|
||||
val secondaryColor = MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next,
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
secondaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev,
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
secondaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause,
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_play_arrow_white_32dp,
|
||||
secondaryColor
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
linkButtons(context, appWidgetView)
|
||||
pushUpdate(context, appWidgetIds, appWidgetView)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all active widget instances by pushing changes
|
||||
*/
|
||||
override fun performUpdate(service: MusicService, appWidgetIds: IntArray?) {
|
||||
val appWidgetView = RemoteViews(service.packageName, R.layout.app_widget_md3)
|
||||
|
||||
val isPlaying = service.isPlaying
|
||||
val song = service.currentSong
|
||||
|
||||
// Set the titles and artwork
|
||||
if (song.title.isEmpty() && song.artistName.isEmpty()) {
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||
} else {
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
|
||||
appWidgetView.setTextViewText(R.id.title, song.title)
|
||||
appWidgetView.setTextViewText(R.id.text, getSongArtistAndAlbum(song))
|
||||
}
|
||||
|
||||
// Set correct drawable for pause state
|
||||
val playPauseRes =
|
||||
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow_white_32dp
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause,
|
||||
service.getTintedDrawable(
|
||||
playPauseRes,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
// Set prev/next button drawables
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next,
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev,
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
MaterialValueHelper.getSecondaryTextColor(service, true)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
// Link actions buttons to intents
|
||||
linkButtons(service, appWidgetView)
|
||||
|
||||
if (imageSize == 0) {
|
||||
imageSize =
|
||||
service.resources.getDimensionPixelSize(R.dimen.app_widget_card_image_size)
|
||||
}
|
||||
if (cardRadius == 0f) {
|
||||
cardRadius =
|
||||
DensityUtil.dip2px(service, 8F).toFloat()
|
||||
}
|
||||
|
||||
// Load the album cover async and push the update on completion
|
||||
service.runOnUiThread {
|
||||
if (target != null) {
|
||||
Glide.with(service).clear(target)
|
||||
}
|
||||
target = Glide.with(service)
|
||||
.asBitmapPalette()
|
||||
.songCoverOptions(song)
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.centerCrop()
|
||||
.into(object : CustomTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
|
||||
override fun onResourceReady(
|
||||
resource: BitmapPaletteWrapper,
|
||||
transition: Transition<in BitmapPaletteWrapper>?,
|
||||
) {
|
||||
val palette = resource.palette
|
||||
update(
|
||||
resource.bitmap, palette.getVibrantColor(
|
||||
palette.getMutedColor(
|
||||
MaterialValueHelper.getSecondaryTextColor(
|
||||
service, true
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(errorDrawable)
|
||||
update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {}
|
||||
|
||||
private fun update(bitmap: Bitmap?, color: Int) {
|
||||
// Set correct drawable for pause state
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause,
|
||||
service.getTintedDrawable(playPauseRes, color).toBitmap()
|
||||
)
|
||||
|
||||
// Set prev/next button drawables
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next,
|
||||
service.getTintedDrawable(R.drawable.ic_skip_next, color).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev,
|
||||
service.getTintedDrawable(R.drawable.ic_skip_previous, color).toBitmap()
|
||||
)
|
||||
|
||||
val image = getAlbumArtDrawable(service, bitmap)
|
||||
val roundedBitmap = createRoundedBitmap(
|
||||
image,
|
||||
imageSize,
|
||||
imageSize,
|
||||
cardRadius,
|
||||
cardRadius,
|
||||
cardRadius,
|
||||
cardRadius
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap)
|
||||
|
||||
pushUpdate(service, appWidgetIds, appWidgetView)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Link up various button actions using [PendingIntent].
|
||||
*/
|
||||
private fun linkButtons(context: Context, views: RemoteViews) {
|
||||
val action = Intent(context, MainActivity::class.java)
|
||||
.putExtra(
|
||||
MainActivity.EXPAND_PANEL,
|
||||
PreferenceUtil.isExpandPanel
|
||||
)
|
||||
|
||||
val serviceName = ComponentName(context, MusicService::class.java)
|
||||
|
||||
// Home
|
||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
var pendingIntent =
|
||||
PendingIntent.getActivity(
|
||||
context, 0, action, if (VersionUtils.hasMarshmallow())
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
else 0
|
||||
)
|
||||
views.setOnClickPendingIntent(R.id.image, pendingIntent)
|
||||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||
|
||||
// Previous track
|
||||
pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
|
||||
|
||||
// Play and pause
|
||||
pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
|
||||
|
||||
// Next track
|
||||
pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val NAME = "app_widget_md3"
|
||||
|
||||
private var mInstance: AppWidgetMD3? = null
|
||||
private var imageSize = 0
|
||||
private var cardRadius = 0F
|
||||
|
||||
val instance: AppWidgetMD3
|
||||
@Synchronized get() {
|
||||
if (mInstance == null) {
|
||||
mInstance = AppWidgetMD3()
|
||||
}
|
||||
return mInstance!!
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.muntashirakon.music.appwidgets
|
||||
package code.name.monkey.retromusic.appwidgets
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.ComponentName
|
||||
|
@ -20,22 +20,28 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.activities.MainActivity
|
||||
import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget
|
||||
import io.github.muntashirakon.music.glide.SongGlideRequest
|
||||
import io.github.muntashirakon.music.glide.palette.BitmapPaletteWrapper
|
||||
import io.github.muntashirakon.music.service.MusicService
|
||||
import io.github.muntashirakon.music.service.MusicService.*
|
||||
import io.github.muntashirakon.music.util.RetroUtil
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.activities.MainActivity
|
||||
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
|
||||
import code.name.monkey.retromusic.extensions.getTintedDrawable
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
|
||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.animation.GlideAnimation
|
||||
import com.bumptech.glide.request.target.SimpleTarget
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
|
||||
class AppWidgetSmall : BaseAppWidget() {
|
||||
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
|
||||
|
@ -51,33 +57,26 @@ class AppWidgetSmall : BaseAppWidget() {
|
|||
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next,
|
||||
createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_skip_next,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
)!!, 1f
|
||||
)
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev,
|
||||
createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_skip_previous,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
)!!, 1f
|
||||
)
|
||||
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause,
|
||||
createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context,
|
||||
R.drawable.ic_play_arrow_white_32dp,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
)!!, 1f
|
||||
)
|
||||
|
||||
context.getTintedDrawable(
|
||||
R.drawable.ic_play_arrow_white_32dp,
|
||||
MaterialValueHelper.getSecondaryTextColor(context, true)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
linkButtons(context, appWidgetView)
|
||||
|
@ -94,10 +93,10 @@ class AppWidgetSmall : BaseAppWidget() {
|
|||
val song = service.currentSong
|
||||
|
||||
// Set the titles and artwork
|
||||
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
|
||||
if (song.title.isEmpty() && song.artistName.isEmpty()) {
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||
} else {
|
||||
if (TextUtils.isEmpty(song.title) || TextUtils.isEmpty(song.artistName)) {
|
||||
if (song.title.isEmpty() || song.artistName.isEmpty()) {
|
||||
appWidgetView.setTextViewText(R.id.text_separator, "")
|
||||
} else {
|
||||
appWidgetView.setTextViewText(R.id.text_separator, "•")
|
||||
|
@ -122,14 +121,18 @@ class AppWidgetSmall : BaseAppWidget() {
|
|||
val appContext = service.applicationContext
|
||||
service.runOnUiThread {
|
||||
if (target != null) {
|
||||
Glide.clear(target)
|
||||
Glide.with(service).clear(target)
|
||||
}
|
||||
target = SongGlideRequest.Builder.from(Glide.with(service), song)
|
||||
.checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop()
|
||||
.into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
|
||||
target = Glide.with(service)
|
||||
.asBitmapPalette()
|
||||
.songCoverOptions(song)
|
||||
//.checkIgnoreMediaStore()
|
||||
.load(RetroGlideExtension.getSongModel(song))
|
||||
.centerCrop()
|
||||
.into(object : CustomTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
|
||||
override fun onResourceReady(
|
||||
resource: BitmapPaletteWrapper,
|
||||
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>
|
||||
transition: Transition<in BitmapPaletteWrapper>?,
|
||||
) {
|
||||
val palette = resource.palette
|
||||
update(
|
||||
|
@ -143,8 +146,12 @@ class AppWidgetSmall : BaseAppWidget() {
|
|||
)
|
||||
}
|
||||
|
||||
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(e, errorDrawable)
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(errorDrawable)
|
||||
update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {
|
||||
update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
|
||||
}
|
||||
|
||||
|
@ -153,30 +160,21 @@ class AppWidgetSmall : BaseAppWidget() {
|
|||
val playPauseRes = if (isPlaying) R.drawable.ic_pause
|
||||
else R.drawable.ic_play_arrow_white_32dp
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
service, playPauseRes, color
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_toggle_play_pause,
|
||||
service.getTintedDrawable(playPauseRes, color).toBitmap()
|
||||
)
|
||||
|
||||
// Set prev/next button drawables
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
service, R.drawable.ic_skip_next, color
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_next,
|
||||
service.getTintedDrawable(R.drawable.ic_skip_next, color).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
service, R.drawable.ic_skip_previous, color
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_prev,
|
||||
service.getTintedDrawable(R.drawable.ic_skip_previous, color).toBitmap()
|
||||
)
|
||||
|
||||
val image = getAlbumArtDrawable(service.resources, bitmap)
|
||||
val image = getAlbumArtDrawable(service, bitmap)
|
||||
val roundedBitmap = createRoundedBitmap(
|
||||
image, imageSize, imageSize, cardRadius, 0f, 0f, 0f
|
||||
)
|
||||
|
@ -192,14 +190,22 @@ class AppWidgetSmall : BaseAppWidget() {
|
|||
* Link up various button actions using [PendingIntent].
|
||||
*/
|
||||
private fun linkButtons(context: Context, views: RemoteViews) {
|
||||
val action = Intent(context, MainActivity::class.java).putExtra("expand", true)
|
||||
var pendingIntent: PendingIntent
|
||||
val action = Intent(context, MainActivity::class.java)
|
||||
.putExtra(
|
||||
MainActivity.EXPAND_PANEL,
|
||||
PreferenceUtil.isExpandPanel
|
||||
)
|
||||
|
||||
val serviceName = ComponentName(context, MusicService::class.java)
|
||||
|
||||
// Home
|
||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
|
||||
var pendingIntent =
|
||||
PendingIntent.getActivity(
|
||||
context, 0, action, if (VersionUtils.hasMarshmallow())
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
else 0
|
||||
)
|
||||
views.setOnClickPendingIntent(R.id.image, pendingIntent)
|
||||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||
|
|
@ -1,72 +1,68 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.muntashirakon.music.appwidgets
|
||||
package code.name.monkey.retromusic.appwidgets
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.content.ContextCompat
|
||||
import io.github.muntashirakon.music.App
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.activities.MainActivity
|
||||
import io.github.muntashirakon.music.appwidgets.base.BaseAppWidget
|
||||
import io.github.muntashirakon.music.service.MusicService
|
||||
import io.github.muntashirakon.music.service.MusicService.*
|
||||
import io.github.muntashirakon.music.util.RetroUtil
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.activities.MainActivity
|
||||
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
|
||||
import code.name.monkey.retromusic.extensions.getTintedDrawable
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
|
||||
class AppWidgetText : BaseAppWidget() {
|
||||
override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
|
||||
val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_text)
|
||||
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context, R.drawable.ic_skip_next, ContextCompat.getColor(
|
||||
context, R.color.md_white_1000
|
||||
)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_next,
|
||||
context.getTintedDrawable(R.drawable.ic_skip_next, ContextCompat.getColor(
|
||||
context, code.name.monkey.appthemehelper.R.color.md_white_1000
|
||||
)).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context, R.drawable.ic_skip_previous, ContextCompat.getColor(
|
||||
context, R.color.md_white_1000
|
||||
)
|
||||
)!!, 1f
|
||||
R.id.button_prev,
|
||||
context.getTintedDrawable(R.drawable.ic_skip_previous, ContextCompat.getColor(
|
||||
context, code.name.monkey.appthemehelper.R.color.md_white_1000
|
||||
)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
context, R.drawable.ic_play_arrow_white_32dp, ContextCompat.getColor(
|
||||
context, R.color.md_white_1000
|
||||
)
|
||||
)!!, 1f
|
||||
R.id.button_toggle_play_pause,
|
||||
context.getTintedDrawable(R.drawable.ic_play_arrow_white_32dp, ContextCompat.getColor(
|
||||
context, code.name.monkey.appthemehelper.R.color.md_white_1000
|
||||
)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
appWidgetView.setTextColor(
|
||||
R.id.title, ContextCompat.getColor(context, R.color.md_white_1000)
|
||||
R.id.title, ContextCompat.getColor(context, code.name.monkey.appthemehelper.R.color.md_white_1000)
|
||||
)
|
||||
appWidgetView.setTextColor(
|
||||
R.id.text, ContextCompat.getColor(context, R.color.md_white_1000)
|
||||
R.id.text, ContextCompat.getColor(context, code.name.monkey.appthemehelper.R.color.md_white_1000)
|
||||
)
|
||||
|
||||
linkButtons(context, appWidgetView)
|
||||
|
@ -77,14 +73,21 @@ class AppWidgetText : BaseAppWidget() {
|
|||
* Link up various button actions using [PendingIntent].
|
||||
*/
|
||||
private fun linkButtons(context: Context, views: RemoteViews) {
|
||||
val action = Intent(context, MainActivity::class.java).putExtra("expand", true)
|
||||
var pendingIntent: PendingIntent
|
||||
val action = Intent(context, MainActivity::class.java)
|
||||
.putExtra(
|
||||
MainActivity.EXPAND_PANEL,
|
||||
PreferenceUtil.isExpandPanel
|
||||
)
|
||||
|
||||
val serviceName = ComponentName(context, MusicService::class.java)
|
||||
|
||||
// Home
|
||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
|
||||
var pendingIntent = PendingIntent.getActivity(
|
||||
context, 0, action, if (VersionUtils.hasMarshmallow())
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
else 0
|
||||
)
|
||||
views.setOnClickPendingIntent(R.id.image, pendingIntent)
|
||||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||
|
||||
|
@ -108,7 +111,7 @@ class AppWidgetText : BaseAppWidget() {
|
|||
val song = service.currentSong
|
||||
|
||||
// Set the titles and artwork
|
||||
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
|
||||
if (song.title.isEmpty() && song.artistName.isEmpty()) {
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||
} else {
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
|
||||
|
@ -122,41 +125,32 @@ class AppWidgetText : BaseAppWidget() {
|
|||
val playPauseRes = if (isPlaying) R.drawable.ic_pause
|
||||
else R.drawable.ic_play_arrow_white_32dp
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_toggle_play_pause, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
App.getContext(), playPauseRes, ContextCompat.getColor(
|
||||
App.getContext(), R.color.md_white_1000
|
||||
)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_toggle_play_pause,
|
||||
service.getTintedDrawable(playPauseRes, ContextCompat.getColor(
|
||||
service, code.name.monkey.appthemehelper.R.color.md_white_1000)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_next, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
App.getContext(),
|
||||
R.drawable.ic_skip_next,
|
||||
ContextCompat.getColor(
|
||||
App.getContext(), R.color.md_white_1000
|
||||
)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_next,
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_next,
|
||||
ContextCompat.getColor(
|
||||
service,
|
||||
code.name.monkey.appthemehelper.R.color.md_white_1000
|
||||
)
|
||||
).toBitmap()
|
||||
)
|
||||
appWidgetView.setImageViewBitmap(
|
||||
R.id.button_prev, createBitmap(
|
||||
RetroUtil.getTintedVectorDrawable(
|
||||
App.getContext(),
|
||||
R.drawable.ic_skip_previous,
|
||||
ContextCompat.getColor(
|
||||
App.getContext(), R.color.md_white_1000
|
||||
)
|
||||
)!!, 1f
|
||||
)
|
||||
R.id.button_prev,
|
||||
service.getTintedDrawable(
|
||||
R.drawable.ic_skip_previous,
|
||||
ContextCompat.getColor(
|
||||
service, code.name.monkey.appthemehelper.R.color.md_white_1000
|
||||
)
|
||||
).toBitmap()
|
||||
)
|
||||
|
||||
|
||||
|
||||
pushUpdate(service.applicationContext, appWidgetIds, appWidgetView)
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
|
@ -1,18 +1,18 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.muntashirakon.music.appwidgets
|
||||
package code.name.monkey.retromusic.appwidgets
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.BroadcastReceiver
|
||||
|
@ -20,7 +20,7 @@ import android.content.ComponentName
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import io.github.muntashirakon.music.service.MusicService
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
|
||||
class BootReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
|
@ -1,18 +1,18 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.muntashirakon.music.appwidgets.base
|
||||
package code.name.monkey.retromusic.appwidgets.base
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
|
@ -20,19 +20,20 @@ import android.appwidget.AppWidgetProvider
|
|||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.graphics.*
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.content.ContextCompat
|
||||
import io.github.muntashirakon.music.App
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.model.Song
|
||||
import io.github.muntashirakon.music.service.MusicService
|
||||
import io.github.muntashirakon.music.service.MusicService.*
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.APP_WIDGET_UPDATE
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.EXTRA_APP_WIDGET_NAME
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.FAVORITE_STATE_CHANGED
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.META_CHANGED
|
||||
import code.name.monkey.retromusic.service.MusicService.Companion.PLAY_STATE_CHANGED
|
||||
|
||||
abstract class BaseAppWidget : AppWidgetProvider() {
|
||||
|
||||
|
@ -40,7 +41,9 @@ abstract class BaseAppWidget : AppWidgetProvider() {
|
|||
* {@inheritDoc}
|
||||
*/
|
||||
override fun onUpdate(
|
||||
context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray
|
||||
) {
|
||||
defaultAppWidget(context, appWidgetIds)
|
||||
val updateIntent = Intent(APP_WIDGET_UPDATE)
|
||||
|
@ -55,14 +58,16 @@ abstract class BaseAppWidget : AppWidgetProvider() {
|
|||
*/
|
||||
fun notifyChange(service: MusicService, what: String) {
|
||||
if (hasInstances(service)) {
|
||||
if (META_CHANGED == what || PLAY_STATE_CHANGED == what) {
|
||||
if (META_CHANGED == what || PLAY_STATE_CHANGED == what || FAVORITE_STATE_CHANGED == what) {
|
||||
performUpdate(service, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun pushUpdate(
|
||||
context: Context, appWidgetIds: IntArray?, views: RemoteViews
|
||||
context: Context,
|
||||
appWidgetIds: IntArray?,
|
||||
views: RemoteViews
|
||||
) {
|
||||
val appWidgetManager = AppWidgetManager.getInstance(context)
|
||||
if (appWidgetIds != null) {
|
||||
|
@ -86,14 +91,20 @@ abstract class BaseAppWidget : AppWidgetProvider() {
|
|||
}
|
||||
|
||||
protected fun buildPendingIntent(
|
||||
context: Context, action: String, serviceName: ComponentName
|
||||
context: Context,
|
||||
action: String,
|
||||
serviceName: ComponentName
|
||||
): PendingIntent {
|
||||
val intent = Intent(action)
|
||||
intent.component = serviceName
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
PendingIntent.getForegroundService(context, 0, intent, 0)
|
||||
return if (VersionUtils.hasOreo()) {
|
||||
PendingIntent.getForegroundService(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||
} else {
|
||||
PendingIntent.getService(context, 0, intent, 0)
|
||||
PendingIntent.getService(
|
||||
context, 0, intent, if (VersionUtils.hasMarshmallow())
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
else 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,18 +112,18 @@ abstract class BaseAppWidget : AppWidgetProvider() {
|
|||
|
||||
abstract fun performUpdate(service: MusicService, appWidgetIds: IntArray?)
|
||||
|
||||
protected fun getAlbumArtDrawable(resources: Resources, bitmap: Bitmap?): Drawable {
|
||||
protected fun getAlbumArtDrawable(context: Context, bitmap: Bitmap?): Drawable {
|
||||
return if (bitmap == null) {
|
||||
ContextCompat.getDrawable(App.getContext(), R.drawable.default_audio_art)!!
|
||||
ContextCompat.getDrawable(context, R.drawable.default_audio_art)!!
|
||||
} else {
|
||||
BitmapDrawable(resources, bitmap)
|
||||
BitmapDrawable(context.resources, bitmap)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun getSongArtistAndAlbum(song: Song): String {
|
||||
val builder = StringBuilder()
|
||||
builder.append(song.artistName)
|
||||
if (!TextUtils.isEmpty(song.artistName) && !TextUtils.isEmpty(song.albumName)) {
|
||||
if (song.artistName.isNotEmpty() && song.albumName.isNotEmpty()) {
|
||||
builder.append(" • ")
|
||||
}
|
||||
builder.append(song.albumName)
|
||||
|
@ -156,20 +167,12 @@ abstract class BaseAppWidget : AppWidgetProvider() {
|
|||
return rounded
|
||||
}
|
||||
|
||||
fun createBitmap(drawable: Drawable, sizeMultiplier: Float): Bitmap {
|
||||
val bitmap = Bitmap.createBitmap(
|
||||
(drawable.intrinsicWidth * sizeMultiplier).toInt(),
|
||||
(drawable.intrinsicHeight * sizeMultiplier).toInt(),
|
||||
Bitmap.Config.ARGB_8888
|
||||
)
|
||||
val c = Canvas(bitmap)
|
||||
drawable.setBounds(0, 0, c.width, c.height)
|
||||
drawable.draw(c)
|
||||
return bitmap
|
||||
}
|
||||
|
||||
protected fun composeRoundedRectPath(
|
||||
rect: RectF, tl: Float, tr: Float, bl: Float, br: Float
|
||||
rect: RectF,
|
||||
tl: Float,
|
||||
tr: Float,
|
||||
bl: Float,
|
||||
br: Float
|
||||
): Path {
|
||||
val path = Path()
|
||||
path.moveTo(rect.left + tl, rect.top)
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.auto;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Created by Beesham Sarendranauth (Beesham)
|
||||
*/
|
||||
public class AutoMediaIDHelper {
|
||||
|
||||
// Media IDs used on browseable items of MediaBrowser
|
||||
public static final String MEDIA_ID_EMPTY_ROOT = "__EMPTY_ROOT__";
|
||||
public static final String MEDIA_ID_ROOT = "__ROOT__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_SEARCH = "__BY_SEARCH__"; // TODO
|
||||
public static final String MEDIA_ID_MUSICS_BY_HISTORY = "__BY_HISTORY__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_TOP_TRACKS = "__BY_TOP_TRACKS__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_SUGGESTIONS = "__BY_SUGGESTIONS__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_PLAYLIST = "__BY_PLAYLIST__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_ALBUM = "__BY_ALBUM__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_ARTIST = "__BY_ARTIST__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_ALBUM_ARTIST = "__BY_ALBUM_ARTIST__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_GENRE = "__BY_GENRE__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_SHUFFLE = "__BY_SHUFFLE__";
|
||||
public static final String MEDIA_ID_MUSICS_BY_QUEUE = "__BY_QUEUE__";
|
||||
public static final String RECENT_ROOT = "__RECENT__";
|
||||
|
||||
private static final String CATEGORY_SEPARATOR = "__/__";
|
||||
private static final String LEAF_SEPARATOR = "__|__";
|
||||
|
||||
/**
|
||||
* Create a String value that represents a playable or a browsable media.
|
||||
* <p/>
|
||||
* Encode the media browseable categories, if any, and the unique music ID, if any,
|
||||
* into a single String mediaID.
|
||||
* <p/>
|
||||
* MediaIDs are of the form <categoryType>__/__<categoryValue>__|__<musicUniqueId>, to make it
|
||||
* easy to find the category (like genre) that a music was selected from, so we
|
||||
* can correctly build the playing queue. This is specially useful when
|
||||
* one music can appear in more than one list, like "by genre -> genre_1"
|
||||
* and "by artist -> artist_1".
|
||||
*
|
||||
* @param mediaID Unique ID for playable items, or null for browseable items.
|
||||
* @param categories Hierarchy of categories representing this item's browsing parents.
|
||||
* @return A hierarchy-aware media ID.
|
||||
*/
|
||||
public static String createMediaID(String mediaID, String... categories) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (categories != null) {
|
||||
for (int i = 0; i < categories.length; i++) {
|
||||
if (!isValidCategory(categories[i])) {
|
||||
throw new IllegalArgumentException("Invalid category: " + categories[i]);
|
||||
}
|
||||
sb.append(categories[i]);
|
||||
if (i < categories.length - 1) {
|
||||
sb.append(CATEGORY_SEPARATOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mediaID != null) {
|
||||
sb.append(LEAF_SEPARATOR).append(mediaID);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String extractCategory(@NonNull String mediaID) {
|
||||
int pos = mediaID.indexOf(LEAF_SEPARATOR);
|
||||
if (pos >= 0) {
|
||||
return mediaID.substring(0, pos);
|
||||
}
|
||||
return mediaID;
|
||||
}
|
||||
|
||||
public static String extractMusicID(@NonNull String mediaID) {
|
||||
int pos = mediaID.indexOf(LEAF_SEPARATOR);
|
||||
if (pos >= 0) {
|
||||
return mediaID.substring(pos + LEAF_SEPARATOR.length());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isBrowseable(@NonNull String mediaID) {
|
||||
return !mediaID.contains(LEAF_SEPARATOR);
|
||||
}
|
||||
|
||||
private static boolean isValidCategory(String category) {
|
||||
return category == null ||
|
||||
(!category.contains(CATEGORY_SEPARATOR) && !category.contains(LEAF_SEPARATOR));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*/
|
||||
package code.name.monkey.retromusic.auto
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.support.v4.media.MediaBrowserCompat
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.model.CategoryInfo
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.repository.*
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
|
||||
/**
|
||||
* Created by Beesham Sarendranauth (Beesham)
|
||||
*/
|
||||
class AutoMusicProvider(
|
||||
private val mContext: Context,
|
||||
private val songsRepository: SongRepository,
|
||||
private val albumsRepository: AlbumRepository,
|
||||
private val artistsRepository: ArtistRepository,
|
||||
private val genresRepository: GenreRepository,
|
||||
private val playlistsRepository: PlaylistRepository,
|
||||
private val topPlayedRepository: TopPlayedRepository
|
||||
) {
|
||||
private var mMusicService: WeakReference<MusicService>? = null
|
||||
|
||||
fun setMusicService(service: MusicService) {
|
||||
mMusicService = WeakReference(service)
|
||||
}
|
||||
|
||||
fun getChildren(mediaId: String?, resources: Resources): List<MediaBrowserCompat.MediaItem> {
|
||||
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
|
||||
when (mediaId) {
|
||||
AutoMediaIDHelper.MEDIA_ID_ROOT -> {
|
||||
mediaItems.addAll(getRootChildren(resources))
|
||||
}
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST -> for (playlist in playlistsRepository.playlists()) {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST, playlist.id)
|
||||
.icon(R.drawable.ic_playlist_play)
|
||||
.title(playlist.name)
|
||||
.subTitle(playlist.getInfoString(mContext))
|
||||
.asPlayable()
|
||||
.build()
|
||||
)
|
||||
}
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM -> for (album in albumsRepository.albums()) {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.path(mediaId, album.id)
|
||||
.title(album.title)
|
||||
.subTitle(album.albumArtist ?: album.artistName)
|
||||
.icon(MusicUtil.getMediaStoreAlbumCoverUri(album.id))
|
||||
.asPlayable()
|
||||
.build()
|
||||
)
|
||||
}
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST -> for (artist in artistsRepository.artists()) {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asPlayable()
|
||||
.path(mediaId, artist.id)
|
||||
.title(artist.name)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM_ARTIST -> for (artist in artistsRepository.albumArtists()) {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asPlayable()
|
||||
// we just pass album id here as we don't have album artist id's
|
||||
.path(mediaId, artist.safeGetFirstAlbum().id)
|
||||
.title(artist.name)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE -> for (genre in genresRepository.genres()) {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asPlayable()
|
||||
.path(mediaId, genre.id)
|
||||
.title(genre.name)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_QUEUE ->
|
||||
mMusicService?.get()?.playingQueue
|
||||
?.let {
|
||||
for (song in it) {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asPlayable()
|
||||
.path(mediaId, song.id)
|
||||
.title(song.title)
|
||||
.subTitle(song.artistName)
|
||||
.icon(MusicUtil.getMediaStoreAlbumCoverUri(song.albumId))
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
getPlaylistChildren(mediaId, mediaItems)
|
||||
}
|
||||
}
|
||||
return mediaItems
|
||||
}
|
||||
|
||||
private fun getPlaylistChildren(
|
||||
mediaId: String?,
|
||||
mediaItems: MutableList<MediaBrowserCompat.MediaItem>
|
||||
) {
|
||||
val songs = when (mediaId) {
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS -> {
|
||||
topPlayedRepository.topTracks()
|
||||
}
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY -> {
|
||||
topPlayedRepository.recentlyPlayedTracks()
|
||||
}
|
||||
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SUGGESTIONS -> {
|
||||
topPlayedRepository.notRecentlyPlayedTracks().take(8)
|
||||
}
|
||||
else -> {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
songs.forEach { song ->
|
||||
mediaItems.add(
|
||||
getPlayableSong(mediaId, song)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRootChildren(resources: Resources): List<MediaBrowserCompat.MediaItem> {
|
||||
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
|
||||
val libraryCategories = PreferenceUtil.libraryCategory
|
||||
libraryCategories.forEach {
|
||||
if (it.visible) {
|
||||
when (it.category) {
|
||||
CategoryInfo.Category.Albums -> {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asBrowsable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM)
|
||||
.gridLayout(true)
|
||||
.icon(R.drawable.ic_album)
|
||||
.title(resources.getString(R.string.albums)).build()
|
||||
)
|
||||
}
|
||||
CategoryInfo.Category.Artists -> {
|
||||
if (PreferenceUtil.albumArtistsOnly) {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asBrowsable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM_ARTIST)
|
||||
.icon(R.drawable.ic_album_artist)
|
||||
.title(resources.getString(R.string.album_artist)).build()
|
||||
)
|
||||
} else {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asBrowsable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST)
|
||||
.icon(R.drawable.ic_artist)
|
||||
.title(resources.getString(R.string.artists)).build()
|
||||
)
|
||||
}
|
||||
}
|
||||
CategoryInfo.Category.Genres -> {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asBrowsable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE)
|
||||
.icon(R.drawable.ic_guitar)
|
||||
.title(resources.getString(R.string.genres)).build()
|
||||
)
|
||||
}
|
||||
CategoryInfo.Category.Playlists -> {
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asBrowsable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST)
|
||||
.icon(R.drawable.ic_playlist_play)
|
||||
.title(resources.getString(R.string.playlists)).build()
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asPlayable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SHUFFLE)
|
||||
.icon(R.drawable.ic_shuffle)
|
||||
.title(resources.getString(R.string.action_shuffle_all))
|
||||
.subTitle(MusicUtil.getPlaylistInfoString(mContext, songsRepository.songs()))
|
||||
.build()
|
||||
)
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asBrowsable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_QUEUE)
|
||||
.icon(R.drawable.ic_queue_music)
|
||||
.title(resources.getString(R.string.queue))
|
||||
.subTitle(MusicUtil.getPlaylistInfoString(mContext, MusicPlayerRemote.playingQueue))
|
||||
.asBrowsable().build()
|
||||
)
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asBrowsable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS)
|
||||
.icon(R.drawable.ic_trending_up)
|
||||
.title(resources.getString(R.string.my_top_tracks))
|
||||
.subTitle(
|
||||
MusicUtil.getPlaylistInfoString(
|
||||
mContext,
|
||||
topPlayedRepository.topTracks()
|
||||
)
|
||||
)
|
||||
.asBrowsable().build()
|
||||
)
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asBrowsable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SUGGESTIONS)
|
||||
.icon(R.drawable.ic_face)
|
||||
.title(resources.getString(R.string.suggestion_songs))
|
||||
.subTitle(
|
||||
MusicUtil.getPlaylistInfoString(
|
||||
mContext,
|
||||
topPlayedRepository.notRecentlyPlayedTracks().takeIf {
|
||||
it.size > 9
|
||||
} ?: emptyList()
|
||||
)
|
||||
)
|
||||
.asBrowsable().build()
|
||||
)
|
||||
mediaItems.add(
|
||||
AutoMediaItem.with(mContext)
|
||||
.asBrowsable()
|
||||
.path(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY)
|
||||
.icon(R.drawable.ic_history)
|
||||
.title(resources.getString(R.string.history))
|
||||
.subTitle(
|
||||
MusicUtil.getPlaylistInfoString(
|
||||
mContext,
|
||||
topPlayedRepository.recentlyPlayedTracks()
|
||||
)
|
||||
)
|
||||
.asBrowsable().build()
|
||||
)
|
||||
return mediaItems
|
||||
}
|
||||
|
||||
private fun getPlayableSong(mediaId: String?, song: Song): MediaBrowserCompat.MediaItem {
|
||||
return AutoMediaItem.with(mContext)
|
||||
.asPlayable()
|
||||
.path(mediaId, song.id)
|
||||
.title(song.title)
|
||||
.subTitle(song.artistName)
|
||||
.icon(MusicUtil.getMediaStoreAlbumCoverUri(song.albumId))
|
||||
.build()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package code.name.monkey.retromusic.auto
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.support.v4.media.MediaBrowserCompat
|
||||
import android.support.v4.media.MediaDescriptionCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.core.os.bundleOf
|
||||
|
||||
|
||||
internal object AutoMediaItem {
|
||||
fun with(context: Context): Builder {
|
||||
return Builder(context)
|
||||
}
|
||||
|
||||
internal class Builder(private val mContext: Context) {
|
||||
private var mBuilder: MediaDescriptionCompat.Builder?
|
||||
private var mFlags = 0
|
||||
fun path(fullPath: String): Builder {
|
||||
mBuilder?.setMediaId(fullPath)
|
||||
return this
|
||||
}
|
||||
|
||||
fun path(path: String?, id: Long): Builder {
|
||||
return path(AutoMediaIDHelper.createMediaID(id.toString(), path))
|
||||
}
|
||||
|
||||
fun title(title: String): Builder {
|
||||
mBuilder?.setTitle(title)
|
||||
return this
|
||||
}
|
||||
|
||||
fun subTitle(subTitle: String): Builder {
|
||||
mBuilder?.setSubtitle(subTitle)
|
||||
return this
|
||||
}
|
||||
|
||||
fun icon(uri: Uri?): Builder {
|
||||
mBuilder?.setIconUri(uri)
|
||||
return this
|
||||
}
|
||||
|
||||
fun icon(iconDrawableId: Int): Builder {
|
||||
mBuilder?.setIconBitmap(
|
||||
ResourcesCompat.getDrawable(
|
||||
mContext.resources,
|
||||
iconDrawableId,
|
||||
mContext.theme
|
||||
)?.toBitmap()
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
fun gridLayout(isGrid: Boolean): Builder {
|
||||
|
||||
val hints = bundleOf(
|
||||
CONTENT_STYLE_SUPPORTED to true,
|
||||
CONTENT_STYLE_BROWSABLE_HINT to
|
||||
if (isGrid) CONTENT_STYLE_GRID_ITEM_HINT_VALUE
|
||||
else CONTENT_STYLE_LIST_ITEM_HINT_VALUE,
|
||||
CONTENT_STYLE_PLAYABLE_HINT to
|
||||
if (isGrid) CONTENT_STYLE_GRID_ITEM_HINT_VALUE
|
||||
else CONTENT_STYLE_LIST_ITEM_HINT_VALUE
|
||||
)
|
||||
mBuilder?.setExtras(hints)
|
||||
return this
|
||||
}
|
||||
|
||||
fun asBrowsable(): Builder {
|
||||
mFlags = mFlags or MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||
return this
|
||||
}
|
||||
|
||||
fun asPlayable(): Builder {
|
||||
mFlags = mFlags or MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): MediaBrowserCompat.MediaItem {
|
||||
val result = MediaBrowserCompat.MediaItem(mBuilder!!.build(), mFlags)
|
||||
mBuilder = null
|
||||
mFlags = 0
|
||||
return result
|
||||
}
|
||||
|
||||
init {
|
||||
mBuilder = MediaDescriptionCompat.Builder()
|
||||
}
|
||||
companion object{
|
||||
// Hints - see https://developer.android.com/training/cars/media#default-content-style
|
||||
const val CONTENT_STYLE_SUPPORTED = "android.media.browse.CONTENT_STYLE_SUPPORTED"
|
||||
const val CONTENT_STYLE_BROWSABLE_HINT = "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT"
|
||||
const val CONTENT_STYLE_PLAYABLE_HINT = "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT"
|
||||
const val CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1
|
||||
const val CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Hemanth Savarla.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
package code.name.monkey.retromusic.db
|
||||
|
||||
import androidx.room.*
|
||||
|
||||
@Dao
|
||||
interface BlackListStoreDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertBlacklistPath(blackListStoreEntity: BlackListStoreEntity)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertBlacklistPath(blackListStoreEntities: List<BlackListStoreEntity>)
|
||||
|
||||
@Delete
|
||||
suspend fun deleteBlacklistPath(blackListStoreEntity: BlackListStoreEntity)
|
||||
|
||||
@Query("DELETE FROM BlackListStoreEntity")
|
||||
suspend fun clearBlacklist()
|
||||
|
||||
@Query("SELECT * FROM BlackListStoreEntity")
|
||||
fun blackListPaths(): List<BlackListStoreEntity>
|
||||
}
|