Compare commits
667 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a8f70f201e | ||
![]() |
b8ecd758cf | ||
![]() |
70970f62f1 | ||
![]() |
4ed40d95b2 | ||
![]() |
b24dcd2951 | ||
![]() |
8ff90bafd7 | ||
![]() |
9190af92ef | ||
![]() |
151a440e7e | ||
![]() |
9471e008e3 | ||
![]() |
ecc0bdcc42 | ||
![]() |
49c04af302 | ||
![]() |
afe0bea306 | ||
![]() |
eadf2d928f | ||
![]() |
a299a97ca4 | ||
![]() |
cbbd0a2b40 | ||
![]() |
216fdadf25 | ||
![]() |
3599a4340f | ||
![]() |
719229b2b7 | ||
![]() |
49db82083c | ||
![]() |
84f119e482 | ||
![]() |
aeb8caf059 | ||
![]() |
7c4956153a | ||
![]() |
58dc80aa09 | ||
![]() |
4a07945214 | ||
![]() |
7b21eca37b | ||
![]() |
43ddf90c48 | ||
![]() |
878a441a9f | ||
![]() |
90493f0ea3 | ||
![]() |
db22b4fcce | ||
![]() |
0d6d482630 | ||
![]() |
a4162dc2ad | ||
![]() |
469a76dbd6 | ||
![]() |
6940e92142 | ||
![]() |
4d57b134a3 | ||
![]() |
a370166576 | ||
![]() |
69d50eec35 | ||
![]() |
2e48ac380b | ||
![]() |
d22b5349a3 | ||
![]() |
83172f198c | ||
![]() |
f870e12011 | ||
![]() |
345c6ac714 | ||
![]() |
005355e267 | ||
![]() |
aee2c9282d | ||
![]() |
ea329f40e8 | ||
![]() |
b248f90403 | ||
![]() |
e2c0ddef24 | ||
![]() |
2a0853026a | ||
![]() |
dddaa5a964 | ||
![]() |
412f3dd81c | ||
![]() |
7f30ae8d31 | ||
![]() |
c0d50d441e | ||
![]() |
66cae6c7ac | ||
![]() |
308c417ed3 | ||
![]() |
0823629fb5 | ||
![]() |
7037145519 | ||
![]() |
08d1c98674 | ||
![]() |
c0dcc87eb2 | ||
![]() |
b4f8d2dfbf | ||
![]() |
48490cf168 | ||
![]() |
ea7138e95b | ||
![]() |
5add53e269 | ||
![]() |
c615d48e27 | ||
![]() |
92c05a51e1 | ||
![]() |
71bcf355ec | ||
![]() |
089a3445a1 | ||
![]() |
dbba55606b | ||
![]() |
a2894db652 | ||
![]() |
edcd784335 | ||
![]() |
0a153fe770 | ||
![]() |
3f18f35c0b | ||
![]() |
30fd4acf37 | ||
![]() |
0e34057794 | ||
![]() |
032309a2c6 | ||
![]() |
858cbd17ad | ||
![]() |
fbd7b771ef | ||
![]() |
8bdbe7dfba | ||
![]() |
b3f6824a94 | ||
![]() |
7bb69c02de | ||
![]() |
a734ded6f8 | ||
![]() |
f228937e3e | ||
![]() |
ee7d0cfeb5 | ||
![]() |
7a630507fb | ||
![]() |
dffd494d4a | ||
![]() |
49e2d1b759 | ||
![]() |
fd3ddfece9 | ||
![]() |
2bb91feb34 | ||
![]() |
eb3fcfdd6f | ||
![]() |
066fe2a326 | ||
![]() |
93d6e0642b | ||
![]() |
1e61bca1e9 | ||
![]() |
3bb7f0d78f | ||
![]() |
75bdbeccc0 | ||
![]() |
846fa679f6 | ||
![]() |
3a5ddd6214 | ||
![]() |
3b21b05fdc | ||
![]() |
53ef9e15e1 | ||
![]() |
7309e900bc | ||
![]() |
c9556c2ecb | ||
![]() |
efdf091894 | ||
![]() |
92dde7286f | ||
![]() |
0a604a9ad5 | ||
![]() |
cfda46e07e | ||
![]() |
60f032039f | ||
![]() |
d2e0dc1ac9 | ||
![]() |
3e7b36f4e6 | ||
![]() |
cdc64e4bb0 | ||
![]() |
2f304fa943 | ||
![]() |
e83a4bb388 | ||
![]() |
90f9cc28eb | ||
![]() |
f23a078b64 | ||
![]() |
d2827a52d3 | ||
![]() |
7de6423935 | ||
![]() |
5573deb52a | ||
![]() |
412c427cec | ||
![]() |
5be4747c66 | ||
![]() |
5b457fc4bd | ||
![]() |
cb97f4352c | ||
![]() |
ee71dbf552 | ||
![]() |
1538dc6d52 | ||
![]() |
53ad5bd448 | ||
![]() |
aa65bf291d | ||
![]() |
41cc79b5e6 | ||
![]() |
86e55f7a3d | ||
![]() |
1eb0b5f847 | ||
![]() |
d898d45b4a | ||
![]() |
5f47dc3d6d | ||
![]() |
4e6db8b9e1 | ||
![]() |
b979a6ede9 | ||
![]() |
fcc05a4243 | ||
![]() |
7569c37739 | ||
![]() |
b8cae569b4 | ||
![]() |
4b09f6c96b | ||
![]() |
758b0bc9d9 | ||
![]() |
326d8212f6 | ||
![]() |
a9ba2582fb | ||
![]() |
f8211cbcc7 | ||
![]() |
3306f21860 | ||
![]() |
7a6821f28d | ||
![]() |
69c0333327 | ||
![]() |
2d6fe886e2 | ||
![]() |
67713c60d4 | ||
![]() |
bb0afa5a0c | ||
![]() |
f4306ad1c3 | ||
![]() |
b9c6c98e38 | ||
![]() |
0326630a19 | ||
![]() |
abe04b28b2 | ||
![]() |
8ac30c0031 | ||
![]() |
9a861b9954 | ||
![]() |
7491175bb6 | ||
![]() |
a65846d15e | ||
![]() |
4f72f3b424 | ||
![]() |
ecb43f64ad | ||
![]() |
e575d23c4a | ||
![]() |
76f0f18592 | ||
![]() |
ae0f24d34a | ||
![]() |
a06614bc5c | ||
![]() |
7c26d29d06 | ||
![]() |
b6c0926efc | ||
![]() |
89cf73d95f | ||
![]() |
e6a9011c5c | ||
![]() |
7143913bba | ||
![]() |
5567214588 | ||
![]() |
5ff1fc9f82 | ||
![]() |
d3edf07ac9 | ||
![]() |
8fc319d980 | ||
![]() |
e986baa0aa | ||
![]() |
8a7f5c4008 | ||
![]() |
9d2ad1c626 | ||
![]() |
b74c2eab8f | ||
![]() |
2c4cc94985 | ||
![]() |
ba27dc70e4 | ||
![]() |
0ec9f3535b | ||
![]() |
df72ba6960 | ||
![]() |
d9057dae57 | ||
![]() |
e61b79585e | ||
![]() |
a9e5636e96 | ||
![]() |
723b6b1f38 | ||
![]() |
764950b286 | ||
![]() |
290affd435 | ||
![]() |
c59b5c651e | ||
![]() |
e4ac7cacc9 | ||
![]() |
26b1c31f8f | ||
![]() |
4bfe334419 | ||
![]() |
48fcfbc928 | ||
![]() |
95e09ffc94 | ||
![]() |
968fd09eb3 | ||
![]() |
1a32264bc7 | ||
![]() |
59f298a935 | ||
![]() |
bbe3394e9e | ||
![]() |
7cde803bee | ||
![]() |
cd475e93d0 | ||
![]() |
3d334d96fd | ||
![]() |
bd4cd22e4e | ||
![]() |
c709de6bf1 | ||
![]() |
ccbac85171 | ||
![]() |
50123b235c | ||
![]() |
4072531c62 | ||
![]() |
464666c01a | ||
![]() |
4db9a95a91 | ||
![]() |
0e58cd0e99 | ||
![]() |
3cce2868de | ||
![]() |
894b0d7e5e | ||
![]() |
7befbca8e5 | ||
![]() |
d4f70f56e4 | ||
![]() |
180d07a1be | ||
![]() |
91871d0d26 | ||
![]() |
c3bbc129ad | ||
![]() |
a9586a64f2 | ||
![]() |
677f95f266 | ||
![]() |
f4b1e550bf | ||
![]() |
bb71fc0eea | ||
![]() |
7eb0e69dd7 | ||
![]() |
30bf0bed62 | ||
![]() |
c82b68a513 | ||
![]() |
edc9636940 | ||
![]() |
47edb8bf96 | ||
![]() |
e8e2d78529 | ||
![]() |
0248dd3c23 | ||
![]() |
1e6d709e04 | ||
![]() |
698fb6ba27 | ||
![]() |
043eaba81f | ||
![]() |
b1780e0cf8 | ||
![]() |
cd39bbf87c | ||
![]() |
d4504722d8 | ||
![]() |
6ddf6b9cce | ||
![]() |
4b2d259add | ||
![]() |
0037677383 | ||
![]() |
2b0b5eae1d | ||
![]() |
6cf4ca9179 | ||
![]() |
2ea8070771 | ||
![]() |
e0760e6250 | ||
![]() |
ada0ca1472 | ||
![]() |
42158a99ff | ||
![]() |
5c6cb770a8 | ||
![]() |
4c3718467d | ||
![]() |
4e8a50ec58 | ||
![]() |
75e189344e | ||
![]() |
8c0a47374d | ||
![]() |
697ba1ed7b | ||
![]() |
e05645c46e | ||
![]() |
ee95b3b26a | ||
![]() |
b2b38a9191 | ||
![]() |
219ae9dcb3 | ||
![]() |
ed1f4ad6f5 | ||
![]() |
15773e1ce7 | ||
![]() |
b7bb35d299 | ||
![]() |
1937b1b840 | ||
![]() |
85ebaf69de | ||
![]() |
becba8956f | ||
![]() |
343198a7e8 | ||
![]() |
0aee8b3399 | ||
![]() |
768bab8bbb | ||
![]() |
082f711018 | ||
![]() |
6e357230ac | ||
![]() |
2411f46877 | ||
![]() |
4fead5f504 | ||
![]() |
086107b2cd | ||
![]() |
74a328e529 | ||
![]() |
e470f50e81 | ||
![]() |
6c005592e3 | ||
![]() |
696875e4b5 | ||
![]() |
5930c342b5 | ||
![]() |
3649b67869 | ||
![]() |
b8ffb9b75f | ||
![]() |
a8694a7a8b | ||
![]() |
b25ae332b8 | ||
![]() |
5a14f587a0 | ||
![]() |
2daea2b3ef | ||
![]() |
88906098dd | ||
![]() |
e54a646073 | ||
![]() |
3ffcb48db9 | ||
![]() |
46852545a9 | ||
![]() |
bf94a490bb | ||
![]() |
6dd9b753b0 | ||
![]() |
e6408ccc0d | ||
![]() |
dff8fe2e64 | ||
![]() |
b734985199 | ||
![]() |
dd0bb01af5 | ||
![]() |
b8f2495acb | ||
![]() |
4afe0cca68 | ||
![]() |
bbad701c07 | ||
![]() |
af295be8c6 | ||
![]() |
780f5dee2e | ||
![]() |
30e03786bf | ||
![]() |
f96b5eae97 | ||
![]() |
684e595d16 | ||
![]() |
047eccfa82 | ||
![]() |
b558cc17f1 | ||
![]() |
d845180e60 | ||
![]() |
2ee0ca44c7 | ||
![]() |
d53782da23 | ||
![]() |
ced85d3f0c | ||
![]() |
e2143a0625 | ||
![]() |
db3aaa4b83 | ||
![]() |
ccf84c568e | ||
![]() |
fdb5ed8f56 | ||
![]() |
b40c0b7772 | ||
![]() |
b09263d128 | ||
![]() |
a6720d5392 | ||
![]() |
05d544fe5a | ||
![]() |
2fefd65f51 | ||
![]() |
7ee071f2e3 | ||
![]() |
418a0d29bd | ||
![]() |
2925dd6748 | ||
![]() |
f80ea32de4 | ||
![]() |
fcefce4b1b | ||
![]() |
1089e90511 | ||
![]() |
2488da2279 | ||
![]() |
04190f2dda | ||
![]() |
fe2f0a373f | ||
![]() |
e73d2d06d8 | ||
![]() |
5ad151932a | ||
![]() |
b52bf0f448 | ||
![]() |
55871e3cdd | ||
![]() |
0aa44520ac | ||
![]() |
4595eb169f | ||
![]() |
08f624ece1 | ||
![]() |
f524a40062 | ||
![]() |
f28fe5a682 | ||
![]() |
efa2b77ac3 | ||
![]() |
12f3ec1776 | ||
![]() |
8c1337d1fc | ||
![]() |
b32ec667a9 | ||
![]() |
18a68d85a5 | ||
![]() |
2d972c8f57 | ||
![]() |
083b366308 | ||
![]() |
8ca2115ef0 | ||
![]() |
499ecfba26 | ||
![]() |
3fd7a18159 | ||
![]() |
e3285bd4da | ||
![]() |
9f2d423cfe | ||
![]() |
3c45732b0a | ||
![]() |
cab61631ef | ||
![]() |
6e1cd96903 | ||
![]() |
9cd839cfd9 | ||
![]() |
94e704087f | ||
![]() |
f553e43b17 | ||
![]() |
8e287ab1e5 | ||
![]() |
fdde972a77 | ||
![]() |
8c5ece5335 | ||
![]() |
a76b5c4c7f | ||
![]() |
70bbb4af5a | ||
![]() |
2a317397ee | ||
![]() |
4e0de13075 | ||
![]() |
58f6142d2f | ||
![]() |
9c80c610b4 | ||
![]() |
b87a51011b | ||
![]() |
34d8bc26fe | ||
![]() |
c1f2ddcdbe | ||
![]() |
200ee4a6bd | ||
![]() |
ce9627218d | ||
![]() |
142d6884c3 | ||
![]() |
ee8406772c | ||
![]() |
87a78549b6 | ||
![]() |
5ac534619f | ||
![]() |
68ca6a7d31 | ||
![]() |
7f23c9f8f6 | ||
![]() |
c9d6d2cd48 | ||
![]() |
c680aeb1f4 | ||
![]() |
4618a6406c | ||
![]() |
02818593ed | ||
![]() |
1cad097d0b | ||
![]() |
57c8f85731 | ||
![]() |
1119c0e47d | ||
![]() |
5ff36a8643 | ||
![]() |
9cb828bb55 | ||
![]() |
36d161c05a | ||
![]() |
959199ff84 | ||
![]() |
33ecbe066c | ||
![]() |
29015bca76 | ||
![]() |
3afe3cea80 | ||
![]() |
40bfcc1961 | ||
![]() |
4f882882e0 | ||
![]() |
efada35ce0 | ||
![]() |
2d4c9cabd2 | ||
![]() |
6d0cbf97e3 | ||
![]() |
4385f2f472 | ||
![]() |
d50e60e2b2 | ||
![]() |
aa5d0e535b | ||
![]() |
af60cfc067 | ||
![]() |
411115327d | ||
![]() |
9a0421ce38 | ||
![]() |
6d9361f3fe | ||
![]() |
95ff13324e | ||
![]() |
470addca83 | ||
![]() |
9294858fb6 | ||
![]() |
9b02412060 | ||
![]() |
a5e02d068e | ||
![]() |
b501078c43 | ||
![]() |
d0a225d0b1 | ||
![]() |
2be84ec4ee | ||
![]() |
c681401b6e | ||
![]() |
1b024b41fe | ||
![]() |
b0a07b58d5 | ||
![]() |
b32ec9ccf9 | ||
![]() |
9b7a7ef1cf | ||
![]() |
7408a87860 | ||
![]() |
95b68687d5 | ||
![]() |
388e2d0289 | ||
![]() |
84f47df6c1 | ||
![]() |
64d25509b4 | ||
![]() |
6456b43d10 | ||
![]() |
123eb168a3 | ||
![]() |
56ce84d748 | ||
![]() |
39ea7a701c | ||
![]() |
1cc7882177 | ||
![]() |
ebaf5615ad | ||
![]() |
ce19ef1efd | ||
![]() |
1c66646798 | ||
![]() |
b5f3894983 | ||
![]() |
0465042264 | ||
![]() |
4b65319879 | ||
![]() |
0d537abab3 | ||
![]() |
d515f3d3be | ||
![]() |
0eb78d14ca | ||
![]() |
b211813213 | ||
![]() |
bd511584e7 | ||
![]() |
0d9fb1d388 | ||
![]() |
9b05b8adf1 | ||
![]() |
1441736627 | ||
![]() |
a2ba46ea45 | ||
![]() |
e6fee03c20 | ||
![]() |
6a500731d6 | ||
![]() |
a6e13ffc08 | ||
![]() |
d22bba5393 | ||
![]() |
abfdbc63ce | ||
![]() |
00f1b3422f | ||
![]() |
7a31119e3c | ||
![]() |
44861a9f5c | ||
![]() |
c7d8ed567a | ||
![]() |
67ae2e8159 | ||
![]() |
d359d130de | ||
![]() |
86ee64c606 | ||
![]() |
15211123aa | ||
![]() |
243a843033 | ||
![]() |
86f5b80177 | ||
![]() |
ccea4a003d | ||
![]() |
2ce4853fd1 | ||
![]() |
2f46fa32f1 | ||
![]() |
359b918e6b | ||
![]() |
7dd76d24c3 | ||
![]() |
f2f35bd2fb | ||
![]() |
79b842dad3 | ||
![]() |
1284b409e7 | ||
![]() |
15bd58a955 | ||
![]() |
cb6b6f142e | ||
![]() |
7927a3e404 | ||
![]() |
3bdc46da4d | ||
![]() |
4c5564fd2f | ||
![]() |
261e1b6685 | ||
![]() |
ffb3a3377f | ||
![]() |
a3021e4c52 | ||
![]() |
1cfd377c29 | ||
![]() |
b495ed4ac0 | ||
![]() |
08ac29c979 | ||
![]() |
44418868ad | ||
![]() |
6c5cab9899 | ||
![]() |
1b420e661b | ||
![]() |
a621a45f0b | ||
![]() |
75173e5096 | ||
![]() |
a83dd64476 | ||
![]() |
f79fedefd4 | ||
![]() |
59041a2948 | ||
![]() |
5936f08a92 | ||
![]() |
b9b7d5c8aa | ||
![]() |
d18f74fd65 | ||
![]() |
a799ca55bc | ||
![]() |
bf6cec8d69 | ||
![]() |
52d4f60ccc | ||
![]() |
3fb003ea60 | ||
![]() |
a35eece00c | ||
![]() |
2540d16ced | ||
![]() |
9034ee29db | ||
![]() |
064d92d35d | ||
![]() |
cb8d24ff31 | ||
![]() |
4f2175987d | ||
![]() |
6067314c5a | ||
![]() |
9ce911a8ab | ||
![]() |
2822d5dbfd | ||
![]() |
de61d90938 | ||
![]() |
f9087b63d5 | ||
![]() |
d115e57058 | ||
![]() |
7816cb8068 | ||
![]() |
8cb6c6d126 | ||
![]() |
4af23241c8 | ||
![]() |
b224af21ea | ||
![]() |
1cfeef54e7 | ||
![]() |
f81219e83e | ||
![]() |
63bf694d5c | ||
![]() |
9c3f9733b5 | ||
![]() |
eea8fd1579 | ||
![]() |
73bd207ce2 | ||
![]() |
491c1b9857 | ||
![]() |
e223a10bde | ||
![]() |
a9c78b7863 | ||
![]() |
bf769f473b | ||
![]() |
6c6488ea6d | ||
![]() |
47f98b98aa | ||
![]() |
5f280a19f4 | ||
![]() |
6591ec0e1b | ||
![]() |
d7d864ff2b | ||
![]() |
3394c1b057 | ||
![]() |
6cb29868d2 | ||
![]() |
37d002d133 | ||
![]() |
73c5b30b63 | ||
![]() |
982cf0bd43 | ||
![]() |
57cd8607dd | ||
![]() |
26ee84d990 | ||
![]() |
8fc44ed655 | ||
![]() |
ed48d25add | ||
![]() |
2b5fd35529 | ||
![]() |
3aca7372af | ||
![]() |
ab713a4eac | ||
![]() |
9cd44b6c9b | ||
![]() |
f6ba95fb64 | ||
![]() |
c3c9fc544c | ||
![]() |
bc534aa240 | ||
![]() |
e63a4bb63c | ||
![]() |
c607a330ed | ||
![]() |
56241f773a | ||
![]() |
71341ec0bd | ||
![]() |
02acbd9327 | ||
![]() |
c4023aa09d | ||
![]() |
e034455173 | ||
![]() |
e1786989ff | ||
![]() |
b2d9e64758 | ||
![]() |
d2683c52d8 | ||
![]() |
82307bc030 | ||
![]() |
fc5bfa089c | ||
![]() |
d82261cb25 | ||
![]() |
22caa818f4 | ||
![]() |
9aea35468f | ||
![]() |
8fad2251b3 | ||
![]() |
bf45681deb | ||
![]() |
0280654bb6 | ||
![]() |
edc997e747 | ||
![]() |
1e7f0e1fe7 | ||
![]() |
dbc1c452dd | ||
![]() |
2b01e4fb4d | ||
![]() |
a838eadc12 | ||
![]() |
a77b6776e8 | ||
![]() |
4ca893950b | ||
![]() |
2286277a06 | ||
![]() |
c1a105a1ff | ||
![]() |
7ad67fa3fa | ||
![]() |
937a706ac9 | ||
![]() |
88fea7ecf9 | ||
![]() |
ef7833eb67 | ||
![]() |
2da5d786d2 | ||
![]() |
9bccbc93c6 | ||
![]() |
7e07c2e198 | ||
![]() |
7ac791757a | ||
![]() |
6907f9c756 | ||
![]() |
930539ca48 | ||
![]() |
cb95663412 | ||
![]() |
883783b769 | ||
![]() |
7ae9f56482 | ||
![]() |
5a6b800912 | ||
![]() |
6f4c308457 | ||
![]() |
6e07fec1a5 | ||
![]() |
9e25c742d4 | ||
![]() |
9791e3fb5f | ||
![]() |
2d1f4b9380 | ||
![]() |
39e97eef34 | ||
![]() |
f82253c6ba | ||
![]() |
57ccf16311 | ||
![]() |
2781127da1 | ||
![]() |
6e41b106fa | ||
![]() |
22a49e56a2 | ||
![]() |
b948872258 | ||
![]() |
17837f4149 | ||
![]() |
4b757d8e8d | ||
![]() |
27604b28f2 | ||
![]() |
beafe23e30 | ||
![]() |
ca546bef17 | ||
![]() |
2abb5800be | ||
![]() |
392047247b | ||
![]() |
9391e7a379 | ||
![]() |
b70f250e8d | ||
![]() |
de68fe3a6b | ||
![]() |
0d080b77b7 | ||
![]() |
f26503990c | ||
![]() |
5afe823abd | ||
![]() |
c8dd8025ec | ||
![]() |
68374efd3e | ||
![]() |
044d3b4820 | ||
![]() |
ee97aedcfc | ||
![]() |
82b1cfa0d7 | ||
![]() |
e99f32b610 | ||
![]() |
9f96b5c537 | ||
![]() |
1a6cea926f | ||
![]() |
1cea95bbda | ||
![]() |
7545ff6415 | ||
![]() |
6673e5a8a8 | ||
![]() |
f4dce2f611 | ||
![]() |
35e9920b51 | ||
![]() |
f37ac06e1a | ||
![]() |
ca2b81d572 | ||
![]() |
6539009629 | ||
![]() |
97ecaca9d9 | ||
![]() |
eec7a9dbc4 | ||
![]() |
e516afb185 | ||
![]() |
a248a4b48c | ||
![]() |
4a044498a4 | ||
![]() |
389a4fc704 | ||
![]() |
a6164c5791 | ||
![]() |
5019c14c44 | ||
![]() |
b354f57d4e | ||
![]() |
ca76a39910 | ||
![]() |
57ac3177c0 | ||
![]() |
60f2911eb9 | ||
![]() |
0742e898e3 | ||
![]() |
06a7ecf393 | ||
![]() |
88c6822555 | ||
![]() |
bda76a59ec | ||
![]() |
d1f0e778f6 | ||
![]() |
d0f6f9d9f6 | ||
![]() |
fdea9307bb | ||
![]() |
af1ce932a1 | ||
![]() |
649d6acfe2 | ||
![]() |
2c0b1a57e5 | ||
![]() |
f09ab004bf | ||
![]() |
09fac66d26 | ||
![]() |
b0970090b1 | ||
![]() |
a9a1d4c9dc | ||
![]() |
e6a20b5a16 | ||
![]() |
0ae6af61c9 | ||
![]() |
761d968ceb | ||
![]() |
019ba865e2 | ||
![]() |
27e7d532f0 | ||
![]() |
befaf59f0c | ||
![]() |
920d4219f0 | ||
![]() |
59a819be9c | ||
![]() |
1fa6d1f819 | ||
![]() |
4d6af8c0a7 | ||
![]() |
17105a640b | ||
![]() |
a850ed270b | ||
![]() |
96a2a1f5a6 | ||
![]() |
ca71406505 | ||
![]() |
9095a8c8f8 | ||
![]() |
6f71e52ea8 | ||
![]() |
87e203b8b4 | ||
![]() |
095587ee84 | ||
![]() |
846ae82084 | ||
![]() |
ab08c82c8d | ||
![]() |
b6d42659b7 | ||
![]() |
ba69496dcc | ||
![]() |
6f8271f5e9 | ||
![]() |
3294f657fe | ||
![]() |
c3289a0ba4 | ||
![]() |
86123456f2 | ||
![]() |
9f9244062a | ||
![]() |
e964f61d38 | ||
![]() |
badd52223a | ||
![]() |
90f7c531cd | ||
![]() |
26db4f3350 | ||
![]() |
cd669e22c1 | ||
![]() |
8ed65bfa17 | ||
![]() |
0737a362d8 | ||
![]() |
8167963212 | ||
![]() |
46830d2122 | ||
![]() |
9b9822cc3c | ||
![]() |
5ad5d16683 | ||
![]() |
9dfd49ca04 | ||
![]() |
4defeefe7e | ||
![]() |
79a371f9b1 | ||
![]() |
806b22ec85 |
@ -1,177 +0,0 @@
|
|||||||
version: 2.1
|
|
||||||
|
|
||||||
orbs:
|
|
||||||
gh: circleci/github-cli@2.2.0
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
flutter_linux_arm:
|
|
||||||
machine:
|
|
||||||
image: ubuntu-2204:current
|
|
||||||
resource_class: arm.medium
|
|
||||||
parameters:
|
|
||||||
version:
|
|
||||||
type: string
|
|
||||||
default: 3.1.1
|
|
||||||
channel:
|
|
||||||
type: enum
|
|
||||||
enum:
|
|
||||||
- release
|
|
||||||
- nightly
|
|
||||||
default: release
|
|
||||||
github_run_number:
|
|
||||||
type: string
|
|
||||||
default: "0"
|
|
||||||
dry_run:
|
|
||||||
type: boolean
|
|
||||||
default: true
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- gh/setup
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Get current date
|
|
||||||
command: |
|
|
||||||
echo "export CURRENT_DATE=$(date +%Y-%m-%d)" >> $BASH_ENV
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Install dependencies
|
|
||||||
command: |
|
|
||||||
sudo apt-get update -y
|
|
||||||
sudo apt-get install -y tar clang cmake ninja-build pkg-config libgtk-3-dev make python3-pip python3-setuptools desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace fuse libunwind-dev locate patchelf gir1.2-appindicator3-0.1 libappindicator3-1 libappindicator3-dev libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev mpv libmpv-dev zip rpm
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Install Flutter
|
|
||||||
command: |
|
|
||||||
git clone https://github.com/flutter/flutter.git
|
|
||||||
cd flutter && git checkout stable && cd ..
|
|
||||||
export PATH="$PATH:`pwd`/flutter/bin"
|
|
||||||
flutter precache
|
|
||||||
flutter doctor -v
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Install AppImageTool
|
|
||||||
command: |
|
|
||||||
wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-aarch64.AppImage"
|
|
||||||
chmod +x appimagetool
|
|
||||||
mv appimagetool flutter/bin
|
|
||||||
|
|
||||||
- persist_to_workspace:
|
|
||||||
root: flutter
|
|
||||||
paths:
|
|
||||||
- .
|
|
||||||
|
|
||||||
- when:
|
|
||||||
condition:
|
|
||||||
equal: [<< parameters.channel >>, nightly]
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Replace pubspec version and BUILD_VERSION Env (nightly)
|
|
||||||
command: |
|
|
||||||
curl -sS https://webi.sh/yq | sh
|
|
||||||
yq -i '.version |= sub("\+\d+", "+<< parameters.channel >>.")' pubspec.yaml
|
|
||||||
yq -i '.version += strenv(GITHUB_RUN_NUMBER)' pubspec.yaml
|
|
||||||
echo 'export BUILD_VERSION="<< parameters.version >>+<< parameters.channel >>.<< parameters.github_run_number >>"' >> $BASH_ENV
|
|
||||||
|
|
||||||
- when:
|
|
||||||
condition:
|
|
||||||
equal: [<< parameters.channel >>, release]
|
|
||||||
steps:
|
|
||||||
- run: echo 'export BUILD_VERSION="<< parameters.version >>"' >> $BASH_ENV
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Generate .env file
|
|
||||||
command: |
|
|
||||||
echo "SPOTIFY_SECRETS=${SPOTIFY_SECRETS}" >> .env
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Replace Version in files
|
|
||||||
command: |
|
|
||||||
sed -i 's|%{{APPDATA_RELEASE}}%|<release version="${BUILD_VERSION}" date="${CURRENT_DATE}" />|' linux/com.github.KRTirtho.Spotube.appdata.xml
|
|
||||||
echo "build_arch: aarch64" >> linux/packaging/rpm/make_config.yaml
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Build secrets
|
|
||||||
command: |
|
|
||||||
export PATH="$PATH:`pwd`/flutter/bin"
|
|
||||||
flutter config --enable-linux-desktop
|
|
||||||
flutter pub get
|
|
||||||
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Build Flutter app
|
|
||||||
command: |
|
|
||||||
export PATH="$PATH:`pwd`/flutter/bin"
|
|
||||||
export PATH="$PATH":"$HOME/.pub-cache/bin"
|
|
||||||
dart pub global activate flutter_distributor
|
|
||||||
alias dpkg-deb="dpkg-deb --Zxz"
|
|
||||||
flutter_distributor package --platform=linux --targets=deb
|
|
||||||
flutter_distributor package --platform=linux --targets=appimage
|
|
||||||
flutter_distributor package --platform=linux --targets=rpm
|
|
||||||
|
|
||||||
- when:
|
|
||||||
condition:
|
|
||||||
equal: [<< parameters.channel >>, nightly]
|
|
||||||
steps:
|
|
||||||
- run: make tar VERSION=nightly ARCH=arm64 PKG_ARCH=aarch64
|
|
||||||
|
|
||||||
- when:
|
|
||||||
condition:
|
|
||||||
equal: [<< parameters.channel >>, release]
|
|
||||||
steps:
|
|
||||||
- run: make tar VERSION=${BUILD_VERSION} ARCH=arm64 PKG_ARCH=aarch64
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Move artifacts
|
|
||||||
command: |
|
|
||||||
mkdir bundle
|
|
||||||
mv build/spotube-linux-*-aarch64.tar.xz bundle/
|
|
||||||
mv dist/**/spotube-*-linux.deb bundle/Spotube-linux-aarch64.deb
|
|
||||||
mv dist/**/spotube-*-linux.rpm bundle/Spotube-linux-aarch64.rpm
|
|
||||||
mv dist/**/spotube-*-linux.AppImage bundle/Spotube-linux-aarch64.AppImage
|
|
||||||
zip -r Spotube-linux-aarch64.zip bundle
|
|
||||||
|
|
||||||
- store_artifacts:
|
|
||||||
path: Spotube-linux-aarch64.zip
|
|
||||||
|
|
||||||
- when:
|
|
||||||
condition:
|
|
||||||
and:
|
|
||||||
- equal: [<< parameters.dry_run >>, false]
|
|
||||||
- equal: [<< parameters.channel >>, release]
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Upload to release (release)
|
|
||||||
command: gh release upload v<< parameters.version >> bundle/* --clobber
|
|
||||||
|
|
||||||
- when:
|
|
||||||
condition:
|
|
||||||
and:
|
|
||||||
- equal: [<< parameters.dry_run >>, false]
|
|
||||||
- equal: [<< parameters.channel >>, nightly]
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Upload to release (nightly)
|
|
||||||
command: gh release upload nightly bundle/* --clobber
|
|
||||||
|
|
||||||
parameters:
|
|
||||||
GHA_Actor:
|
|
||||||
type: string
|
|
||||||
default: ""
|
|
||||||
GHA_Action:
|
|
||||||
type: string
|
|
||||||
default: ""
|
|
||||||
GHA_Event:
|
|
||||||
type: string
|
|
||||||
default: ""
|
|
||||||
GHA_Meta:
|
|
||||||
type: string
|
|
||||||
default: ""
|
|
||||||
|
|
||||||
workflows:
|
|
||||||
build_flutter_for_arm_workflow:
|
|
||||||
when: << pipeline.parameters.GHA_Action >>
|
|
||||||
jobs:
|
|
||||||
- flutter_linux_arm:
|
|
||||||
context:
|
|
||||||
- org-global
|
|
||||||
- GITHUB_CREDS
|
|
6
.dockerignore
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
build
|
||||||
|
dist
|
||||||
|
.dart_tool
|
||||||
|
.idea
|
||||||
|
.github
|
||||||
|
.git
|
15
.env.example
@ -1,11 +1,12 @@
|
|||||||
# The format:
|
|
||||||
# SPOTIFY_SECRETS=clintId1:clientSecret1,clientId2:clientSecret2
|
|
||||||
SPOTIFY_SECRETS=
|
|
||||||
|
|
||||||
# 0 or 1
|
# 0 or 1
|
||||||
# 0 = disable
|
# 0 = disable
|
||||||
# 1 = enable
|
# 1 = enable
|
||||||
ENABLE_UPDATE_CHECK=
|
ENABLE_UPDATE_CHECK=$ENABLE_UPDATE_CHECK
|
||||||
|
|
||||||
LASTFM_API_KEY=
|
LASTFM_API_KEY=$LASTFM_API_KEY
|
||||||
LASTFM_API_SECRET=
|
LASTFM_API_SECRET=$LASTFM_API_SECRET
|
||||||
|
|
||||||
|
# Release channel. Can be: nightly, stable
|
||||||
|
RELEASE_CHANNEL=$RELEASE_CHANNEL
|
||||||
|
|
||||||
|
HIDE_DONATIONS=$HIDE_DONATIONS
|
@ -1,4 +1,3 @@
|
|||||||
{
|
{
|
||||||
"flutterSdkVersion": "3.16.0",
|
"flutterSdkVersion": "3.35.2"
|
||||||
"flavors": {}
|
|
||||||
}
|
}
|
25
.github/Dockerfile
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
ARG FLUTTER_VERSION
|
||||||
|
|
||||||
|
FROM --platform=linux/arm64 krtirtho/flutter_distributor:${FLUTTER_VERSION}
|
||||||
|
|
||||||
|
ARG BUILD_VERSION
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN chown -R $(whoami) /app
|
||||||
|
|
||||||
|
RUN rustup target add aarch64-unknown-linux-gnu
|
||||||
|
|
||||||
|
RUN flutter pub get
|
||||||
|
|
||||||
|
RUN alias dpkg-deb="dpkg-deb --Zxz" &&\
|
||||||
|
flutter_distributor package --platform=linux --targets=deb --skip-clean
|
||||||
|
|
||||||
|
RUN make tar VERSION=${BUILD_VERSION} ARCH=arm64 PKG_ARCH=aarch64
|
||||||
|
|
||||||
|
RUN mv build/spotube-linux-*-aarch64.tar.xz dist/ &&\
|
||||||
|
mv dist/**/spotube-*-linux.deb dist/Spotube-linux-aarch64.deb
|
||||||
|
|
||||||
|
CMD [ "sleep", "5000000" ]
|
38
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -7,8 +7,13 @@ labels:
|
|||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Is there an existing issue for this?
|
label: Is there an existing issue for this? (Please read the description)
|
||||||
description: Make sure to check if this issue is a duplicate.
|
description: |
|
||||||
|
🚨 PLEASE! Make sure to check if this issue is a duplicate. 🚨
|
||||||
|
|
||||||
|
Don't waste our time, we are working hard to make Spotube better for you.
|
||||||
|
|
||||||
|
Try with multiple similar keywords, and check the closed issues too.
|
||||||
options:
|
options:
|
||||||
- label: I have searched the existing issues
|
- label: I have searched the existing issues
|
||||||
required: true
|
required: true
|
||||||
@ -16,29 +21,47 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Current Behavior
|
label: Current Behavior
|
||||||
description: Write what you are experiencing currently.
|
description: Write what you are experiencing currently.
|
||||||
|
placeholder: |
|
||||||
|
The app isn't working as expected. It crashes when I do this...
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Expected Behavior
|
label: Expected Behavior
|
||||||
description: Write what you expected to happen.
|
description: Write what you expected to happen.
|
||||||
|
placeholder: |
|
||||||
|
The app should do this when I do that...
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Steps to reproduce
|
label: Steps to reproduce
|
||||||
description: Steps to reproduce the issue. A not well written description might delay the resolve of it.
|
description: Steps to reproduce the issue. A not well written description might lead to the delay in fixing the issue.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
1. I opened the app
|
1. I opened the app
|
||||||
2. I did this
|
2. I did this
|
||||||
3. And that
|
3. And that
|
||||||
4. Then this happened
|
4. Then this happened
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Logs
|
||||||
|
description: |
|
||||||
|
If you have any logs, paste them here. Make sure to remove any sensitive information.
|
||||||
|
You can find the logs in the app's Settings > Developers > Logs page.
|
||||||
|
value: |
|
||||||
|
<details>
|
||||||
|
<summary>Logs</summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
<Replace this line by pasting your logs here>
|
||||||
|
```
|
||||||
|
</details>
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: Operating System
|
label: Operating System
|
||||||
description: The OS in which you used Spotube to face the issue.
|
description: The OS in which you used Spotube to face the issue. Use comma to separate multiple OS.
|
||||||
placeholder: Android, Linux, macOS or Windows? Make sure to include the version too.
|
placeholder: Android, Linux, macOS or Windows? Make sure to include the version too.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
@ -53,7 +76,7 @@ body:
|
|||||||
description: Where did you install Spotube from?
|
description: Where did you install Spotube from?
|
||||||
multiple: true
|
multiple: true
|
||||||
options:
|
options:
|
||||||
- "Website (spotube.netlify.app) or (spotube.krtirtho.dev)"
|
- "Website (spotube.krtirtho.dev)"
|
||||||
- "GitHub Releases (Binary)"
|
- "GitHub Releases (Binary)"
|
||||||
- "GitHub Actions (Nightly Binary)"
|
- "GitHub Actions (Nightly Binary)"
|
||||||
- "Play Store (Android)"
|
- "Play Store (Android)"
|
||||||
@ -74,7 +97,10 @@ body:
|
|||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Self grab
|
label: Self grab
|
||||||
description: If you are a developer and want to work on this issue yourself, you can check this box and wait for maintainer response. We welcome contributions!
|
description: |
|
||||||
|
If you are a developer and want to work on this issue yourself, you can check this box and wait for maintainer response. Any contributions are welcome!
|
||||||
|
|
||||||
|
This project is maintained by one person. So PRs are always welcome. This is the best way to get your issue fixed faster.
|
||||||
options:
|
options:
|
||||||
- label: I'm ready to work on this issue!
|
- label: I'm ready to work on this issue!
|
||||||
required: false
|
required: false
|
21
.github/workflows/pr-lint.yml
vendored
@ -4,29 +4,34 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FLUTTER_VERSION: '3.16.0'
|
FLUTTER_VERSION: 3.35.2
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
|
||||||
|
- name: Dummy Envs
|
||||||
|
run: |
|
||||||
|
envsubst < .env.example > .env
|
||||||
|
env:
|
||||||
|
ENABLE_UPDATE_CHECK: true
|
||||||
|
LASTFM_API_KEY: xxx
|
||||||
|
LASTFM_API_SECRET: xxx
|
||||||
|
RELEASE_CHANNEL: nightly
|
||||||
|
HIDE_DONATIONS: 0
|
||||||
|
|
||||||
- name: Configure repo
|
- name: Configure repo
|
||||||
run: |
|
run: |
|
||||||
flutter pub get
|
flutter pub get
|
||||||
echo '${{ secrets.DOTENV_NIGHTLY }}' > .env
|
|
||||||
dart run build_runner build --delete-conflicting-outputs
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
|
||||||
- name: Lint Dart files
|
- name: Lint Dart files
|
||||||
run: |
|
run: |
|
||||||
dart analyze --no-fatal-warnings
|
dart analyze --no-fatal-warnings
|
||||||
|
|
||||||
- name: Lint translations & config files
|
|
||||||
run: |
|
|
||||||
npm install -g @prantlf/jsonlint
|
|
||||||
jsonlint -q -D --enforce-double-quotes ./lib/l10n/*.arb
|
|
||||||
jsonlint -q -D --enforce-double-quotes -T .vscode/*.json
|
|
43
.github/workflows/spotube-publish-binary.yml
vendored
@ -4,7 +4,7 @@ on:
|
|||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
description: Version to publish (x.x.x)
|
description: Version to publish (x.x.x)
|
||||||
default: 3.1.0
|
default: 4.0.0
|
||||||
required: true
|
required: true
|
||||||
dry_run:
|
dry_run:
|
||||||
description: Dry run
|
description: Dry run
|
||||||
@ -12,10 +12,10 @@ on:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: true
|
default: true
|
||||||
jobs:
|
jobs:
|
||||||
description: Jobs to run (flathub,aur,winget,chocolatey)
|
description: Jobs to run (flathub,aur,winget,chocolatey,playstore)
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
default: "flathub,aur,winget,chocolatey"
|
default: "flathub,aur,winget,chocolatey,playstore"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
flathub:
|
flathub:
|
||||||
@ -66,7 +66,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Release to AUR
|
- name: Release to AUR
|
||||||
if: ${{ !inputs.dry_run }}
|
if: ${{ !inputs.dry_run }}
|
||||||
uses: KSXGitHub/github-actions-deploy-aur@v2.7.0
|
uses: KSXGitHub/github-actions-deploy-aur@v2.7.2
|
||||||
with:
|
with:
|
||||||
pkgname: spotube-bin
|
pkgname: spotube-bin
|
||||||
pkgbuild: aur-struct/PKGBUILD
|
pkgbuild: aur-struct/PKGBUILD
|
||||||
@ -76,12 +76,12 @@ jobs:
|
|||||||
commit_message: Updated to v${{ inputs.version }}
|
commit_message: Updated to v${{ inputs.version }}
|
||||||
|
|
||||||
winget:
|
winget:
|
||||||
runs-on: windows-latest
|
runs-on: ubuntu-latest
|
||||||
if: contains(inputs.jobs, 'winget')
|
if: contains(inputs.jobs, 'winget')
|
||||||
steps:
|
steps:
|
||||||
- name: Release winget package
|
- name: Release winget package
|
||||||
if: ${{ !inputs.dry_run }}
|
if: ${{ !inputs.dry_run }}
|
||||||
uses: vedantmgoyal2009/winget-releaser@v2
|
uses: vedantmgoyal9/winget-releaser@main
|
||||||
with:
|
with:
|
||||||
version: ${{ inputs.version }}
|
version: ${{ inputs.version }}
|
||||||
release-tag: v${{ inputs.version }}
|
release-tag: v${{ inputs.version }}
|
||||||
@ -104,3 +104,34 @@ jobs:
|
|||||||
- name: Publish to Chocolatey Repository
|
- name: Publish to Chocolatey Repository
|
||||||
if: ${{ !inputs.dry_run }}
|
if: ${{ !inputs.dry_run }}
|
||||||
run: choco push Spotube-windows-x86_64.nupkg --source https://push.chocolatey.org/
|
run: choco push Spotube-windows-x86_64.nupkg --source https://push.chocolatey.org/
|
||||||
|
|
||||||
|
playstore:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: contains(inputs.jobs, 'playstore')
|
||||||
|
steps:
|
||||||
|
- name: Tagname (workflow dispatch)
|
||||||
|
run: echo 'TAG_NAME=${{inputs.version}}' >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- uses: robinraju/release-downloader@main
|
||||||
|
with:
|
||||||
|
repository: KRTirtho/spotube
|
||||||
|
tag: v${{ env.TAG_NAME }}
|
||||||
|
tarBall: false
|
||||||
|
zipBall: false
|
||||||
|
out-file-path: dist
|
||||||
|
fileName: "Spotube-playstore-all-arch.aab"
|
||||||
|
|
||||||
|
- name: Create service-account.json
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_BASE64 }}" | base64 -d > service-account.json
|
||||||
|
|
||||||
|
- name: Upload Android Release to Play Store
|
||||||
|
if: ${{!inputs.dry_run}}
|
||||||
|
uses: r0adkll/upload-google-play@v1
|
||||||
|
with:
|
||||||
|
serviceAccountJson: ./service-account.json
|
||||||
|
releaseFiles: ./dist/Spotube-playstore-all-arch.aab
|
||||||
|
packageName: oss.krtirtho.spotube
|
||||||
|
track: production
|
||||||
|
status: draft
|
||||||
|
releaseName: ${{ env.TAG_NAME }}
|
||||||
|
470
.github/workflows/spotube-release-binary.yml
vendored
@ -2,388 +2,123 @@ name: Spotube Release Binary
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
|
||||||
description: Version to release (x.x.x)
|
|
||||||
default: 3.4.1
|
|
||||||
required: true
|
|
||||||
channel:
|
channel:
|
||||||
type: choice
|
type: choice
|
||||||
description: Release Channel
|
|
||||||
required: true
|
|
||||||
options:
|
options:
|
||||||
- stable
|
- stable
|
||||||
- nightly
|
- nightly
|
||||||
default: nightly
|
default: nightly
|
||||||
|
description: The release channel
|
||||||
debug:
|
debug:
|
||||||
description: Debug on failed when channel is nightly
|
|
||||||
required: true
|
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
description: Debug with SSH toggle
|
||||||
|
required: false
|
||||||
dry_run:
|
dry_run:
|
||||||
description: Dry run
|
|
||||||
required: true
|
|
||||||
type: boolean
|
type: boolean
|
||||||
default: true
|
default: false
|
||||||
|
description: Dry run without uploading to release
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FLUTTER_VERSION: '3.16.3'
|
FLUTTER_VERSION: 3.35.2
|
||||||
|
FLUTTER_CHANNEL: master
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
windows:
|
build_platform:
|
||||||
runs-on: windows-latest
|
strategy:
|
||||||
steps:
|
matrix:
|
||||||
- uses: actions/checkout@v4
|
include:
|
||||||
- uses: subosito/flutter-action@v2.12.0
|
- os: ubuntu-22.04
|
||||||
with:
|
platform: linux
|
||||||
cache: true
|
arch: x86
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
files: |
|
||||||
|
|
||||||
- name: Replace pubspec version and BUILD_VERSION Env (nightly)
|
|
||||||
if: ${{ inputs.channel == 'nightly' }}
|
|
||||||
run: |
|
|
||||||
choco install sed make yq -y
|
|
||||||
yq -i '.version |= sub("\+\d+", "+${{ inputs.channel }}.")' pubspec.yaml
|
|
||||||
yq -i '.version += strenv(GITHUB_RUN_NUMBER)' pubspec.yaml
|
|
||||||
"BUILD_VERSION=${{ inputs.version }}+${{ inputs.channel }}.${{ github.run_number }}" >> $env:GITHUB_ENV
|
|
||||||
|
|
||||||
- name: BUILD_VERSION Env (stable)
|
|
||||||
if: ${{ inputs.channel == 'stable' }}
|
|
||||||
run: |
|
|
||||||
"BUILD_VERSION=${{ inputs.version }}" >> $env:GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Replace version in files
|
|
||||||
run: |
|
|
||||||
choco install sed make -y
|
|
||||||
sed -i "s/%{{SPOTUBE_VERSION}}%/${{ env.BUILD_VERSION }}/" windows/runner/Runner.rc
|
|
||||||
sed -i "s/%{{SPOTUBE_VERSION}}%/${{ env.BUILD_VERSION }}/" choco-struct/tools/VERIFICATION.txt
|
|
||||||
sed -i "s/%{{SPOTUBE_VERSION}}%/${{ env.BUILD_VERSION }}/" choco-struct/spotube.nuspec
|
|
||||||
|
|
||||||
- name: Create Stable .env
|
|
||||||
if: ${{ inputs.channel == 'stable' }}
|
|
||||||
run: echo '${{ secrets.DOTENV_RELEASE }}' > .env
|
|
||||||
|
|
||||||
- name: Create Nightly .env
|
|
||||||
if: ${{ inputs.channel == 'nightly' }}
|
|
||||||
run: echo '${{ secrets.DOTENV_NIGHTLY }}' > .env
|
|
||||||
|
|
||||||
- name: Generating Secrets
|
|
||||||
run: |
|
|
||||||
flutter config --enable-windows-desktop
|
|
||||||
flutter pub get
|
|
||||||
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
|
|
||||||
|
|
||||||
- name: Build Windows Executable
|
|
||||||
run: |
|
|
||||||
dart pub global activate flutter_distributor
|
|
||||||
make innoinstall
|
|
||||||
flutter_distributor package --platform=windows --targets=exe --skip-clean
|
|
||||||
mv dist/**/spotube-*-windows-setup.exe dist/Spotube-windows-x86_64-setup.exe
|
|
||||||
|
|
||||||
- name: Create Chocolatey Package and set hash
|
|
||||||
if: ${{ inputs.channel == 'stable' }}
|
|
||||||
run: |
|
|
||||||
Set-Variable -Name HASH -Value (Get-FileHash dist\Spotube-windows-x86_64-setup.exe).Hash
|
|
||||||
sed -i "s/%{{WIN_SHA256}}%/$HASH/" choco-struct/tools/VERIFICATION.txt
|
|
||||||
make choco
|
|
||||||
mv dist/spotube.*.nupkg dist/Spotube-windows-x86_64.nupkg
|
|
||||||
|
|
||||||
|
|
||||||
- name: Upload Artifact
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
if-no-files-found: error
|
|
||||||
name: Spotube-Release-Binaries
|
|
||||||
path: |
|
|
||||||
dist/Spotube-windows-x86_64.nupkg
|
|
||||||
dist/Spotube-windows-x86_64-setup.exe
|
|
||||||
|
|
||||||
- name: Debug With SSH When fails
|
|
||||||
if: ${{ failure() && inputs.debug && inputs.channel == 'nightly' }}
|
|
||||||
uses: mxschmitt/action-tmate@v3
|
|
||||||
with:
|
|
||||||
limit-access-to-actor: true
|
|
||||||
|
|
||||||
linux:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: subosito/flutter-action@v2.12.0
|
|
||||||
with:
|
|
||||||
cache: true
|
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
||||||
|
|
||||||
- name: Get current date
|
|
||||||
id: date
|
|
||||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update -y
|
|
||||||
sudo apt-get install -y tar clang cmake ninja-build pkg-config libgtk-3-dev make python3-pip python3-setuptools desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace fuse libunwind-dev locate patchelf gir1.2-appindicator3-0.1 libappindicator3-1 libappindicator3-dev libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev mpv libmpv-dev
|
|
||||||
|
|
||||||
- name: Install AppImage Tool
|
|
||||||
run: |
|
|
||||||
wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
|
||||||
chmod +x appimagetool
|
|
||||||
mv appimagetool /usr/local/bin/
|
|
||||||
|
|
||||||
- name: Replace pubspec version and BUILD_VERSION Env (nightly)
|
|
||||||
if: ${{ inputs.channel == 'nightly' }}
|
|
||||||
run: |
|
|
||||||
curl -sS https://webi.sh/yq | sh
|
|
||||||
yq -i '.version |= sub("\+\d+", "+${{ inputs.channel }}.")' pubspec.yaml
|
|
||||||
yq -i '.version += strenv(GITHUB_RUN_NUMBER)' pubspec.yaml
|
|
||||||
echo "BUILD_VERSION=${{ inputs.version }}+${{ inputs.channel }}.${{ github.run_number }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: BUILD_VERSION Env (stable)
|
|
||||||
if: ${{ inputs.channel == 'stable' }}
|
|
||||||
run: |
|
|
||||||
echo "BUILD_VERSION=${{ inputs.version }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Create Stable .env
|
|
||||||
if: ${{ inputs.channel == 'stable' }}
|
|
||||||
run: echo '${{ secrets.DOTENV_RELEASE }}' > .env
|
|
||||||
|
|
||||||
- name: Create Nightly .env
|
|
||||||
if: ${{ inputs.channel == 'nightly' }}
|
|
||||||
run: echo '${{ secrets.DOTENV_NIGHTLY }}' > .env
|
|
||||||
|
|
||||||
- name: Replace Version in files
|
|
||||||
run: |
|
|
||||||
sed -i 's|%{{APPDATA_RELEASE}}%|<release version="${{ env.BUILD_VERSION }}" date="${{ steps.date.outputs.date }}" />|' linux/com.github.KRTirtho.Spotube.appdata.xml
|
|
||||||
|
|
||||||
- name: Generate Secrets
|
|
||||||
run: |
|
|
||||||
flutter config --enable-linux-desktop
|
|
||||||
flutter pub get
|
|
||||||
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
|
|
||||||
|
|
||||||
- name: Build Linux Packages
|
|
||||||
run: |
|
|
||||||
dart pub global activate flutter_distributor
|
|
||||||
alias dpkg-deb="dpkg-deb --Zxz"
|
|
||||||
flutter_distributor package --platform=linux --targets=deb
|
|
||||||
flutter_distributor package --platform=linux --targets=rpm
|
|
||||||
|
|
||||||
- name: Create tar.xz (stable)
|
|
||||||
if: ${{ inputs.channel == 'stable' }}
|
|
||||||
run: make tar VERSION=${{ env.BUILD_VERSION }} ARCH=x64 PKG_ARCH=x86_64
|
|
||||||
|
|
||||||
- name: Create tar.xz (nightly)
|
|
||||||
if: ${{ inputs.channel == 'nightly' }}
|
|
||||||
run: make tar VERSION=nightly ARCH=x64 PKG_ARCH=x86_64
|
|
||||||
|
|
||||||
- name: Move Files to dist
|
|
||||||
run: |
|
|
||||||
mv build/spotube-linux-*-x86_64.tar.xz dist/
|
|
||||||
mv dist/**/spotube-*-linux.deb dist/Spotube-linux-x86_64.deb
|
|
||||||
mv dist/**/spotube-*-linux.rpm dist/Spotube-linux-x86_64.rpm
|
|
||||||
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
if-no-files-found: error
|
|
||||||
name: Spotube-Release-Binaries
|
|
||||||
path: |
|
|
||||||
dist/Spotube-linux-x86_64.deb
|
dist/Spotube-linux-x86_64.deb
|
||||||
dist/Spotube-linux-x86_64.rpm
|
dist/Spotube-linux-x86_64.rpm
|
||||||
dist/spotube-linux-${{ env.BUILD_VERSION }}-x86_64.tar.xz
|
dist/spotube-linux-*-x86_64.tar.xz
|
||||||
|
- os: ubuntu-22.04-arm
|
||||||
- name: Debug With SSH When fails
|
platform: linux
|
||||||
if: ${{ failure() && inputs.debug && inputs.channel == 'nightly' }}
|
arch: arm64
|
||||||
uses: mxschmitt/action-tmate@v3
|
files: |
|
||||||
with:
|
dist/Spotube-linux-aarch64.deb
|
||||||
limit-access-to-actor: true
|
dist/spotube-linux-*-aarch64.tar.xz
|
||||||
|
- os: ubuntu-22.04
|
||||||
|
platform: android
|
||||||
android:
|
arch: all
|
||||||
runs-on: ubuntu-latest
|
files: |
|
||||||
|
build/Spotube-android-all-arch.apk
|
||||||
|
build/Spotube-playstore-all-arch.aab
|
||||||
|
- os: windows-latest
|
||||||
|
platform: windows
|
||||||
|
arch: x86
|
||||||
|
files: |
|
||||||
|
dist/Spotube-windows-x86_64.nupkg
|
||||||
|
dist/Spotube-windows-x86_64-setup.exe
|
||||||
|
- os: macos-14
|
||||||
|
platform: ios
|
||||||
|
arch: all
|
||||||
|
files: |
|
||||||
|
Spotube-iOS.ipa
|
||||||
|
- os: macos-14
|
||||||
|
platform: macos
|
||||||
|
arch: all
|
||||||
|
files: |
|
||||||
|
build/Spotube-macos-universal.dmg
|
||||||
|
build/Spotube-macos-universal.pkg
|
||||||
|
runs-on: ${{matrix.os}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: subosito/flutter-action@v2.12.0
|
- uses: subosito/flutter-action@v2.18.0
|
||||||
with:
|
with:
|
||||||
cache: true
|
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
channel: ${{ env.FLUTTER_CHANNEL }}
|
||||||
|
cache: true
|
||||||
|
git-source: https://github.com/flutter/flutter.git
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Setup Java
|
||||||
run: |
|
if: ${{matrix.platform == 'android'}}
|
||||||
sudo apt-get update -y
|
uses: actions/setup-java@v4
|
||||||
sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev make python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace fuse xmlstarlet
|
with:
|
||||||
|
distribution: "zulu"
|
||||||
|
java-version: "17"
|
||||||
|
cache: "gradle"
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
- name: Replace pubspec version and BUILD_VERSION Env (nightly)
|
- name: Setup Rust toolchain
|
||||||
if: ${{ inputs.channel == 'nightly' }}
|
uses: dtolnay/rust-toolchain@stable
|
||||||
run: |
|
with:
|
||||||
curl -sS https://webi.sh/yq | sh
|
toolchain: stable
|
||||||
yq -i '.version |= sub("\+\d+", "+${{ inputs.channel }}.")' pubspec.yaml
|
|
||||||
yq -i '.version += strenv(GITHUB_RUN_NUMBER)' pubspec.yaml
|
|
||||||
echo "BUILD_VERSION=${{ inputs.version }}+${{ inputs.channel }}.${{ github.run_number }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: BUILD_VERSION Env (stable)
|
- name: Install Xcode
|
||||||
if: ${{ inputs.channel == 'stable' }}
|
if: ${{matrix.platform == 'ios'}}
|
||||||
run: |
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
echo "BUILD_VERSION=${{ inputs.version }}" >> $GITHUB_ENV
|
with:
|
||||||
|
xcode-version: "16.2"
|
||||||
|
|
||||||
- name: Create Stable .env
|
- name: Install ${{matrix.platform}} dependencies
|
||||||
if: ${{ inputs.channel == 'stable' }}
|
|
||||||
run: echo '${{ secrets.DOTENV_RELEASE }}' > .env
|
|
||||||
|
|
||||||
- name: Create Nightly .env
|
|
||||||
if: ${{ inputs.channel == 'nightly' }}
|
|
||||||
run: echo '${{ secrets.DOTENV_NIGHTLY }}' > .env
|
|
||||||
|
|
||||||
- name: Generate Secrets
|
|
||||||
run: |
|
run: |
|
||||||
flutter pub get
|
flutter pub get
|
||||||
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
|
dart cli/cli.dart install-dependencies --platform=${{matrix.platform}}
|
||||||
|
|
||||||
- name: Sign Apk
|
- name: Sign Apk
|
||||||
|
if: ${{matrix.platform == 'android'}}
|
||||||
run: |
|
run: |
|
||||||
echo '${{ secrets.KEYSTORE }}' | base64 --decode > android/app/upload-keystore.jks
|
echo '${{ secrets.KEYSTORE }}' | base64 --decode > android/app/upload-keystore.jks
|
||||||
echo '${{ secrets.KEY_PROPERTIES }}' > android/key.properties
|
echo '${{ secrets.KEY_PROPERTIES }}' > android/key.properties
|
||||||
|
|
||||||
- name: Build Apk
|
- name: Build ${{matrix.platform}} binaries
|
||||||
run: |
|
run: dart cli/cli.dart build --arch=${{matrix.arch}} ${{matrix.platform}}
|
||||||
flutter build apk --flavor ${{ inputs.channel }}
|
env:
|
||||||
mv build/app/outputs/flutter-apk/app-${{ inputs.channel }}-release.apk build/Spotube-android-all-arch.apk
|
CHANNEL: ${{inputs.channel}}
|
||||||
|
DOTENV: ${{secrets.DOTENV_RELEASE}}
|
||||||
|
|
||||||
- name: Build Playstore AppBundle
|
- uses: actions/upload-artifact@v4
|
||||||
run: |
|
|
||||||
echo 'ENABLE_UPDATE_CHECK=0' >> .env
|
|
||||||
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
|
|
||||||
export MANIFEST=android/app/src/main/AndroidManifest.xml
|
|
||||||
xmlstarlet ed -d '//meta-data[@android:name="com.google.android.gms.car.application"]' $MANIFEST > $MANIFEST.tmp
|
|
||||||
mv $MANIFEST.tmp $MANIFEST
|
|
||||||
flutter build appbundle --flavor ${{ inputs.channel }}
|
|
||||||
mv build/app/outputs/bundle/${{ inputs.channel }}Release/app-${{ inputs.channel }}-release.aab build/Spotube-playstore-all-arch.aab
|
|
||||||
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
with:
|
with:
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
name: Spotube-Release-Binaries
|
name: ${{matrix.platform}}-${{matrix.arch}}
|
||||||
path: |
|
path: ${{matrix.files}}
|
||||||
build/Spotube-android-all-arch.apk
|
|
||||||
build/Spotube-playstore-all-arch.aab
|
|
||||||
|
|
||||||
- name: Debug With SSH When fails
|
|
||||||
if: ${{ failure() && inputs.debug && inputs.channel == 'nightly' }}
|
|
||||||
uses: mxschmitt/action-tmate@v3
|
|
||||||
with:
|
|
||||||
limit-access-to-actor: true
|
|
||||||
|
|
||||||
macos:
|
|
||||||
|
|
||||||
runs-on: macos-12
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: subosito/flutter-action@v2.12.0
|
|
||||||
with:
|
|
||||||
cache: true
|
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
||||||
|
|
||||||
- name: Replace pubspec version and BUILD_VERSION Env (nightly)
|
|
||||||
if: ${{ inputs.channel == 'nightly' }}
|
|
||||||
run: |
|
|
||||||
brew install yq
|
|
||||||
yq -i '.version |= sub("\+\d+", "+${{ inputs.channel }}.")' pubspec.yaml
|
|
||||||
yq -i '.version += strenv(GITHUB_RUN_NUMBER)' pubspec.yaml
|
|
||||||
echo "BUILD_VERSION=${{ inputs.version }}+${{ inputs.channel }}.${{ github.run_number }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: BUILD_VERSION Env (stable)
|
|
||||||
if: ${{ inputs.channel == 'stable' }}
|
|
||||||
run: |
|
|
||||||
echo "BUILD_VERSION=${{ inputs.version }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Create Stable .env
|
|
||||||
if: ${{ inputs.channel == 'stable' }}
|
|
||||||
run: echo '${{ secrets.DOTENV_RELEASE }}' > .env
|
|
||||||
|
|
||||||
- name: Create Nightly .env
|
|
||||||
if: ${{ inputs.channel == 'nightly' }}
|
|
||||||
run: echo '${{ secrets.DOTENV_NIGHTLY }}' > .env
|
|
||||||
|
|
||||||
- name: Generate Secrets
|
|
||||||
run: |
|
|
||||||
dart pub global activate flutter_distributor
|
|
||||||
flutter pub get
|
|
||||||
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
|
|
||||||
|
|
||||||
- name: Build Macos App
|
|
||||||
run: |
|
|
||||||
flutter config --enable-macos-desktop
|
|
||||||
flutter build macos
|
|
||||||
du -sh build/macos/Build/Products/Release/spotube.app
|
|
||||||
|
|
||||||
- name: Package Macos App
|
|
||||||
run: |
|
|
||||||
python3 -m pip install setuptools
|
|
||||||
npm install -g appdmg
|
|
||||||
mkdir -p build/${{ env.BUILD_VERSION }}
|
|
||||||
appdmg appdmg.json build/Spotube-macos-universal.dmg
|
|
||||||
flutter_distributor package --platform=macos --targets pkg --skip-clean
|
|
||||||
mv dist/**/spotube-*-macos.pkg build/Spotube-macos-universal.pkg
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
if-no-files-found: error
|
|
||||||
name: Spotube-Release-Binaries
|
|
||||||
path: |
|
|
||||||
build/Spotube-macos-universal.dmg
|
|
||||||
build/Spotube-macos-universal.pkg
|
|
||||||
|
|
||||||
- name: Debug With SSH When fails
|
|
||||||
if: ${{ failure() && inputs.debug && inputs.channel == 'nightly' }}
|
|
||||||
uses: mxschmitt/action-tmate@v3
|
|
||||||
with:
|
|
||||||
limit-access-to-actor: true
|
|
||||||
|
|
||||||
iOS:
|
|
||||||
runs-on: macos-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: subosito/flutter-action@v2.10.0
|
|
||||||
with:
|
|
||||||
cache: true
|
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
||||||
|
|
||||||
- name: Replace pubspec version and BUILD_VERSION Env (nightly)
|
|
||||||
if: ${{ inputs.channel == 'nightly' }}
|
|
||||||
run: |
|
|
||||||
brew install yq
|
|
||||||
yq -i '.version |= sub("\+\d+", "+${{ inputs.channel }}.")' pubspec.yaml
|
|
||||||
yq -i '.version += strenv(GITHUB_RUN_NUMBER)' pubspec.yaml
|
|
||||||
echo "BUILD_VERSION=${{ inputs.version }}+${{ inputs.channel }}.${{ github.run_number }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: BUILD_VERSION Env (stable)
|
|
||||||
if: ${{ inputs.channel == 'stable' }}
|
|
||||||
run: |
|
|
||||||
echo "BUILD_VERSION=${{ inputs.version }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Create Stable .env
|
|
||||||
if: ${{ inputs.channel == 'stable' }}
|
|
||||||
run: echo '${{ secrets.DOTENV_RELEASE }}' > .env
|
|
||||||
|
|
||||||
- name: Create Nightly .env
|
|
||||||
if: ${{ inputs.channel == 'nightly' }}
|
|
||||||
run: echo '${{ secrets.DOTENV_NIGHTLY }}' > .env
|
|
||||||
|
|
||||||
- name: Generate Secrets
|
|
||||||
run: |
|
|
||||||
flutter pub get
|
|
||||||
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
|
|
||||||
|
|
||||||
- name: Build iOS iPA
|
|
||||||
run: |
|
|
||||||
flutter build ios --release --no-codesign --flavor ${{ inputs.channel }}
|
|
||||||
ln -sf ./build/ios/iphoneos Payload
|
|
||||||
zip -r9 Spotube-iOS.ipa Payload/${{ inputs.channel }}.app
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
if-no-files-found: error
|
|
||||||
name: Spotube-Release-Binaries
|
|
||||||
path: |
|
|
||||||
Spotube-iOS.ipa
|
|
||||||
|
|
||||||
- name: Debug With SSH When fails
|
- name: Debug With SSH When fails
|
||||||
if: ${{ failure() && inputs.debug && inputs.channel == 'nightly' }}
|
if: ${{ failure() && inputs.debug && inputs.channel == 'nightly' }}
|
||||||
@ -392,18 +127,13 @@ jobs:
|
|||||||
limit-access-to-actor: true
|
limit-access-to-actor: true
|
||||||
|
|
||||||
upload:
|
upload:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
needs:
|
needs:
|
||||||
- windows
|
- build_platform
|
||||||
- linux
|
|
||||||
- android
|
|
||||||
- macos
|
|
||||||
- iOS
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Spotube-Release-Binaries
|
|
||||||
path: ./Spotube-Release-Binaries
|
path: ./Spotube-Release-Binaries
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@ -412,14 +142,19 @@ jobs:
|
|||||||
- name: Generate Checksums
|
- name: Generate Checksums
|
||||||
run: |
|
run: |
|
||||||
tree .
|
tree .
|
||||||
md5sum Spotube-Release-Binaries/* >> RELEASE.md5sum
|
find Spotube-Release-Binaries -type f -exec md5sum {} \; >> RELEASE.md5sum
|
||||||
sha256sum Spotube-Release-Binaries/* >> RELEASE.sha256sum
|
find Spotube-Release-Binaries -type f -exec sha256sum {} \; >> RELEASE.sha256sum
|
||||||
|
sed -i 's|Spotube-Release-Binaries/.*/\([^/]*\)$|\1|' RELEASE.sha256sum RELEASE.md5sum
|
||||||
sed -i 's|Spotube-Release-Binaries/||' RELEASE.sha256sum RELEASE.md5sum
|
sed -i 's|Spotube-Release-Binaries/||' RELEASE.sha256sum RELEASE.md5sum
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- name: Extract pubspec version
|
||||||
|
run: |
|
||||||
|
echo "PUBSPEC_VERSION=$(grep -oP 'version:\s*\K[^+]+(?=\+)' pubspec.yaml)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
name: Spotube-Release-Binaries
|
name: sums
|
||||||
path: |
|
path: |
|
||||||
RELEASE.md5sum
|
RELEASE.md5sum
|
||||||
RELEASE.sha256sum
|
RELEASE.sha256sum
|
||||||
@ -429,12 +164,12 @@ jobs:
|
|||||||
uses: ncipollo/release-action@v1
|
uses: ncipollo/release-action@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: v${{ inputs.version }} # mind the "v" prefix
|
tag: v${{ env.PUBSPEC_VERSION }} # mind the "v" prefix
|
||||||
omitBodyDuringUpdate: true
|
omitBodyDuringUpdate: true
|
||||||
omitNameDuringUpdate: true
|
omitNameDuringUpdate: true
|
||||||
omitPrereleaseDuringUpdate: true
|
omitPrereleaseDuringUpdate: true
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
artifacts: Spotube-Release-Binaries/*,RELEASE.sha256sum,RELEASE.md5sum
|
artifacts: Spotube-Release-Binaries/**/*,RELEASE.sha256sum,RELEASE.md5sum
|
||||||
|
|
||||||
- name: Upload Release Binaries (nightly)
|
- name: Upload Release Binaries (nightly)
|
||||||
if: ${{ !inputs.dry_run && inputs.channel == 'nightly' }}
|
if: ${{ !inputs.dry_run && inputs.channel == 'nightly' }}
|
||||||
@ -446,4 +181,15 @@ jobs:
|
|||||||
omitNameDuringUpdate: true
|
omitNameDuringUpdate: true
|
||||||
omitPrereleaseDuringUpdate: true
|
omitPrereleaseDuringUpdate: true
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
artifacts: Spotube-Release-Binaries/*,RELEASE.sha256sum,RELEASE.md5sum
|
artifacts: Spotube-Release-Binaries/**/*,RELEASE.sha256sum,RELEASE.md5sum
|
||||||
|
body: |
|
||||||
|
Build Number: ${{github.run_number}}
|
||||||
|
|
||||||
|
Nightly release includes newest features but may contain bugs
|
||||||
|
It is preferred to use the stable version unless you know what you're doing
|
||||||
|
|
||||||
|
- name: Debug With SSH When fails
|
||||||
|
if: ${{ failure() && inputs.debug && inputs.channel == 'nightly' }}
|
||||||
|
uses: mxschmitt/action-tmate@v3
|
||||||
|
with:
|
||||||
|
limit-access-to-actor: true
|
||||||
|
9
.gitignore
vendored
@ -73,6 +73,13 @@ dist
|
|||||||
appimage-build
|
appimage-build
|
||||||
|
|
||||||
android/key.properties
|
android/key.properties
|
||||||
.fvm/flutter_sdk
|
|
||||||
|
|
||||||
**/pb_data
|
**/pb_data
|
||||||
|
|
||||||
|
tm.json
|
||||||
|
|
||||||
|
# FVM Version Cache
|
||||||
|
.fvm/
|
||||||
|
|
||||||
|
android/build
|
||||||
|
android/app/.cxx
|
||||||
|
16
.metadata
@ -1,11 +1,11 @@
|
|||||||
# This file tracks properties of this Flutter project.
|
# This file tracks properties of this Flutter project.
|
||||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
#
|
#
|
||||||
# This file should be version controlled.
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: eb6d86ee27deecba4a83536aa20f366a6044895c
|
revision: "d7b523b356d15fb81e7d340bbe52b47f93937323"
|
||||||
channel: stable
|
channel: "stable"
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
|
|
||||||
@ -13,11 +13,11 @@ project_type: app
|
|||||||
migration:
|
migration:
|
||||||
platforms:
|
platforms:
|
||||||
- platform: root
|
- platform: root
|
||||||
create_revision: eb6d86ee27deecba4a83536aa20f366a6044895c
|
create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
|
||||||
base_revision: eb6d86ee27deecba4a83536aa20f366a6044895c
|
base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
|
||||||
- platform: macos
|
- platform: windows
|
||||||
create_revision: eb6d86ee27deecba4a83536aa20f366a6044895c
|
create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
|
||||||
base_revision: eb6d86ee27deecba4a83536aa20f366a6044895c
|
base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
|
||||||
|
|
||||||
# User provided section
|
# User provided section
|
||||||
|
|
||||||
|
22
.vscode/launch.json
vendored
@ -17,6 +17,17 @@
|
|||||||
"dev"
|
"dev"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "spotube (mobile-skia)",
|
||||||
|
"type": "dart",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "lib/main.dart",
|
||||||
|
"args": [
|
||||||
|
"--flavor",
|
||||||
|
"dev",
|
||||||
|
"--no-enable-impeller"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "spotube (profile)",
|
"name": "spotube (profile)",
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
@ -30,6 +41,17 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "lib/main.dart",
|
"program": "lib/main.dart",
|
||||||
"flutterMode": "release"
|
"flutterMode": "release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "spotube (mobile) (release)",
|
||||||
|
"type": "dart",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "lib/main.dart",
|
||||||
|
"flutterMode": "release",
|
||||||
|
"args": [
|
||||||
|
"--flavor",
|
||||||
|
"dev"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"compounds": []
|
"compounds": []
|
||||||
|
16
.vscode/settings.json
vendored
@ -2,13 +2,25 @@
|
|||||||
"cmake.configureOnOpen": false,
|
"cmake.configureOnOpen": false,
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"acousticness",
|
"acousticness",
|
||||||
|
"ambiguate",
|
||||||
|
"Amoled",
|
||||||
|
"Buildless",
|
||||||
|
"configurators",
|
||||||
"danceability",
|
"danceability",
|
||||||
|
"fuzzywuzzy",
|
||||||
|
"gapless",
|
||||||
"instrumentalness",
|
"instrumentalness",
|
||||||
|
"isrc",
|
||||||
"Mpris",
|
"Mpris",
|
||||||
|
"RGBO",
|
||||||
"riverpod",
|
"riverpod",
|
||||||
"Scrobblenaut",
|
"Scrobblenaut",
|
||||||
|
"shadcn",
|
||||||
|
"skeletonizer",
|
||||||
|
"songlink",
|
||||||
"speechiness",
|
"speechiness",
|
||||||
"Spotube",
|
"Spotube",
|
||||||
|
"titlebar",
|
||||||
"winget"
|
"winget"
|
||||||
],
|
],
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
@ -16,5 +28,7 @@
|
|||||||
"explorer.fileNesting.patterns": {
|
"explorer.fileNesting.patterns": {
|
||||||
"pubspec.yaml": "pubspec.lock,analysis_options.yaml,.packages,.flutter-plugins,.flutter-plugins-dependencies,flutter_launcher_icons*.yaml,flutter_native_splash*.yaml",
|
"pubspec.yaml": "pubspec.lock,analysis_options.yaml,.packages,.flutter-plugins,.flutter-plugins-dependencies,flutter_launcher_icons*.yaml,flutter_native_splash*.yaml",
|
||||||
"README.md": "LICENSE,CODE_OF_CONDUCT.md,CONTRIBUTING.md,SECURITY.md,CONTRIBUTION.md,CHANGELOG.md,PRIVACY_POLICY.md",
|
"README.md": "LICENSE,CODE_OF_CONDUCT.md,CONTRIBUTING.md,SECURITY.md,CONTRIBUTION.md,CHANGELOG.md,PRIVACY_POLICY.md",
|
||||||
}
|
"*.dart": "${capture}.g.dart,${capture}.freezed.dart"
|
||||||
|
},
|
||||||
|
"dart.flutterSdkPath": ".fvm/versions/3.35.2"
|
||||||
}
|
}
|
170
.vscode/snippets.code-snippets
vendored
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
{
|
||||||
|
"PaginatedState": {
|
||||||
|
"scope": "dart",
|
||||||
|
"prefix": "paginatedState",
|
||||||
|
"description": "Generate a PaginatedState",
|
||||||
|
"body": [
|
||||||
|
"class ${1:Model}State extends PaginatedState<${2:Model}> {",
|
||||||
|
" ${1:Model}State({",
|
||||||
|
" required super.items,",
|
||||||
|
" required super.offset,",
|
||||||
|
" required super.limit,",
|
||||||
|
" required super.hasMore,",
|
||||||
|
" });",
|
||||||
|
" ",
|
||||||
|
" @override",
|
||||||
|
" ${1:Model}State copyWith({",
|
||||||
|
" List<${2:Model}>? items,",
|
||||||
|
" int? offset,",
|
||||||
|
" int? limit,",
|
||||||
|
" bool? hasMore,",
|
||||||
|
" }) {",
|
||||||
|
" return ${1:Model}State(",
|
||||||
|
" items: items ?? this.items,",
|
||||||
|
" offset: offset ?? this.offset,",
|
||||||
|
" limit: limit ?? this.limit,",
|
||||||
|
" hasMore: hasMore ?? this.hasMore,",
|
||||||
|
" );",
|
||||||
|
" }",
|
||||||
|
"}"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"PaginatedAsyncNotifier": {
|
||||||
|
"scope": "dart",
|
||||||
|
"prefix": "paginatedAsyncNotifier",
|
||||||
|
"description": "Generate a PaginatedAsyncNotifier",
|
||||||
|
"body": [
|
||||||
|
"class ${1:NotifierName}Notifier extends PaginatedAsyncNotifier<${3:Item}, ${2:Model}State> {",
|
||||||
|
" ${1:NotifierName}Notifier() : super();",
|
||||||
|
" ",
|
||||||
|
" @override",
|
||||||
|
" fetch(int offset, int limit) async {",
|
||||||
|
" throw UnimplementedError();",
|
||||||
|
" }",
|
||||||
|
" ",
|
||||||
|
" @override",
|
||||||
|
" build() async {",
|
||||||
|
" throw UnimplementedError();",
|
||||||
|
" }",
|
||||||
|
"}"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"PaginaitedNotifierWithState": {
|
||||||
|
"scope": "dart",
|
||||||
|
"prefix": "paginatedNotifierWithState",
|
||||||
|
"description": "Generate a PaginatedNotifier with PaginatedState",
|
||||||
|
"body": [
|
||||||
|
"class $1State extends PaginatedState<$2> {",
|
||||||
|
" $1State({",
|
||||||
|
" required super.items,",
|
||||||
|
" required super.offset,",
|
||||||
|
" required super.limit,",
|
||||||
|
" required super.hasMore,",
|
||||||
|
" });",
|
||||||
|
" ",
|
||||||
|
" @override",
|
||||||
|
" $1State copyWith({",
|
||||||
|
" List<$2>? items,",
|
||||||
|
" int? offset,",
|
||||||
|
" int? limit,",
|
||||||
|
" bool? hasMore,",
|
||||||
|
" }) {",
|
||||||
|
" return $1State(",
|
||||||
|
" items: items ?? this.items,",
|
||||||
|
" offset: offset ?? this.offset,",
|
||||||
|
" limit: limit ?? this.limit,",
|
||||||
|
" hasMore: hasMore ?? this.hasMore,",
|
||||||
|
" );",
|
||||||
|
" }",
|
||||||
|
"}",
|
||||||
|
" ",
|
||||||
|
"class $1Notifier",
|
||||||
|
" extends PaginatedAsyncNotifier<$2, $1State> {",
|
||||||
|
" $1Notifier() : super();",
|
||||||
|
" ",
|
||||||
|
" @override",
|
||||||
|
" fetch(int offset, int limit) async {",
|
||||||
|
" throw UnimplementedError();",
|
||||||
|
" }",
|
||||||
|
" ",
|
||||||
|
" @override",
|
||||||
|
" build() async {",
|
||||||
|
" throw UnimplementedError();",
|
||||||
|
" }",
|
||||||
|
"}",
|
||||||
|
" ",
|
||||||
|
"final ${1/(.*)/${1:/camelcase}/}Provider = AsyncNotifierProvider<$1Notifier, $1State>(",
|
||||||
|
" ()=> $1Notifier(),",
|
||||||
|
");"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"FamilyPaginatedAsyncNotifier": {
|
||||||
|
"scope": "dart",
|
||||||
|
"prefix": "familyPaginatedAsyncNotifier",
|
||||||
|
"description": "Generate a FamilyPaginatedAsyncNotifier",
|
||||||
|
"body": [
|
||||||
|
"class ${1:NotifierName}Notifier extends FamilyPaginatedAsyncNotifier<${3:Item}, ${2:Model}State, {$4:Arg}> {",
|
||||||
|
" ${1:NotifierName}Notifier() : super();",
|
||||||
|
" ",
|
||||||
|
" @override",
|
||||||
|
" fetch(arg, offset, limit) async {",
|
||||||
|
" throw UnimplementedError();",
|
||||||
|
" }",
|
||||||
|
" ",
|
||||||
|
" @override",
|
||||||
|
" build(arg) async {",
|
||||||
|
" throw UnimplementedError();",
|
||||||
|
" }",
|
||||||
|
"}"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"FamilyPaginaitedNotifierWithState": {
|
||||||
|
"scope": "dart",
|
||||||
|
"prefix": "familyPaginatedNotifierWithState",
|
||||||
|
"description": "Generate a FamilyPaginatedAsyncNotifier with PaginatedState",
|
||||||
|
"body": [
|
||||||
|
"class $1State extends PaginatedState<$2> {",
|
||||||
|
" $1State({",
|
||||||
|
" required super.items,",
|
||||||
|
" required super.offset,",
|
||||||
|
" required super.limit,",
|
||||||
|
" required super.hasMore,",
|
||||||
|
" });",
|
||||||
|
" ",
|
||||||
|
" @override",
|
||||||
|
" $1State copyWith({",
|
||||||
|
" List<$2>? items,",
|
||||||
|
" int? offset,",
|
||||||
|
" int? limit,",
|
||||||
|
" bool? hasMore,",
|
||||||
|
" }) {",
|
||||||
|
" return $1State(",
|
||||||
|
" items: items ?? this.items,",
|
||||||
|
" offset: offset ?? this.offset,",
|
||||||
|
" limit: limit ?? this.limit,",
|
||||||
|
" hasMore: hasMore ?? this.hasMore,",
|
||||||
|
" );",
|
||||||
|
" }",
|
||||||
|
"}",
|
||||||
|
" ",
|
||||||
|
"class $1Notifier",
|
||||||
|
" extends FamilyPaginatedAsyncNotifier<$2, $1State, $3> {",
|
||||||
|
" $1Notifier() : super();",
|
||||||
|
" ",
|
||||||
|
" @override",
|
||||||
|
" fetch(arg, offset, limit) async {",
|
||||||
|
" throw UnimplementedError();",
|
||||||
|
" }",
|
||||||
|
" ",
|
||||||
|
" @override",
|
||||||
|
" build(arg) async {",
|
||||||
|
" throw UnimplementedError();",
|
||||||
|
" }",
|
||||||
|
"}",
|
||||||
|
" ",
|
||||||
|
"final ${1/(.*)/${1:/camelcase}/}Provider = AsyncNotifierProviderFamily<$1Notifier, $1State, $3>(",
|
||||||
|
" ()=> $1Notifier(),",
|
||||||
|
");"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
1277
CHANGELOG.md
@ -25,14 +25,14 @@ All types of contributions are encouraged and valued. See the [Table of Contents
|
|||||||
- [Before Submitting an Enhancement](#before-submitting-an-enhancement)
|
- [Before Submitting an Enhancement](#before-submitting-an-enhancement)
|
||||||
- [How Do I Submit a Good Enhancement Suggestion?](#how-do-i-submit-a-good-enhancement-suggestion)
|
- [How Do I Submit a Good Enhancement Suggestion?](#how-do-i-submit-a-good-enhancement-suggestion)
|
||||||
- [Your First Code Contribution](#your-first-code-contribution)
|
- [Your First Code Contribution](#your-first-code-contribution)
|
||||||
- [Submit translations](#submit-translations)
|
- [Submit Translations](#submit-translations)
|
||||||
|
|
||||||
## Code of Conduct
|
## Code of Conduct
|
||||||
|
|
||||||
This project and everyone participating in it is governed by the
|
This project and everyone participating in it is governed by the
|
||||||
[Spotube Code of Conduct](https://github.com/KRTirtho/spotube/blob/master/CODE_OF_CONDUCT.md).
|
[Spotube Code of Conduct](https://github.com/KRTirtho/spotube/blob/master/CODE_OF_CONDUCT.md).
|
||||||
By participating, you are expected to uphold this code. Please report unacceptable behavior
|
By participating, you are expected to uphold this code. Please report unacceptable behavior
|
||||||
to <>.
|
to krtirtho@gmail.com.
|
||||||
|
|
||||||
## I Have a Question
|
## I Have a Question
|
||||||
|
|
||||||
@ -119,30 +119,30 @@ Enhancement suggestions are tracked as [GitHub issues](https://github.com/KRTirt
|
|||||||
|
|
||||||
Do the following:
|
Do the following:
|
||||||
|
|
||||||
- Download the latest Flutter SDK (>=3.16.0) & enable desktop support
|
- Install [Dart](https://dart.dev/get-dart) and [fvm](https://fvm.app/documentation/getting-started/installation)
|
||||||
- Install Development dependencies in linux
|
- Install Development dependencies in linux
|
||||||
- Debian (>=12/Bookworm)/Ubuntu
|
- Debian (>=12/Bookworm)/Ubuntu
|
||||||
```bash
|
```bash
|
||||||
$ apt-get install mpv libmpv-dev libappindicator3-1 gir1.2-appindicator3-0.1 libappindicator3-dev libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev
|
$ apt-get install mpv libmpv-dev libappindicator3-1 gir1.2-appindicator3-0.1 libappindicator3-dev libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev avahi-daemon avahi-discover avahi-utils libnss-mdns mdns-scan libwebkit2gtk-4.1-0 libwebkit2gtk-4.1-dev libsoup-3.0-0 libsoup-3.0-dev
|
||||||
```
|
```
|
||||||
- Use `libjsoncpp1` instead of `libjsoncpp25` (for Ubuntu < 22.04)
|
- Use `libjsoncpp1` instead of `libjsoncpp25` (for Ubuntu < 22.04)
|
||||||
- Arch/Manjaro
|
- Arch/Manjaro
|
||||||
```bash
|
```bash
|
||||||
yay -S mpv libappindicator-gtk3 libsecret jsoncpp libnotify
|
yay -S mpv libappindicator-gtk3 libsecret jsoncpp libnotify avahi nss-mdns mdns-scan webkit2gtk-4.1 libsoup3
|
||||||
```
|
```
|
||||||
- Fedora
|
- Fedora
|
||||||
```bash
|
```bash
|
||||||
dnf install mpv mpv-devel libappindicator-gtk3 libappindicator-gtk3-devel libsecret libsecret-devel jsoncpp jsoncpp-devel libnotify libnotify-devel
|
dnf install mpv mpv-devel libappindicator-gtk3 libappindicator-gtk3-devel libsecret libsecret-devel jsoncpp jsoncpp-devel libnotify libnotify-devel avahi mdns-scan nss-mdns webkit2gtk4.1 webkit2gtk4.1-devel libsoup3 libsoup3-devel
|
||||||
```
|
```
|
||||||
- Clone the Repo
|
- Clone the Repo
|
||||||
- Create a `.env` in root of the project following the `.env.example` template
|
- Create a `.env` in root of the project following the `.env.example` template
|
||||||
- Now run the following to bootstrap the project
|
- Now run the following to bootstrap the project
|
||||||
```bash
|
```bash
|
||||||
flutter pub get && dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
|
fvm flutter pub get && fvm dart run build_runner build --delete-conflicting-outputs
|
||||||
```
|
```
|
||||||
- Finally run these following commands in the root of the project to start the Spotube Locally
|
- Finally run these following commands in the root of the project to start the Spotube Locally
|
||||||
```bash
|
```bash
|
||||||
flutter run -d <window|macos|linux|(<android-device-id>)>
|
fvm flutter run -d <window|macos|linux|(<android-device-id>)>
|
||||||
```
|
```
|
||||||
|
|
||||||
Do debugging/testing/build etc then submit to us with PR against the development branch (dev) & we'll review your code
|
Do debugging/testing/build etc then submit to us with PR against the development branch (dev) & we'll review your code
|
||||||
|
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
BSD-4-Clause License
|
BSD-4-Clause License
|
||||||
|
|
||||||
Copyright (c) 2023 Kingkor Roy Tirtho. All rights reserved.
|
Copyright (c) 2025 Kingkor Roy Tirtho. All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
16
Makefile
@ -8,7 +8,7 @@ tar:
|
|||||||
mkdir -p $(TEMP_DIR)\
|
mkdir -p $(TEMP_DIR)\
|
||||||
&& cp -r $(BUNDLE_DIR)/* $(TEMP_DIR)\
|
&& cp -r $(BUNDLE_DIR)/* $(TEMP_DIR)\
|
||||||
&& cp linux/spotube.desktop $(TEMP_DIR)\
|
&& cp linux/spotube.desktop $(TEMP_DIR)\
|
||||||
&& cp assets/spotube-logo.png $(TEMP_DIR)\
|
&& cp assets/branding/spotube-logo.png $(TEMP_DIR)\
|
||||||
&& cp linux/com.github.KRTirtho.Spotube.appdata.xml $(TEMP_DIR)\
|
&& cp linux/com.github.KRTirtho.Spotube.appdata.xml $(TEMP_DIR)\
|
||||||
&& tar -cJf build/spotube-linux-${VERSION}-${PKG_ARCH}.tar.xz -C $(TEMP_DIR) .\
|
&& tar -cJf build/spotube-linux-${VERSION}-${PKG_ARCH}.tar.xz -C $(TEMP_DIR) .\
|
||||||
&& rm -rf $(TEMP_DIR)
|
&& rm -rf $(TEMP_DIR)
|
||||||
@ -28,6 +28,7 @@ publishaur:
|
|||||||
|
|
||||||
innoinstall:
|
innoinstall:
|
||||||
powershell curl -o build\installer.exe http://files.jrsoftware.org/is/6/innosetup-${INNO_VERSION}.exe
|
powershell curl -o build\installer.exe http://files.jrsoftware.org/is/6/innosetup-${INNO_VERSION}.exe
|
||||||
|
powershell git clone https://github.com/DomGries/InnoDependencyInstaller.git build\inno-depend
|
||||||
powershell build\installer.exe /verysilent /allusers /dir=build\iscc
|
powershell build\installer.exe /verysilent /allusers /dir=build\iscc
|
||||||
|
|
||||||
inno:
|
inno:
|
||||||
@ -42,3 +43,16 @@ apk:
|
|||||||
|
|
||||||
gensums:
|
gensums:
|
||||||
sh -c scripts/gensums.sh
|
sh -c scripts/gensums.sh
|
||||||
|
|
||||||
|
migrate:
|
||||||
|
dart run drift_dev make-migrations
|
||||||
|
|
||||||
|
dmg:
|
||||||
|
flutter build macos &&\
|
||||||
|
if [ -f dist/Spotube-macos-universal.dmg ];\
|
||||||
|
then rm dist/Spotube-macos-universal.dmg;\
|
||||||
|
fi &&\
|
||||||
|
appdmg appdmg.json dist/Spotube-macos-universal.dmg
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
git-cliff --unreleased
|
208
README.md
@ -1,47 +1,41 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<img width="600" src="assets/spotube_banner.png" alt="Spotube Logo">
|
<img width="600" src="assets/branding/spotube_banner.png" alt="Spotube Logo">
|
||||||
|
|
||||||
An open source, cross-platform Spotify client compatible across multiple platforms<br />
|
A cross-platform extensible open-source music streaming platform.<br>
|
||||||
utilizing Spotify's data API and YouTube, Piped.video or JioSaavn as an audio source,<br />
|
Bring your own music metadata/playlist with plugins created by community or by yourself. A small step towards the decentralized music streaming era!
|
||||||
eliminating the need for Spotify Premium
|
|
||||||
|
|
||||||
Btw it's not just another Electron app 😉
|
Btw it's not just another Electron app 😉
|
||||||
|
|
||||||
<a href="https://spotube.netlify.app"><img alt="Visit the website" height="56" src="https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/documentation/website_vector.svg"></a>
|
<a href="https://spotube.krtirtho.dev"><img alt="Visit the website" height="56" src="https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/documentation/website_vector.svg"></a>
|
||||||
<a href="https://discord.gg/uJ94vxB6vg"><img alt="Discord Server" height="56" src="https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/social/discord-plural_vector.svg"></a>
|
<a href="https://discord.gg/uJ94vxB6vg"><img alt="Discord Server" height="56" src="https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/social/discord-plural_vector.svg"></a>
|
||||||
|
|
||||||
<a href="https://patreon.com/krtirtho"><img alt="Support me on Patron" height="56" src="https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/donate/patreon-singular_vector.svg"></a>
|
<a href="https://patreon.com/krtirtho"><img alt="Support me on Patron" height="56" src="https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/donate/patreon-singular_vector.svg"></a>
|
||||||
<a href="https://www.buymeacoffee.com/krtirtho"><img alt="Buy me a Coffee" height="56" src="https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/donate/buymeacoffee-singular_vector.svg"></a>
|
<a href="https://www.buymeacoffee.com/krtirtho"><img alt="Buy me a Coffee" height="56" src="https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/donate/buymeacoffee-singular_vector.svg"></a>
|
||||||
|
|
||||||
|
[](https://news.ycombinator.com/item?id=39066136)
|
||||||
|
|
||||||
<a href="https://opencollective.com/spotube"><img src="https://opencollective.com/spotube/donate/button.png?color=blue" alt="Donate to our Open Collective" height="45"></a>
|
<a href="https://opencollective.com/spotube"><img src="https://opencollective.com/spotube/donate/button.png?color=blue" alt="Donate to our Open Collective" height="45"></a>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 🌃 Features
|
## 🌃 Features
|
||||||
|
|
||||||
- 🚫 No ads, thanks to the use of public & free Spotify and YT Music APIs¹
|
- 🧩 Plugin powered, supports any platform or custom music service through plugins.
|
||||||
- ⬇️ Freely downloadable tracks
|
- 🗺️ Community driven plugins for popular platforms or create your own.
|
||||||
- 🖥️ 📱 Cross-platform support
|
- ⬇️ Freely downloadable tracks with tagged metadata.
|
||||||
- 🪶 Small size & less data usage
|
- 🖥️ 📱 Cross-platform support.
|
||||||
- 🕵️ Anonymous/guest login
|
- 🪶 Small size & less data usage.
|
||||||
- 🕒 Time synced lyrics
|
- 🕒 Time synced lyrics regardless of the plugin support.
|
||||||
- ✋ No telemetry, diagnostics or user data collection
|
- ✋ No telemetry, diagnostics or user data collection.
|
||||||
- 🚀 Native performance
|
- 🚀 Native performance.
|
||||||
- 📖 Open source/libre software
|
- 📖 Open source/libre software.
|
||||||
- 🔉 Playback control is done locally, not on the server
|
- 🔉 Playback control is done locally, not on the server.
|
||||||
|
|
||||||
**¹** It is still **recommended** to support creators by engaging with their YouTube channels/Spotify tracks (or preferably by buying their merch/concert tickets/physical media).
|
|
||||||
|
|
||||||
### ❌ Unsupported features
|
|
||||||
|
|
||||||
- 🗣️ **Spotify Shows & Podcasts:** Shows and Podcasts will <ins>**never be supported**</ins> because the audio tracks are <ins>_only_</ins> available on Spotify and accessing them would require Spotify Premium.
|
|
||||||
- 🎧 **Spotify Listen Along:** [Coming soon!](https://github.com/KRTirtho/spotube/issues/8)
|
|
||||||
|
|
||||||
## 📜 ⬇️ Installation guide
|
## 📜 ⬇️ Installation guide
|
||||||
|
|
||||||
@ -64,17 +58,13 @@ This handy table lists all the methods you can use to install Spotube:
|
|||||||
<td>MacOS</td>
|
<td>MacOS</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-macos-universal.dmg">
|
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-macos-universal.dmg">
|
||||||
<img width="220" alt="MacOS Download" src="https://reachify.io/wp-content/uploads/2018/09/mac-download-button-1.png">
|
<img width="220" alt="MacOS Download" src="https://memory-map.com/wp-content/uploads/download-mac-OS-01.svg">
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Android</td>
|
<td>Android</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://play.google.com/store/apps/details?id=oss.krtirtho.spotube">
|
|
||||||
<img width="220" alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png">
|
|
||||||
</a>
|
|
||||||
<br>
|
|
||||||
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-android-all-arch.apk">
|
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-android-all-arch.apk">
|
||||||
<img width="220" alt="APK download" src="https://user-images.githubusercontent.com/114044633/223920025-83687de0-e463-4c5d-8122-e06e4bb7d40c.png">
|
<img width="220" alt="APK download" src="https://user-images.githubusercontent.com/114044633/223920025-83687de0-e463-4c5d-8122-e06e4bb7d40c.png">
|
||||||
</a>
|
</a>
|
||||||
@ -84,6 +74,19 @@ This handy table lists all the methods you can use to install Spotube:
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<tr>
|
||||||
|
<td>iOS</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-iOS.ipa">
|
||||||
|
<img width="220" alt="Download iOS IPA" src="https://github.com/user-attachments/assets/3e50d93d-fb39-435c-be6b-337745f7c423">
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
<blockquote style="color:red">
|
||||||
|
*iPA file only. Requires sideloading with <a href="https://altstore.io/">AltStore</a> or similar tools.
|
||||||
|
</blockquote>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Flatpak</td>
|
<td>Flatpak</td>
|
||||||
<td>
|
<td>
|
||||||
@ -95,12 +98,7 @@ This handy table lists all the methods you can use to install Spotube:
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>AppImage</td>
|
<td>AppImage</td>
|
||||||
<td>
|
<td>AppImage's lacking stability led to it's temporary removal. More information at https://github.com/KRTirtho/spotube/issues/1082</td>
|
||||||
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-linux-x86_64.AppImage">
|
|
||||||
<img width="220" alt="Download AppImage" src="https://user-images.githubusercontent.com/61944859/169455015-13385466-8901-48fe-ba90-b62d58b0be64.png">
|
|
||||||
</a>
|
|
||||||
<p><b>Note:</b> <a href="https://github.com/TheAssassin/AppImageLauncher">AppimageLauncher</a> is required!</p>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Debian/Ubuntu</td>
|
<td>Debian/Ubuntu</td>
|
||||||
@ -136,6 +134,15 @@ This handy table lists all the methods you can use to install Spotube:
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Macos - <a href="https://brew.sh">Homebrew</a></td>
|
||||||
|
<td>
|
||||||
|
<pre lang="bash">
|
||||||
|
brew tap krtirtho/apps
|
||||||
|
brew install --cask spotube
|
||||||
|
</pre>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Windows - <a href="https://chocolatey.org">Chocolatey</a></td>
|
<td>Windows - <a href="https://chocolatey.org">Chocolatey</a></td>
|
||||||
<td>
|
<td>
|
||||||
@ -170,9 +177,7 @@ You can compile Spotube's source code by [following these instructions](CONTRIBU
|
|||||||
## 👥 The Spotube team
|
## 👥 The Spotube team
|
||||||
|
|
||||||
- [Kingkor Roy Tirtho](https://github.com/KRTirtho) - The Founder, Maintainer and Lead Developer
|
- [Kingkor Roy Tirtho](https://github.com/KRTirtho) - The Founder, Maintainer and Lead Developer
|
||||||
- [RaptaG](https://github.com/RaptaG) - The GitHub Moderator and Community Manager
|
|
||||||
- [Owen Connor](https://github.com/owencz1998) - The Cool Discord Moderator
|
- [Owen Connor](https://github.com/owencz1998) - The Cool Discord Moderator
|
||||||
- [Meenbeese](https://github.com/meenbeese) - The Android Developer
|
|
||||||
- [Piotr Rogowski](https://github.com/karniv00l) - The MacOS Developer
|
- [Piotr Rogowski](https://github.com/karniv00l) - The MacOS Developer
|
||||||
- [Rusty Apple](https://github.com/RustyApple) - The Mysterious Unknown Guy
|
- [Rusty Apple](https://github.com/RustyApple) - The Mysterious Unknown Guy
|
||||||
|
|
||||||
@ -180,7 +185,7 @@ You can compile Spotube's source code by [following these instructions](CONTRIBU
|
|||||||
|
|
||||||
Spotube is open source and licensed under the [BSD-4-Clause](/LICENSE) License.
|
Spotube is open source and licensed under the [BSD-4-Clause](/LICENSE) License.
|
||||||
|
|
||||||
If you are concerned, you can [read the reason of choosing this license](https://dev.to/krtirtho/choosing-open-source-license-wisely-1m3p).
|
If you are curious, you can [read the reason of choosing this license](https://dev.to/krtirtho/choosing-open-source-license-wisely-1m3p).
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>
|
<summary>
|
||||||
@ -188,11 +193,17 @@ If you are concerned, you can [read the reason of choosing this license](https:/
|
|||||||
</summary>
|
</summary>
|
||||||
|
|
||||||
### Services
|
### Services
|
||||||
|
|
||||||
1. [Flutter](https://flutter.dev) - Flutter transforms the app development process. Build, test, and deploy beautiful mobile, web, desktop, and embedded apps from a single codebase
|
1. [Flutter](https://flutter.dev) - Flutter transforms the app development process. Build, test, and deploy beautiful mobile, web, desktop, and embedded apps from a single codebase
|
||||||
1. [Spotify API](https://developer.spotify.com/documentation/web-api) - The Spotify Web API is a RESTful API that provides access to Spotify data
|
1. [MPV](https://mpv.io) - mpv is a free (as in freedom) media player for the command line. It supports a wide variety of media file formats, audio and video codecs, and subtitle types.
|
||||||
|
1. [Musicbrainz](https://musicbrainz.org) - MusicBrainz is a MetaBrainz project that aims to create a collaborative music database that is similar to the freedb project.
|
||||||
|
1. [Listenbrainz](https://listenbrainz.org) - ListenBrainz is a open-source project by the MetaBrainz Foundation that allows users to crowdsource and publicly store their digital music listening data.
|
||||||
1. [Piped](https://piped-docs.kavin.rocks/) - Piped is a privacy friendly alternative YouTube frontend, which is efficient and scalable by design.
|
1. [Piped](https://piped-docs.kavin.rocks/) - Piped is a privacy friendly alternative YouTube frontend, which is efficient and scalable by design.
|
||||||
1. [YouTube](https://youtube.com/) - YouTube is an American online video-sharing platform headquartered in San Bruno, California. Three former PayPal employees—Chad Hurley, Steve Chen, and Jawed Karim—created the service in February 2005
|
1. [Invidious](https://invidious.io/) - Invidious is an open source alternative front-end to YouTube.
|
||||||
1. [JioSaavn](https://www.jiosaavn.com) - JioSaavn is an Indian online music streaming service and a digital distributor of Bollywood, English and other regional Indian music across the world. Since it was founded in 2007 as Saavn, the company has acquired rights to over 5 crore (50 million) music tracks in 15 languages
|
1. [yt-dlp](https://github.com/yt-dlp/yt-dlp) - A feature-rich command-line audio/video downloader.
|
||||||
|
1. [NewPipeExtractor](https://github.com/TeamNewPipe/NewPipeExtractor) - NewPipe's core library for extracting data from streaming sites.
|
||||||
|
1. [SongLink](https://song.link) - SongLink is a free smart link service that helps you share music with your audience. It's a one-stop-shop for creating smart links for music, podcasts, and other audio content
|
||||||
|
1. [LRCLib](https://lrclib.net/) - A public synced lyric API.
|
||||||
1. [Linux](https://www.linux.org) - Linux is a family of open-source Unix-like operating systems based on the Linux kernel, an operating system kernel first released on September 17, 1991, by Linus Torvalds. Linux is typically packaged in a Linux distribution
|
1. [Linux](https://www.linux.org) - Linux is a family of open-source Unix-like operating systems based on the Linux kernel, an operating system kernel first released on September 17, 1991, by Linus Torvalds. Linux is typically packaged in a Linux distribution
|
||||||
1. [AUR](https://aur.archlinux.org) - AUR stands for Arch User Repository. It is a community-driven repository for Arch-based Linux distributions users
|
1. [AUR](https://aur.archlinux.org) - AUR stands for Arch User Repository. It is a community-driven repository for Arch-based Linux distributions users
|
||||||
1. [Flatpak](https://flatpak.org) - Flatpak is a utility for software deployment and package management for Linux
|
1. [Flatpak](https://flatpak.org) - Flatpak is a utility for software deployment and package management for Linux
|
||||||
@ -202,106 +213,125 @@ If you are concerned, you can [read the reason of choosing this license](https:/
|
|||||||
1. [LastFM](https://last.fm) - Last.fm is a music streaming and discovery platform that helps users discover and share new music. It tracks users' music listening habits across many devices and platforms.
|
1. [LastFM](https://last.fm) - Last.fm is a music streaming and discovery platform that helps users discover and share new music. It tracks users' music listening habits across many devices and platforms.
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
|
1. [app_links](https://github.com/llfbandit/app_links) - Android App Links, Deep Links, iOs Universal Links and Custom URL schemes handler for Flutter (desktop included).
|
||||||
1. [args](https://pub.dev/packages/args) - Library for defining parsers for parsing raw command-line arguments into a set of options and values using GNU and POSIX style options.
|
1. [args](https://pub.dev/packages/args) - Library for defining parsers for parsing raw command-line arguments into a set of options and values using GNU and POSIX style options.
|
||||||
1. [async](https://pub.dev/packages/async) - Utility functions and classes related to the 'dart:async' library.
|
1. [async](https://pub.dev/packages/async) - Utility functions and classes related to the 'dart:async' library.
|
||||||
1. [audio_service](https://pub.dev/packages/audio_service) - Flutter plugin to play audio in the background while the screen is off.
|
1. [audio_service](https://pub.dev/packages/audio_service) - Flutter plugin to play audio in the background while the screen is off.
|
||||||
|
1. [audio_service_mpris](https://github.com/bdrazhzhov/audio-service-mpris) - audio_service platform interface supporting Media Player Remote Interfacing Specification.
|
||||||
1. [audio_session](https://github.com/ryanheise/audio_session) - Sets the iOS audio session category and Android audio attributes for your app, and manages your app's audio focus, mixing and ducking behaviour.
|
1. [audio_session](https://github.com/ryanheise/audio_session) - Sets the iOS audio session category and Android audio attributes for your app, and manages your app's audio focus, mixing and ducking behaviour.
|
||||||
|
1. [auto_route](https://github.com/Milad-Akarie/auto_route_library) - AutoRoute is a declarative routing solution, where everything needed for navigation is automatically generated for you.
|
||||||
1. [auto_size_text](https://github.com/leisim/auto_size_text) - Flutter widget that automatically resizes text to fit perfectly within its bounds.
|
1. [auto_size_text](https://github.com/leisim/auto_size_text) - Flutter widget that automatically resizes text to fit perfectly within its bounds.
|
||||||
1. [buttons_tabbar](https://afonsoraposo.com) - A Flutter package that implements a TabBar where each label is a toggle button.
|
1. [bonsoir](https://bonsoir.skyost.eu) - A Zeroconf library that allows you to discover network services and to broadcast your own. Based on Apple Bonjour and Android NSD.
|
||||||
1. [cached_network_image](https://github.com/Baseflow/flutter_cached_network_image) - Flutter library to load and cache network images. Can also be used with placeholder and error widgets.
|
1. [cached_network_image](https://github.com/Baseflow/flutter_cached_network_image) - Flutter library to load and cache network images. Can also be used with placeholder and error widgets.
|
||||||
1. [catcher_2](https://github.com/ThexXTURBOXx/catcher_2) - Plugin for error catching which provides multiple handlers for dealing with errors when they are not caught by the developer.
|
1. [connectivity_plus](https://github.com/fluttercommunity/plus_plugins) - Flutter plugin for discovering the state of the network (WiFi & mobile/cellular) connectivity on Android and iOS.
|
||||||
1. [collection](https://pub.dev/packages/collection) - Collections and utilities functions and classes related to collections.
|
1. [device_info_plus](https://github.com/fluttercommunity/plus_plugins) - Flutter plugin providing detailed information about the device (make, model, etc.), and Android or iOS version the app is running on.
|
||||||
1. [cupertino_icons](https://pub.dev/packages/cupertino_icons) - Default icons asset for Cupertino widgets based on Apple styled icons
|
|
||||||
1. [curved_navigation_bar](https://github.com/rafalbednarczuk/curved_navigation_bar) - Stunning Animating Curved Shape Navigation Bar. Adjustable color, background color, animation curve, animation duration.
|
|
||||||
1. [dbus](https://github.com/canonical/dbus.dart) - A native Dart implementation of the D-Bus message bus client. This package allows Dart applications to directly access services on the Linux desktop.
|
|
||||||
1. [device_info_plus](https://plus.fluttercommunity.dev/) - Flutter plugin providing detailed information about the device (make, model, etc.), and Android or iOS version the app is running on.
|
|
||||||
1. [device_preview](https://github.com/aloisdeniel/flutter_device_preview) - Approximate how your Flutter app looks and performs on another device.
|
|
||||||
1. [dio](https://github.com/cfug/dio) - A powerful HTTP networking package,supports Interceptors,Aborting and canceling a request,Custom adapters, Transformers, etc.
|
1. [dio](https://github.com/cfug/dio) - A powerful HTTP networking package,supports Interceptors,Aborting and canceling a request,Custom adapters, Transformers, etc.
|
||||||
1. [disable_battery_optimization](https://github.com/pvsvamsi/Disable-Battery-Optimizations) - Flutter plugin to check and disable battery optimizations. Also shows custom steps to disable the optimizations in devices like mi, xiaomi, samsung, oppo, huawei, oneplus etc
|
1. [drift](https://drift.simonbinder.eu/) - Drift is a reactive library to store relational data in Dart and Flutter applications.
|
||||||
1. [duration](https://github.com/desktop-dart/duration) - Utilities to make working with 'Duration's easier. Formats duration in human readable form and also parses duration in human readable form to Dart's Duration.
|
1. [duration](https://github.com/desktop-dart/duration) - Utilities to make working with 'Duration's easier. Formats duration in human readable form and also parses duration in human readable form to Dart's Duration.
|
||||||
|
1. [encrypt](https://pub.dev/packages/encrypt) - A set of high-level APIs over PointyCastle for two-way cryptography.
|
||||||
1. [envied](https://github.com/petercinibulk/envied) - Explicitly reads environment variables into a dart file from a .env file for more security and faster start up times.
|
1. [envied](https://github.com/petercinibulk/envied) - Explicitly reads environment variables into a dart file from a .env file for more security and faster start up times.
|
||||||
|
1. [file_picker](https://github.com/miguelpruivo/plugins_flutter_file_picker) - A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extension filtering support.
|
||||||
1. [file_selector](https://pub.dev/packages/file_selector) - Flutter plugin for opening and saving files, or selecting directories, using native file selection UI.
|
1. [file_selector](https://pub.dev/packages/file_selector) - Flutter plugin for opening and saving files, or selecting directories, using native file selection UI.
|
||||||
1. [fl_query](https://fl-query.krtirtho.dev) - Asynchronous data caching, refetching & invalidation library for Flutter
|
|
||||||
1. [fl_query_hooks](https://fl-query.krtirtho.dev) - Elite flutter_hooks compatible library for fl_query, the Asynchronous data caching, refetching & invalidation library for Flutter
|
|
||||||
1. [fl_query_devtools](https://fl-query.krtirtho.dev) - Devtools support for Fl-Query
|
|
||||||
1. [fluentui_system_icons](https://github.com/microsoft/fluentui-system-icons/tree/main) - Fluent UI System Icons are a collection of familiar, friendly and modern icons from Microsoft.
|
1. [fluentui_system_icons](https://github.com/microsoft/fluentui-system-icons/tree/main) - Fluent UI System Icons are a collection of familiar, friendly and modern icons from Microsoft.
|
||||||
1. [flutter_cache_manager](https://github.com/Baseflow/flutter_cache_manager/tree/develop/flutter_cache_manager) - Generic cache manager for flutter. Saves web files on the storages of the device and saves the cache info using sqflite.
|
1. [flutter_cache_manager](https://github.com/Baseflow/flutter_cache_manager/tree/develop/flutter_cache_manager) - Generic cache manager for flutter. Saves web files on the storages of the device and saves the cache info using sqflite.
|
||||||
|
1. [flutter_discord_rpc](https://pub.dev/packages/flutter_discord_rpc) - Discord RPC support for Flutter desktop platforms
|
||||||
1. [flutter_displaymode](https://github.com/ajinasokan/flutter_displaymode) - A Flutter plugin to set display mode (resolution, refresh rate) on Android platform. Allows to enable high refresh rate on supported devices.
|
1. [flutter_displaymode](https://github.com/ajinasokan/flutter_displaymode) - A Flutter plugin to set display mode (resolution, refresh rate) on Android platform. Allows to enable high refresh rate on supported devices.
|
||||||
1. [flutter_feather_icons](https://github.com/muj-programmer/flutter_feather_icons) - Feather is a collection of simply beautiful open source icons. Each icon is designed on a 24x24 grid with an emphasis on simplicity, consistency and usability.
|
1. [flutter_feather_icons](https://github.com/muj-programmer/flutter_feather_icons) - Feather is a collection of simply beautiful open source icons. Each icon is designed on a 24x24 grid with an emphasis on simplicity, consistency and usability.
|
||||||
|
1. [flutter_form_builder](https://github.com/flutter-form-builder-ecosystem) - This package helps in creation of forms in Flutter by removing the boilerplate code, reusing validation, react to changes, and collect final user input.
|
||||||
1. [flutter_hooks](https://github.com/rrousselGit/flutter_hooks) - A flutter implementation of React hooks. It adds a new kind of widget with enhanced code reuse.
|
1. [flutter_hooks](https://github.com/rrousselGit/flutter_hooks) - A flutter implementation of React hooks. It adds a new kind of widget with enhanced code reuse.
|
||||||
1. [flutter_inappwebview](https://inappwebview.dev/) - A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window.
|
1. [flutter_inappwebview](https://inappwebview.dev/) - A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window.
|
||||||
1. [flutter_native_splash](https://pub.dev/packages/flutter_native_splash) - Customize Flutter's default white native splash screen with background color and splash image. Supports dark mode, full screen, and more.
|
1. [flutter_native_splash](https://pub.dev/packages/flutter_native_splash) - Customize Flutter's default white native splash screen with background color and splash image. Supports dark mode, full screen, and more.
|
||||||
1. [flutter_riverpod](https://riverpod.dev) - A simple way to access state from anywhere in your application while robust and testable.
|
1. [flutter_riverpod](https://riverpod.dev) - A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
|
||||||
1. [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage) - Flutter Secure Storage provides API to store data in secure storage. Keychain is used in iOS, KeyStore based solution is used in Android.
|
1. [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage) - Flutter Secure Storage provides API to store data in secure storage. Keychain is used in iOS, KeyStore based solution is used in Android.
|
||||||
1. [flutter_svg](https://pub.dev/packages/flutter_svg) - An SVG rendering and widget library for Flutter, which allows painting and displaying Scalable Vector Graphics 1.1 files.
|
1. [flutter_sharing_intent](https://github.com/bhagat-techind/flutter_sharing_intent.git) - A flutter plugin that allow flutter apps to receive photos, videos, text, urls or any other file types from another app.
|
||||||
|
1. [flutter_undraw](https://github.com/KRTirtho/flutter_undraw) - Undraw.co Illustrations for Flutter with customization options
|
||||||
|
1. [form_builder_validators](https://github.com/flutter-form-builder-ecosystem) - Form Builder Validators set of validators for FlutterFormBuilder. Provides common validators and a way to make your own.
|
||||||
1. [form_validator](https://github.com/TheMisir/form-validator) - Simplest form validation library for flutter's form field widgets
|
1. [form_validator](https://github.com/TheMisir/form-validator) - Simplest form validation library for flutter's form field widgets
|
||||||
|
1. [freezed_annotation](https://pub.dev/packages/freezed_annotation) - Annotations for the freezed code-generator. This package does nothing without freezed too.
|
||||||
1. [fuzzywuzzy](https://github.com/sphericalkat/dart-fuzzywuzzy) - An implementation of the popular fuzzywuzzy package in Dart, to suit all your fuzzy string matching/searching needs!
|
1. [fuzzywuzzy](https://github.com/sphericalkat/dart-fuzzywuzzy) - An implementation of the popular fuzzywuzzy package in Dart, to suit all your fuzzy string matching/searching needs!
|
||||||
1. [go_router](https://pub.dev/packages/go_router) - A declarative router for Flutter based on Navigation 2 supporting deep linking, data-driven routes and more
|
1. [gap](https://github.com/letsar/gap) - Flutter widgets for easily adding gaps inside Flex widgets such as Columns and Rows or scrolling views.
|
||||||
1. [google_fonts](https://pub.dev/packages/google_fonts) - A Flutter package to use fonts from fonts.google.com. Supports HTTP fetching, caching, and asset bundling.
|
1. [google_fonts](https://pub.dev/packages/google_fonts) - A Flutter package to use fonts from fonts.google.com. Supports HTTP fetching, caching, and asset bundling.
|
||||||
1. [hive](https://github.com/hivedb/hive/tree/master/hive) - Lightweight and blazing fast key-value database written in pure Dart. Strongly encrypted using AES-256.
|
1. [home_widget](https://pub.dev/packages/home_widget) - A plugin to provide a common interface for creating HomeScreen Widgets for Android and iOS.
|
||||||
1. [hive_flutter](https://github.com/hivedb/hive/tree/master/hive_flutter) - Extension for Hive. Makes it easier to use Hive in Flutter apps.
|
1. [hooks_riverpod](https://riverpod.dev) - A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
|
||||||
1. [hooks_riverpod](https://riverpod.dev) - A simple way to access state from anywhere in your application while robust and testable.
|
|
||||||
1. [html](https://pub.dev/packages/html) - APIs for parsing and manipulating HTML content outside the browser.
|
1. [html](https://pub.dev/packages/html) - APIs for parsing and manipulating HTML content outside the browser.
|
||||||
|
1. [html_unescape](https://github.com/filiph/html_unescape) - A small library for un-escaping HTML. Supports all Named Character References, Decimal Character References and Hexadecimal Character References.
|
||||||
1. [http](https://pub.dev/packages/http) - A composable, multi-platform, Future-based API for HTTP requests.
|
1. [http](https://pub.dev/packages/http) - A composable, multi-platform, Future-based API for HTTP requests.
|
||||||
1. [image_picker](https://pub.dev/packages/image_picker) - Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera.
|
1. [image_picker](https://pub.dev/packages/image_picker) - Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera.
|
||||||
1. [intl](https://pub.dev/packages/intl) - Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and other internationalization issues.
|
1. [intl](https://pub.dev/packages/intl) - Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and other internationalization issues.
|
||||||
1. [introduction_screen](https://github.com/pyozer/introduction_screen) - Introduction/Onboarding package for flutter app with some customizations possibilities
|
1. [invidious](https://pub.dev/packages/invidious) - Invidious API client for Dart and Flutter.
|
||||||
|
1. [jiosaavn](https://github.com/KRTirtho/jiosaavn) - Unofficial API client for jiosaavn.com
|
||||||
1. [json_annotation](https://pub.dev/packages/json_annotation) - Classes and helper functions that support JSON code generation via the `json_serializable` package.
|
1. [json_annotation](https://pub.dev/packages/json_annotation) - Classes and helper functions that support JSON code generation via the `json_serializable` package.
|
||||||
|
1. [local_notifier](https://github.com/leanflutter/local_notifier) - This plugin allows Flutter desktop apps to displaying local notifications.
|
||||||
1. [logger](https://pub.dev/packages/logger) - Small, easy to use and extensible logger which prints beautiful logs.
|
1. [logger](https://pub.dev/packages/logger) - Small, easy to use and extensible logger which prints beautiful logs.
|
||||||
|
1. [logging](https://pub.dev/packages/logging) - Provides APIs for debugging and error logging, similar to loggers in other languages, such as the Closure JS Logger and java.util.logging.Logger.
|
||||||
|
1. [lrc](https://pub.dev/packages/lrc) - A Dart-only package that creates, parses, and handles LRC, which is a format that stores song lyrics.
|
||||||
1. [media_kit](https://github.com/media-kit/media-kit) - A cross-platform video player & audio player for Flutter & Dart. Performant, stable, feature-proof & modular.
|
1. [media_kit](https://github.com/media-kit/media-kit) - A cross-platform video player & audio player for Flutter & Dart. Performant, stable, feature-proof & modular.
|
||||||
1. [media_kit_libs_audio](https://github.com/media-kit/media-kit.git) - package:media_kit audio (only) playback native libraries for all platforms.
|
1. [media_kit_libs_audio](https://github.com/media-kit/media-kit.git) - package:media_kit audio (only) playback native libraries for all platforms.
|
||||||
1. [metadata_god](https://github.com/KRTirtho/metadata_god) - Plugin for retrieving and writing audio tags/metadata from audio files
|
1. [metadata_god](https://pub.dev/packages/metadata_god) - Plugin for retrieving and writing audio tags/metadata from audio files
|
||||||
1. [mime](https://pub.dev/packages/mime) - Utilities for handling media (MIME) types, including determining a type from a file extension and file contents.
|
1. [mime](https://pub.dev/packages/mime) - Utilities for handling media (MIME) types, including determining a type from a file extension and file contents.
|
||||||
1. [package_info_plus](https://plus.fluttercommunity.dev/) - Flutter plugin for querying information about the application package, such as CFBundleVersion on iOS or versionCode on Android.
|
1. [open_file](https://pub.dev/packages/open_file) - A plug-in that can call native APP to open files with string result in flutter, support iOS(UTI) / android(intent) / PC(ffi) / web(dart:html)
|
||||||
|
1. [package_info_plus](https://github.com/fluttercommunity/plus_plugins) - Flutter plugin for querying information about the application package, such as CFBundleVersion on iOS or versionCode on Android.
|
||||||
1. [palette_generator](https://pub.dev/packages/palette_generator) - Flutter package for generating palette colors from a source image.
|
1. [palette_generator](https://pub.dev/packages/palette_generator) - Flutter package for generating palette colors from a source image.
|
||||||
1. [path](https://pub.dev/packages/path) - A string-based path manipulation library. All of the path operations you know and love, with solid support for Windows, POSIX (Linux and Mac OS X), and the web.
|
1. [path](https://pub.dev/packages/path) - A string-based path manipulation library. All of the path operations you know and love, with solid support for Windows, POSIX (Linux and Mac OS X), and the web.
|
||||||
1. [path_provider](https://pub.dev/packages/path_provider) - Flutter plugin for getting commonly used locations on host platform file systems, such as the temp and app data directories.
|
1. [path_provider](https://pub.dev/packages/path_provider) - Flutter plugin for getting commonly used locations on host platform file systems, such as the temp and app data directories.
|
||||||
1. [permission_handler](https://pub.dev/packages/permission_handler) - Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.
|
1. [permission_handler](https://pub.dev/packages/permission_handler) - Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.
|
||||||
1. [piped_client](https://github.com/KRTirtho/piped_client) - API Client for piped.video
|
1. [piped_client](https://github.com/KRTirtho/piped_client) - API Client for piped.video
|
||||||
1. [popover](https://github.com/minikin/popover) - A popover is a transient view that appears above other content onscreen when you tap a control or in an area.
|
1. [riverpod](https://riverpod.dev) - A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
|
||||||
1. [scroll_to_index](https://github.com/quire-io/scroll-to-index) - Scroll to a specific child of any scrollable widget in Flutter
|
1. [scroll_to_index](https://github.com/quire-io/scroll-to-index) - Scroll to a specific child of any scrollable widget in Flutter
|
||||||
1. [sidebarx](https://github.com/Frezyx/sidebarx) - flutter multiplatform navigation sidebar / side navigationbar / drawer widget
|
1. [shadcn_flutter](https://github.com/sunarya-thito/shadcn_flutter) - Beautifully designed components from Shadcn/UI is now available for Flutter
|
||||||
1. [shared_preferences](https://pub.dev/packages/shared_preferences) - Flutter plugin for reading and writing simple key-value pairs. Wraps NSUserDefaults on iOS and SharedPreferences on Android.
|
1. [shared_preferences](https://pub.dev/packages/shared_preferences) - Flutter plugin for reading and writing simple key-value pairs. Wraps NSUserDefaults on iOS and SharedPreferences on Android.
|
||||||
1. [skeleton_text](https://github.com/101Loop/Skeleton-Text) - A package that provides an easy way to add skeleton text loading animation in Flutter project. This project is a part of 101Loop community.
|
1. [shelf](https://pub.dev/packages/shelf) - A model for web server middleware that encourages composition and easy reuse.
|
||||||
1. [smtc_windows](https://github.com/KRTirtho/smtc_windows) - Windows `SystemMediaTransportControls` implementation for Flutter giving access to Windows OS Media Control applet.
|
1. [shelf_router](https://pub.dev/packages/shelf_router) - A convenient request router for the shelf web-framework, with support for URL-parameters, nested routers and routers generated from source annotations.
|
||||||
1. [spotify](https://github.com/rinukkusu/spotify-dart) - An incomplete dart library for interfacing with the Spotify Web API.
|
1. [shelf_web_socket](https://pub.dev/packages/shelf_web_socket) - A shelf handler that wires up a listener for every connection.
|
||||||
|
1. [simple_icons](https://teavelopment.com/) - The Simple Icon pack available as Flutter Icons. Provides over 1500 Free SVG icons for popular brands.
|
||||||
|
1. [skeletonizer](https://github.com/Milad-Akarie/skeletonizer) - Converts already built widgets into skeleton loaders with no extra effort.
|
||||||
|
1. [sliding_up_panel](https://github.com/akshathjain/sliding_up_panel) - A draggable Flutter widget that makes implementing a SlidingUpPanel much easier!
|
||||||
|
1. [sliver_tools](https://github.com/Kavantix) - A set of useful sliver tools that are missing from the flutter framework
|
||||||
|
1. [smtc_windows](https://pub.dev/packages/smtc_windows) - Windows `SystemMediaTransportControls` implementation for Flutter giving access to Windows OS Media Control applet.
|
||||||
|
1. [sqlite3](https://github.com/simolus3/sqlite3.dart/tree/main/sqlite3) - Provides lightweight yet convenient bindings to SQLite by using dart:ffi
|
||||||
|
1. [sqlite3_flutter_libs](https://github.com/simolus3/sqlite3.dart/tree/main/sqlite3_flutter_libs) - Flutter plugin to include native sqlite3 libraries with your app
|
||||||
1. [stroke_text](https://github.com/MohamedAbd0/stroke_text) - A Simple Flutter plugin for applying stroke (border) style to a text widget
|
1. [stroke_text](https://github.com/MohamedAbd0/stroke_text) - A Simple Flutter plugin for applying stroke (border) style to a text widget
|
||||||
1. [system_theme](https://pub.dev/packages/system_theme) - A plugin to get the current system theme info. Supports Android, Web, Windows, Linux and macOS
|
1. [system_theme](https://github.com/bdlukaa/system_theme/tree/master/system_theme) - A plugin to get the current system theme info. Supports Android, Web, Windows, Linux and macOS
|
||||||
|
1. [test](https://pub.dev/packages/test) - A full featured library for writing and running Dart tests across platforms.
|
||||||
|
1. [timezone](https://pub.dev/packages/timezone) - Time zone database and time zone aware DateTime.
|
||||||
1. [titlebar_buttons](https://github.com/gtk-flutter/titlebar_buttons) - A package which provides most of the titlebar buttons from windows, linux and macos.
|
1. [titlebar_buttons](https://github.com/gtk-flutter/titlebar_buttons) - A package which provides most of the titlebar buttons from windows, linux and macos.
|
||||||
|
1. [tray_manager](https://github.com/leanflutter/tray_manager) - This plugin allows Flutter desktop apps to defines system tray.
|
||||||
1. [url_launcher](https://pub.dev/packages/url_launcher) - Flutter plugin for launching a URL. Supports web, phone, SMS, and email schemes.
|
1. [url_launcher](https://pub.dev/packages/url_launcher) - Flutter plugin for launching a URL. Supports web, phone, SMS, and email schemes.
|
||||||
1. [uuid](https://pub.dev/packages/uuid) - RFC4122 (v1, v4, v5, v6, v7, v8) UUID Generator and Parser for Dart
|
1. [uuid](https://pub.dev/packages/uuid) - RFC4122 (v1, v4, v5, v6, v7, v8) UUID Generator and Parser for Dart
|
||||||
1. [version](https://github.com/dartninja/version) - Provides a simple class for parsing and comparing semantic versions as defined by http://semver.org/
|
1. [version](https://github.com/dartninja/version) - Provides a simple class for parsing and comparing semantic versions as defined by http://semver.org/
|
||||||
|
1. [very_good_infinite_list](https://github.com/VeryGoodOpenSource/very_good_infinite_list) - A library for easily displaying paginated data, created by Very Good Ventures. Great for activity feeds, news feeds, and more.
|
||||||
1. [visibility_detector](https://pub.dev/packages/visibility_detector) - A widget that detects the visibility of its child and notifies a callback.
|
1. [visibility_detector](https://pub.dev/packages/visibility_detector) - A widget that detects the visibility of its child and notifies a callback.
|
||||||
|
1. [web_socket_channel](https://pub.dev/packages/web_socket_channel) - StreamChannel wrappers for WebSockets. Provides a cross-platform WebSocketChannel API, a cross-platform implementation of that API that communicates over an underlying StreamChannel.
|
||||||
|
1. [wikipedia_api](https://github.com/KRTirtho/wikipedia_api) - Wikipedia API for dart and flutter
|
||||||
|
1. [win32_registry](https://pub.dev/packages/win32_registry) - A package that provides a friendly Dart API for accessing the Windows Registry.
|
||||||
1. [window_manager](https://github.com/leanflutter/window_manager) - This plugin allows Flutter desktop apps to resizing and repositioning the window.
|
1. [window_manager](https://github.com/leanflutter/window_manager) - This plugin allows Flutter desktop apps to resizing and repositioning the window.
|
||||||
1. [youtube_explode_dart](https://github.com/Hexer10/youtube_explode_dart) - A port in dart of the youtube explode library. Supports several API functions without the need of Youtube API Key.
|
1. [youtube_explode_dart](https://github.com/Hexer10/youtube_explode_dart) - A port in dart of the youtube explode library. Supports several API functions without the need of Youtube API Key.
|
||||||
1. [simple_icons](https://teavelopment.com/) - The Simple Icon pack available as Flutter Icons. Provides over 1500 Free SVG icons for popular brands.
|
1. [http_parser](https://pub.dev/packages/http_parser) - A platform-independent package for parsing and serializing HTTP formats.
|
||||||
1. [audio_service_mpris](https://github.com/bdrazhzhov/audio-service-mpris) - audio_service platform interface supporting Media Player Remote Interfacing Specification.
|
1. [collection](https://pub.dev/packages/collection) - Collections and utilities functions and classes related to collections.
|
||||||
1. [file_picker](https://github.com/miguelpruivo/plugins_flutter_file_picker) - A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extension filtering support.
|
1. [otp_util](https://github.com/dushiling) - otp_util is a dart package to generate and verify one-time passwords,it It provides two methods TOPT and HOTP.They are Time-based OTPs and Counter-based OTPs.
|
||||||
1. [jiosaavn](https://github.com/KRTirtho/jiosaavn) - Unofficial API client for jiosaavn.com
|
1. [dio_http2_adapter](https://github.com/cfug/dio) - An adapter that combines HTTP/2 and dio. Supports reusing connections, header compression, etc.
|
||||||
1. [very_good_infinite_list](https://github.com/VeryGoodOpenSource/very_good_infinite_list) - A library for easily displaying paginated data, created by Very Good Ventures. Great for activity feeds, news feeds, and more.
|
|
||||||
1. [gap](https://github.com/letsar/gap) - Flutter widgets for easily adding gaps inside Flex widgets such as Columns and Rows or scrolling views.
|
|
||||||
1. [sliver_tools](https://github.com/Kavantix) - A set of useful sliver tools that are missing from the flutter framework
|
|
||||||
1. [html_unescape](https://github.com/filiph/html_unescape) - A small library for un-escaping HTML. Supports all Named Character References, Decimal Character References and Hexadecimal Character References.
|
|
||||||
1. [wikipedia_api](https://github.com/KRTirtho/wikipedia_api) - Wikipedia API for dart and flutter
|
|
||||||
1. [skeletonizer](https://github.com/Milad-Akarie/skeletonizer) - Converts already built widgets into skeleton loaders with no extra effort.
|
|
||||||
1. [app_links](https://github.com/llfbandit/app_links) - Android App Links, Deep Links, iOs Universal Links and Custom URL schemes handler for Flutter (desktop included).
|
|
||||||
1. [win32_registry](https://win32.pub) - A package that provides a friendly Dart API for accessing the Windows Registry.
|
|
||||||
1. [flutter_sharing_intent](https://github.com/bhagat-techind/flutter_sharing_intent.git) - A flutter plugin that allow flutter apps to receive photos, videos, text, urls or any other file types from another app.
|
|
||||||
1. [build_runner](https://pub.dev/packages/build_runner) - A build system for Dart code generation and modular compilation.
|
1. [build_runner](https://pub.dev/packages/build_runner) - A build system for Dart code generation and modular compilation.
|
||||||
1. [envied_generator](https://github.com/petercinibulk/envied) - Generator for the Envied package. See https://pub.dev/packages/envied.
|
1. [envied_generator](https://github.com/petercinibulk/envied) - Generator for the Envied package. See https://pub.dev/packages/envied.
|
||||||
1. [flutter_distributor](https://distributor.leanflutter.org) - A complete tool for packaging and publishing your Flutter apps.
|
|
||||||
1. [flutter_gen_runner](https://github.com/FlutterGen/flutter_gen) - The Flutter code generator for your assets, fonts, colors, … — Get rid of all String-based APIs.
|
1. [flutter_gen_runner](https://github.com/FlutterGen/flutter_gen) - The Flutter code generator for your assets, fonts, colors, … — Get rid of all String-based APIs.
|
||||||
1. [flutter_launcher_icons](https://github.com/fluttercommunity/flutter_launcher_icons) - A package which simplifies the task of updating your Flutter app's launcher icon.
|
1. [flutter_launcher_icons](https://github.com/fluttercommunity/flutter_launcher_icons) - A package which simplifies the task of updating your Flutter app's launcher icon.
|
||||||
1. [flutter_lints](https://pub.dev/packages/flutter_lints) - Recommended lints for Flutter apps, packages, and plugins to encourage good coding practices.
|
1. [flutter_lints](https://pub.dev/packages/flutter_lints) - Recommended lints for Flutter apps, packages, and plugins to encourage good coding practices.
|
||||||
1. [hive_generator](https://github.com/hivedb/hive/tree/master/hive_generator) - Extension for Hive. Automatically generates TypeAdapters to store any class.
|
|
||||||
1. [json_serializable](https://pub.dev/packages/json_serializable) - Automatically generate code for converting to and from JSON by annotating Dart classes.
|
1. [json_serializable](https://pub.dev/packages/json_serializable) - Automatically generate code for converting to and from JSON by annotating Dart classes.
|
||||||
1. [pub_api_client](https://github.com/leoafarias/pub_api_client) - An API Client for Pub to interact with public package information.
|
1. [freezed](https://pub.dev/packages/freezed) - Code generation for immutable classes that has a simple syntax/API without compromising on the features.
|
||||||
|
1. [process_run](https://github.com/tekartik/process_run.dart/blob/master/packages/process_run) - Process run helpers for Linux/Win/Mac and which like feature for finding executables.
|
||||||
1. [pubspec_parse](https://pub.dev/packages/pubspec_parse) - Simple package for parsing pubspec.yaml files with a type-safe API and rich error reporting.
|
1. [pubspec_parse](https://pub.dev/packages/pubspec_parse) - Simple package for parsing pubspec.yaml files with a type-safe API and rich error reporting.
|
||||||
1. [flutter_desktop_tools](https://github.com/KRTirtho/flutter_desktop_tools) - Essential collection of tools for flutter desktop app development
|
1. [pub_api_client](https://github.com/leoafarias/pub_api_client) - An API Client for Pub to interact with public package information.
|
||||||
1. [scrobblenaut](https://github.com/Nebulino/Scrobblenaut) - A deadly simple LastFM API Wrapper for Dart. So deadly simple that it's gonna hit the mark.
|
1. [xml](https://github.com/renggli/dart-xml) - A lightweight library for parsing, traversing, querying, transforming and building XML documents.
|
||||||
1. [window_size](https://github.com/google/flutter-desktop-embedding.git) - Allows resizing and repositioning the window containing Flutter.
|
1. [io](https://pub.dev/packages/io) - Utilities for the Dart VM Runtime including support for ANSI colors, file copying, and standard exit code values.
|
||||||
|
1. [drift_dev](https://drift.simonbinder.eu/) - Dev-dependency for users of drift. Contains the generator and development tools.
|
||||||
|
1. [auto_route_generator](https://github.com/Milad-Akarie/auto_route_library) - AutoRoute is a declarative routing solution, where everything needed for navigation is automatically generated for you.
|
||||||
|
1. [desktop_webview_window](https://github.com/MixinNetwork/flutter-plugins/tree/main/packages/desktop_webview_window) - Show a webview window on your flutter desktop application.
|
||||||
|
1. [disable_battery_optimization](https://github.com/pvsvamsi/Disable-Battery-Optimizations) - Flutter plugin to check and disable battery optimizations. Also shows custom steps to disable the optimizations in devices like mi, xiaomi, samsung, oppo, huawei, oneplus etc
|
||||||
1. [draggable_scrollbar](https://github.com/fluttercommunity/flutter-draggable-scrollbar) - A scrollbar that can be dragged for quickly navigation through a vertical list. Additional option is showing label next to scrollthumb with information about current item.
|
1. [draggable_scrollbar](https://github.com/fluttercommunity/flutter-draggable-scrollbar) - A scrollbar that can be dragged for quickly navigation through a vertical list. Additional option is showing label next to scrollthumb with information about current item.
|
||||||
1. [dart_discord_rpc](https://github.com/alexmercerind/dart_discord_rpc) - Discord Rich Presence for Flutter & Dart apps & games.
|
1. [flutter_broadcasts](https://github.com/KRTirtho/flutter_broadcasts.git) - A plugin for sending and receiving broadcasts with Android intents and iOS notifications.
|
||||||
|
1. [scrobblenaut](https://github.com/Nebulino/Scrobblenaut) - A deadly simple LastFM API Wrapper for Dart. So deadly simple that it's gonna hit the mark.
|
||||||
|
1. [yt_dlp_dart](https://github.com/KRTirtho/yt_dlp_dart.git) - yt-dlp binding in Dart
|
||||||
|
1. [flutter_new_pipe_extractor](https://github.com/KRTirtho/flutter_new_pipe_extractor) - NewPipeExtractor binding for Flutter (Android only)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<div align="center"><h4>© Copyright Spotube 2024</h4></div>
|
<div align="center"><h4>© Copyright Spotube 2025</h4></div>
|
||||||
|
@ -25,10 +25,16 @@ linter:
|
|||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
file_names: false
|
file_names: false
|
||||||
|
avoid_renaming_method_parameters: false
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
# Additional information about this file can be found at
|
||||||
# https://dart.dev/guides/language/analysis-options
|
# https://dart.dev/guides/language/analysis-options
|
||||||
analyzer:
|
analyzer:
|
||||||
enable-experiment:
|
errors:
|
||||||
- records
|
invalid_annotation_target: ignore
|
||||||
- patterns
|
exclude:
|
||||||
|
- "**.freezed.dart"
|
||||||
|
- "**.g.dart"
|
||||||
|
- "**.gr.dart"
|
||||||
|
- "**/generated_plugin_registrant.dart"
|
||||||
|
- test/**/*.dart
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
plugins {
|
||||||
|
id "com.android.application"
|
||||||
|
id "kotlin-android"
|
||||||
|
id "dev.flutter.flutter-gradle-plugin"
|
||||||
|
}
|
||||||
|
|
||||||
def localProperties = new Properties()
|
def localProperties = new Properties()
|
||||||
def localPropertiesFile = rootProject.file('local.properties')
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
if (localPropertiesFile.exists()) {
|
if (localPropertiesFile.exists()) {
|
||||||
@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
|
||||||
if (flutterRoot == null) {
|
|
||||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
|
||||||
}
|
|
||||||
|
|
||||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
if (flutterVersionCode == null) {
|
if (flutterVersionCode == null) {
|
||||||
flutterVersionCode = '1'
|
flutterVersionCode = '1'
|
||||||
@ -21,22 +22,23 @@ if (flutterVersionName == null) {
|
|||||||
flutterVersionName = '1.0'
|
flutterVersionName = '1.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
|
||||||
|
|
||||||
def keystoreProperties = new Properties()
|
def keystoreProperties = new Properties()
|
||||||
def keystorePropertiesFile = rootProject.file('key.properties')
|
def keystorePropertiesFile = rootProject.file('key.properties')
|
||||||
if (keystorePropertiesFile.exists()) {
|
if (keystorePropertiesFile.exists()) {
|
||||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
def composeVersion = "1.4.8"
|
||||||
compileSdkVersion 34
|
|
||||||
|
|
||||||
ndkVersion "21.4.7075529"
|
android {
|
||||||
|
namespace "oss.krtirtho.spotube"
|
||||||
|
|
||||||
|
compileSdkVersion 36
|
||||||
|
|
||||||
|
ndkVersion = "27.0.12077973"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
coreLibraryDesugaringEnabled true
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
@ -49,11 +51,18 @@ android {
|
|||||||
main.java.srcDirs += 'src/main/kotlin'
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
compose true
|
||||||
|
}
|
||||||
|
|
||||||
|
composeOptions {
|
||||||
|
kotlinCompilerExtensionVersion "$composeVersion" // Correlates with org.jetbrains.kotlin.android plugin in settings.gradle
|
||||||
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
|
||||||
applicationId "oss.krtirtho.spotube"
|
applicationId "oss.krtirtho.spotube"
|
||||||
minSdkVersion 24
|
minSdkVersion 24
|
||||||
targetSdkVersion flutter.targetSdkVersion
|
targetSdkVersion 35
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
@ -67,10 +76,14 @@ android {
|
|||||||
storePassword keystoreProperties['storePassword']
|
storePassword keystoreProperties['storePassword']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.release
|
||||||
}
|
}
|
||||||
|
debug {
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions "default"
|
flavorDimensions "default"
|
||||||
@ -81,37 +94,46 @@ android {
|
|||||||
resValue "string", "app_name_en", "Spotube Nightly"
|
resValue "string", "app_name_en", "Spotube Nightly"
|
||||||
applicationIdSuffix ".nightly"
|
applicationIdSuffix ".nightly"
|
||||||
versionNameSuffix "-nightly"
|
versionNameSuffix "-nightly"
|
||||||
|
signingConfig signingConfigs.release
|
||||||
}
|
}
|
||||||
dev {
|
dev {
|
||||||
dimension "default"
|
dimension "default"
|
||||||
resValue "string", "app_name_en", "Spotube Dev"
|
resValue "string", "app_name_en", "Spotube Dev"
|
||||||
applicationIdSuffix ".dev"
|
applicationIdSuffix ".dev"
|
||||||
versionNameSuffix "-dev"
|
versionNameSuffix "-dev"
|
||||||
|
signingConfig signingConfigs.release
|
||||||
}
|
}
|
||||||
stable {
|
stable {
|
||||||
dimension "default"
|
dimension "default"
|
||||||
resValue "string", "app_name_en", "Spotube"
|
resValue "string", "app_name_en", "Spotube"
|
||||||
|
signingConfig signingConfigs.release
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
resources.excludes += "DebugProbesKt.bin"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flutter {
|
flutter {
|
||||||
source '../..'
|
source '../..'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def glanceVersion = "1.1.1"
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
|
||||||
constraints {
|
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version") {
|
|
||||||
because("kotlin-stdlib-jdk7 is now a part of kotlin-stdlib")
|
|
||||||
}
|
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version") {
|
|
||||||
because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
|
|
||||||
|
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
|
||||||
// other deps so just ignore
|
// other deps so just ignore
|
||||||
implementation 'com.android.support:multidex:2.0.1'
|
implementation 'com.android.support:multidex:2.0.1'
|
||||||
|
|
||||||
|
implementation "androidx.glance:glance-appwidget:$glanceVersion"
|
||||||
|
implementation "androidx.glance:glance-appwidget-preview:$glanceVersion"
|
||||||
|
implementation "androidx.glance:glance-preview:$glanceVersion"
|
||||||
|
implementation "androidx.glance:glance-material3:$glanceVersion"
|
||||||
|
implementation "androidx.glance:glance-material:$glanceVersion"
|
||||||
|
implementation "androidx.work:work-runtime-ktx:2.8.1"
|
||||||
|
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3"
|
||||||
|
implementation 'com.google.code.gson:gson:2.11.0'
|
||||||
}
|
}
|
58
android/app/proguard-rules.pro
vendored
@ -1 +1,59 @@
|
|||||||
|
#Flutter Wrapper
|
||||||
|
# -keep class io.flutter.app.** { *; }
|
||||||
|
-keep class io.flutter.plugin.** { *; }
|
||||||
|
-keep class io.flutter.util.** { *; }
|
||||||
|
-keep class io.flutter.view.** { *; }
|
||||||
|
# -keep class io.flutter.** { *; }
|
||||||
|
-keep class io.flutter.plugins.** { *; }
|
||||||
|
-keep class de.prosiebensat1digital.** { *; }
|
||||||
|
|
||||||
-keep class androidx.lifecycle.DefaultLifecycleObserver
|
-keep class androidx.lifecycle.DefaultLifecycleObserver
|
||||||
|
|
||||||
|
-keepnames class kotlinx.serialization.** { *; }
|
||||||
|
-keepnames class oss.krtirtho.spotube.glance.models.** { *; }
|
||||||
|
-keep @kotlinx.serialization.Serializable class *
|
||||||
|
-keepclassmembers class ** {
|
||||||
|
@kotlinx.serialization.* <fields>;
|
||||||
|
}
|
||||||
|
|
||||||
|
## We don't need beans
|
||||||
|
-dontwarn java.beans.BeanDescriptor
|
||||||
|
-dontwarn java.beans.BeanInfo
|
||||||
|
-dontwarn java.beans.IntrospectionException
|
||||||
|
-dontwarn java.beans.Introspector
|
||||||
|
-dontwarn java.beans.PropertyDescriptor
|
||||||
|
|
||||||
|
## Rules for NewPipeExtractor
|
||||||
|
-keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; }
|
||||||
|
-keep class org.mozilla.javascript.** { *; }
|
||||||
|
-keep class org.mozilla.classfile.ClassFileWriter
|
||||||
|
-dontwarn org.mozilla.javascript.tools.**
|
||||||
|
|
||||||
|
-dontwarn javax.script.AbstractScriptEngine
|
||||||
|
-dontwarn javax.script.Bindings
|
||||||
|
-dontwarn javax.script.Compilable
|
||||||
|
-dontwarn javax.script.CompiledScript
|
||||||
|
-dontwarn javax.script.Invocable
|
||||||
|
-dontwarn javax.script.ScriptContext
|
||||||
|
-dontwarn javax.script.ScriptEngine
|
||||||
|
-dontwarn javax.script.ScriptEngineFactory
|
||||||
|
-dontwarn javax.script.ScriptException
|
||||||
|
-dontwarn javax.script.SimpleBindings
|
||||||
|
-dontwarn jdk.dynalink.CallSiteDescriptor
|
||||||
|
-dontwarn jdk.dynalink.DynamicLinker
|
||||||
|
-dontwarn jdk.dynalink.DynamicLinkerFactory
|
||||||
|
-dontwarn jdk.dynalink.NamedOperation
|
||||||
|
-dontwarn jdk.dynalink.Namespace
|
||||||
|
-dontwarn jdk.dynalink.NamespaceOperation
|
||||||
|
-dontwarn jdk.dynalink.Operation
|
||||||
|
-dontwarn jdk.dynalink.RelinkableCallSite
|
||||||
|
-dontwarn jdk.dynalink.StandardNamespace
|
||||||
|
-dontwarn jdk.dynalink.StandardOperation
|
||||||
|
-dontwarn jdk.dynalink.linker.GuardedInvocation
|
||||||
|
-dontwarn jdk.dynalink.linker.GuardingDynamicLinker
|
||||||
|
-dontwarn jdk.dynalink.linker.LinkRequest
|
||||||
|
-dontwarn jdk.dynalink.linker.LinkerServices
|
||||||
|
-dontwarn jdk.dynalink.linker.TypeBasedGuardingDynamicLinker
|
||||||
|
-dontwarn jdk.dynalink.linker.support.CompositeTypeBasedGuardingDynamicLinker
|
||||||
|
-dontwarn jdk.dynalink.linker.support.Guards
|
||||||
|
-dontwarn jdk.dynalink.support.ChainedCallSite
|
@ -1,7 +1,19 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
package="oss.krtirtho.spotube">
|
|
||||||
<!-- Flutter needs it to communicate with the running application
|
<!-- Flutter needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<application
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:allowBackup="false"
|
||||||
|
android:fullBackupContent="false"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name_en"
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
|
android:usesCleartextTraffic="true">
|
||||||
|
<!-- Disable Impeller -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.EnableImpeller"
|
||||||
|
android:value="false" />
|
||||||
|
</application>
|
||||||
</manifest>
|
</manifest>
|
@ -1,8 +1,9 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="oss.krtirtho.spotube">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||||
@ -16,23 +17,26 @@
|
|||||||
</queries>
|
</queries>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name="${applicationName}"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:fullBackupContent="false"
|
android:fullBackupContent="false"
|
||||||
android:label="@string/app_name_en"
|
|
||||||
android:name="${applicationName}"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:usesCleartextTraffic="true"
|
android:label="@string/app_name_en"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
>
|
android:usesCleartextTraffic="true">
|
||||||
|
<!-- Enable Impeller -->
|
||||||
|
<!-- <meta-data
|
||||||
|
android:name="io.flutter.embedding.android.EnableImpeller"
|
||||||
|
android:value="false" /> -->
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.ryanheise.audioservice.AudioServiceActivity"
|
android:name="com.ryanheise.audioservice.AudioServiceActivity"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:windowSoftInputMode="adjustResize">
|
||||||
android:hardwareAccelerated="true"
|
|
||||||
android:windowSoftInputMode="adjustResize"
|
|
||||||
>
|
|
||||||
<!--
|
<!--
|
||||||
Specifies an Android theme to apply to this Activity as soon as
|
Specifies an Android theme to apply to this Activity as soon as
|
||||||
the Android process has started. This theme is visible to the user
|
the Android process has started. This theme is visible to the user
|
||||||
@ -41,8 +45,8 @@
|
|||||||
-->
|
-->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme"
|
android:resource="@style/NormalTheme" />
|
||||||
/>
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
@ -50,12 +54,9 @@
|
|||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data
|
|
||||||
android:scheme="https"
|
|
||||||
android:host="open.spotify.com"
|
|
||||||
/>
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@ -66,23 +67,28 @@
|
|||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<!-- Accepts URIs that begin with "spotify:// -->
|
<data android:scheme="spotube" />
|
||||||
<data android:scheme="spotify" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="es.antonborri.home_widget.action.LAUNCH" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<!-- AudioService Config -->
|
<!-- AudioService Config -->
|
||||||
<service android:name="com.ryanheise.audioservice.AudioService"
|
<service
|
||||||
android:foregroundServiceType="mediaPlayback"
|
android:name="com.ryanheise.audioservice.AudioService"
|
||||||
android:exported="true">
|
android:exported="true"
|
||||||
|
android:foregroundServiceType="mediaPlayback">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.media.browse.MediaBrowserService" />
|
<action android:name="android.media.browse.MediaBrowserService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver"
|
<receiver
|
||||||
|
android:name="com.ryanheise.audioservice.MediaButtonReceiver"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
@ -90,11 +96,40 @@
|
|||||||
</receiver>
|
</receiver>
|
||||||
<!-- =================== -->
|
<!-- =================== -->
|
||||||
|
|
||||||
<meta-data android:name="com.google.android.gms.car.application"
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.car.application"
|
||||||
android:resource="@xml/automotive_app_desc" />
|
android:resource="@xml/automotive_app_desc" />
|
||||||
|
|
||||||
|
<!-- Home Widget config -->
|
||||||
|
<receiver
|
||||||
|
android:name=".glance.HomePlayerWidgetReceiver"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/home_player_widget_config" />
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="es.antonborri.home_widget.action.BACKGROUND" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="es.antonborri.home_widget.HomeWidgetBackgroundService"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||||
|
<!-- =================== -->
|
||||||
|
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
<meta-data android:name="flutterEmbedding" android:value="2" />
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
@ -0,0 +1,207 @@
|
|||||||
|
package oss.krtirtho.spotube.glance
|
||||||
|
|
||||||
|
import HomeWidgetGlanceState
|
||||||
|
import HomeWidgetGlanceStateDefinition
|
||||||
|
import android.R
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Icon
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.glance.GlanceId
|
||||||
|
import androidx.glance.GlanceModifier
|
||||||
|
import androidx.glance.GlanceTheme
|
||||||
|
import androidx.glance.Image
|
||||||
|
import androidx.glance.ImageProvider
|
||||||
|
import androidx.glance.LocalSize
|
||||||
|
import androidx.glance.action.ActionParameters
|
||||||
|
import androidx.glance.action.actionParametersOf
|
||||||
|
import androidx.glance.action.clickable
|
||||||
|
import androidx.glance.background
|
||||||
|
import androidx.glance.appwidget.GlanceAppWidget
|
||||||
|
import androidx.glance.appwidget.SizeMode
|
||||||
|
import androidx.glance.appwidget.action.ActionCallback
|
||||||
|
import androidx.glance.appwidget.action.actionRunCallback
|
||||||
|
import androidx.glance.appwidget.background
|
||||||
|
import androidx.glance.appwidget.components.CircleIconButton
|
||||||
|
import androidx.glance.appwidget.components.Scaffold
|
||||||
|
import androidx.glance.appwidget.cornerRadius
|
||||||
|
import androidx.glance.appwidget.provideContent
|
||||||
|
import androidx.glance.background
|
||||||
|
import androidx.glance.currentState
|
||||||
|
import androidx.glance.layout.Alignment
|
||||||
|
import androidx.glance.layout.Box
|
||||||
|
import androidx.glance.layout.Column
|
||||||
|
import androidx.glance.layout.ContentScale
|
||||||
|
import androidx.glance.layout.Row
|
||||||
|
import androidx.glance.layout.Spacer
|
||||||
|
import androidx.glance.layout.fillMaxSize
|
||||||
|
import androidx.glance.layout.fillMaxWidth
|
||||||
|
import androidx.glance.layout.padding
|
||||||
|
import androidx.glance.layout.size
|
||||||
|
import androidx.glance.preview.ExperimentalGlancePreviewApi
|
||||||
|
import androidx.glance.preview.Preview
|
||||||
|
import androidx.glance.state.GlanceStateDefinition
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import es.antonborri.home_widget.HomeWidgetBackgroundIntent
|
||||||
|
import es.antonborri.home_widget.actionStartActivity
|
||||||
|
import oss.krtirtho.spotube.MainActivity
|
||||||
|
import oss.krtirtho.spotube.glance.models.Track
|
||||||
|
import oss.krtirtho.spotube.glance.widgets.FlutterAssetImageProvider
|
||||||
|
import oss.krtirtho.spotube.glance.widgets.TrackDetailsView
|
||||||
|
import oss.krtirtho.spotube.glance.widgets.TrackProgress
|
||||||
|
|
||||||
|
val gson = Gson()
|
||||||
|
val serverAddressKey = ActionParameters.Key<String>("serverAddress")
|
||||||
|
|
||||||
|
class Breakpoints {
|
||||||
|
companion object {
|
||||||
|
val SMALL_SQUARE = DpSize(100.dp, 100.dp)
|
||||||
|
val HORIZONTAL_RECTANGLE = DpSize(250.dp, 100.dp)
|
||||||
|
val BIG_SQUARE = DpSize(250.dp, 250.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HomePlayerWidget : GlanceAppWidget() {
|
||||||
|
|
||||||
|
override val sizeMode = SizeMode.Responsive(
|
||||||
|
setOf(
|
||||||
|
Breakpoints.SMALL_SQUARE,
|
||||||
|
Breakpoints.HORIZONTAL_RECTANGLE,
|
||||||
|
Breakpoints.BIG_SQUARE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
override val stateDefinition: GlanceStateDefinition<*>?
|
||||||
|
get() = HomeWidgetGlanceStateDefinition()
|
||||||
|
|
||||||
|
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||||
|
provideContent {
|
||||||
|
GlanceContent(context, currentState())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalGlancePreviewApi::class)
|
||||||
|
@Preview(widthDp = 100, heightDp = 100)
|
||||||
|
@Composable
|
||||||
|
private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) {
|
||||||
|
val prefs = currentState.preferences
|
||||||
|
val size = LocalSize.current
|
||||||
|
|
||||||
|
val activeTrackStr = prefs.getString("activeTrack", null)
|
||||||
|
|
||||||
|
val isPlaying = prefs.getBoolean("isPlaying", false)
|
||||||
|
val playbackServerAddress = prefs.getString("playbackServerAddress", null) ?: ""
|
||||||
|
|
||||||
|
var activeTrack: Track? = null
|
||||||
|
if (activeTrackStr != null) {
|
||||||
|
activeTrack = gson.fromJson(activeTrackStr, Track::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val playIcon = Icon.createWithResource(context, R.drawable.ic_media_play);
|
||||||
|
val pauseIcon = Icon.createWithResource(context, R.drawable.ic_media_pause);
|
||||||
|
val previousIcon = Icon.createWithResource(context, R.drawable.ic_media_previous);
|
||||||
|
val nextIcon = Icon.createWithResource(context, R.drawable.ic_media_next);
|
||||||
|
|
||||||
|
GlanceTheme {
|
||||||
|
Box(
|
||||||
|
modifier = GlanceModifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.cornerRadius(8.dp)
|
||||||
|
.background(
|
||||||
|
color = GlanceTheme.colors.surface.getColor(context)
|
||||||
|
)
|
||||||
|
.clickable {
|
||||||
|
actionStartActivity<MainActivity>(context)
|
||||||
|
}
|
||||||
|
,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = GlanceModifier
|
||||||
|
.background(
|
||||||
|
color =
|
||||||
|
GlanceTheme.colors.surface.getColor(context)
|
||||||
|
.copy(alpha = 0.5f),
|
||||||
|
)
|
||||||
|
.fillMaxSize(),
|
||||||
|
) {}
|
||||||
|
Column(
|
||||||
|
modifier = GlanceModifier.padding(top = 10.dp, start = 10.dp, end = 10.dp)
|
||||||
|
) {
|
||||||
|
Row(verticalAlignment = Alignment.Vertical.CenterVertically) {
|
||||||
|
TrackDetailsView(activeTrack)
|
||||||
|
}
|
||||||
|
Spacer(modifier = GlanceModifier.size(6.dp))
|
||||||
|
if (size != Breakpoints.SMALL_SQUARE) {
|
||||||
|
TrackProgress(prefs)
|
||||||
|
}
|
||||||
|
Spacer(modifier = GlanceModifier.size(6.dp))
|
||||||
|
Row(
|
||||||
|
modifier = GlanceModifier.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.Horizontal.CenterHorizontally
|
||||||
|
) {
|
||||||
|
CircleIconButton(
|
||||||
|
imageProvider = ImageProvider(previousIcon),
|
||||||
|
contentDescription = "Previous",
|
||||||
|
onClick = actionRunCallback<PreviousAction>(
|
||||||
|
parameters = actionParametersOf(serverAddressKey to playbackServerAddress)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Spacer(modifier = GlanceModifier.size(6.dp))
|
||||||
|
CircleIconButton(
|
||||||
|
imageProvider =
|
||||||
|
if (isPlaying) ImageProvider(pauseIcon)
|
||||||
|
else ImageProvider(playIcon),
|
||||||
|
contentDescription = "Play/Pause",
|
||||||
|
onClick = actionRunCallback<PlayPauseAction>(
|
||||||
|
parameters = actionParametersOf(serverAddressKey to playbackServerAddress)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Spacer(modifier = GlanceModifier.size(6.dp))
|
||||||
|
CircleIconButton(
|
||||||
|
imageProvider = ImageProvider(nextIcon),
|
||||||
|
contentDescription = "Previous",
|
||||||
|
onClick = actionRunCallback<NextAction>(
|
||||||
|
parameters = actionParametersOf(
|
||||||
|
serverAddressKey to playbackServerAddress
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PlayPauseAction : InteractiveAction("toggle-playback")
|
||||||
|
class NextAction : InteractiveAction("next")
|
||||||
|
class PreviousAction : InteractiveAction("previous")
|
||||||
|
|
||||||
|
|
||||||
|
abstract class InteractiveAction(val command: String) : ActionCallback {
|
||||||
|
override suspend fun onAction(
|
||||||
|
context: Context,
|
||||||
|
glanceId: GlanceId,
|
||||||
|
parameters: ActionParameters
|
||||||
|
) {
|
||||||
|
val serverAddress = parameters[serverAddressKey] ?: ""
|
||||||
|
|
||||||
|
Log.d("HomePlayerWidget", "Sending command $command to $serverAddress")
|
||||||
|
|
||||||
|
if (serverAddress == null || serverAddress.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(
|
||||||
|
context,
|
||||||
|
Uri.parse("spotube://playback/$command?serverAddress=$serverAddress")
|
||||||
|
)
|
||||||
|
backgroundIntent.send()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package oss.krtirtho.spotube.glance
|
||||||
|
|
||||||
|
import HomeWidgetGlanceWidgetReceiver
|
||||||
|
|
||||||
|
class HomePlayerWidgetReceiver : HomeWidgetGlanceWidgetReceiver<HomePlayerWidget>() {
|
||||||
|
override val glanceAppWidget = HomePlayerWidget()
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package oss.krtirtho.spotube.glance.models
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AlbumSimple(
|
||||||
|
@SerializedName("album_type")
|
||||||
|
val albumType: AlbumType?,
|
||||||
|
|
||||||
|
@SerializedName("available_markets")
|
||||||
|
val availableMarkets: List<Market>?,
|
||||||
|
|
||||||
|
val href: String?,
|
||||||
|
val id: String?,
|
||||||
|
val images: List<Image>?,
|
||||||
|
val name: String?,
|
||||||
|
|
||||||
|
@SerializedName("release_date")
|
||||||
|
val releaseDate: String?,
|
||||||
|
|
||||||
|
@SerializedName("release_date_precision")
|
||||||
|
val releaseDatePrecision: DatePrecision?,
|
||||||
|
|
||||||
|
val type: String?,
|
||||||
|
val uri: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class AlbumType {
|
||||||
|
album,
|
||||||
|
single,
|
||||||
|
compilation
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class DatePrecision {
|
||||||
|
year,
|
||||||
|
month,
|
||||||
|
day
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package oss.krtirtho.spotube.glance.models
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Artist(
|
||||||
|
val href: String?,
|
||||||
|
val id: String?,
|
||||||
|
val name: String?,
|
||||||
|
val type: String?,
|
||||||
|
val uri: String?,
|
||||||
|
|
||||||
|
val followers: Followers?,
|
||||||
|
val genres: List<String>?,
|
||||||
|
val images: List<Image>?,
|
||||||
|
|
||||||
|
@SerializedName("popularity")
|
||||||
|
val popularity: Int?
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Followers(
|
||||||
|
val total: Int?
|
||||||
|
)
|
@ -0,0 +1,10 @@
|
|||||||
|
package oss.krtirtho.spotube.glance.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Image(
|
||||||
|
val height: Int?,
|
||||||
|
val width: Int?,
|
||||||
|
val path: String,
|
||||||
|
)
|
@ -0,0 +1,37 @@
|
|||||||
|
package oss.krtirtho.spotube.glance.models
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Track(
|
||||||
|
val album: AlbumSimple?, val artists: List<Artist>?,
|
||||||
|
|
||||||
|
@SerializedName("available_markets") val availableMarkets: List<Market>?,
|
||||||
|
|
||||||
|
@SerializedName("disc_number") val discNumber: Int?,
|
||||||
|
|
||||||
|
@SerializedName("duration_ms") val durationMs: Int,
|
||||||
|
|
||||||
|
val explicit: Boolean?, val href: String?, val id: String?,
|
||||||
|
|
||||||
|
@SerializedName("is_playable") val isPlayable: Boolean?,
|
||||||
|
|
||||||
|
val name: String?,
|
||||||
|
|
||||||
|
@SerializedName("popularity") val popularity: Int?,
|
||||||
|
|
||||||
|
@SerializedName("preview_url") val previewUrl: String?,
|
||||||
|
|
||||||
|
@SerializedName("track_number") val trackNumber: Int?,
|
||||||
|
|
||||||
|
val type: String?, val uri: String?
|
||||||
|
) {
|
||||||
|
val duration: kotlin.time.Duration
|
||||||
|
get() = durationMs.toLong().milliseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Market {
|
||||||
|
AD, AE, AF, AG, AI, AL, AM, AO, AQ, AR, AS, AT, AU, AW, AX, AZ, BA, BB, BD, BE, BF, BG, BH, BI, BJ, BL, BM, BN, BO, BQ, BR, BS, BT, BV, BW, BY, BZ, CA, CC, CD, CF, CG, CH, CI, CK, CL, CM, CN, CO, CR, CU, CV, CW, CX, CY, CZ, DE, DJ, DK, DM, DO, DZ, EC, EE, EG, EH, ER, ES, ET, FI, FJ, FK, FM, FO, FR, GA, GB, GD, GE, GF, GG, GH, GI, GL, GM, GN, GP, GQ, GR, GS, GT, GU, GW, GY, HK, HM, HN, HR, HT, HU, ID, IE, IL, IM, IN, IO, IQ, IR, IS, IT, JE, JM, JO, JP, KE, KG, KH, KI, KM, KN, KP, KR, KW, KY, KZ, LA, LB, LC, LI, LK, LR, LS, LT, LU, LV, LY, MA, MC, MD, ME, MF, MG, MH, MK, ML, MM, MN, MO, MP, MQ, MR, MS, MT, MU, MV, MW, MX, MY, MZ, NA, NC, NE, NF, NG, NI, NL, NO, NP, NR, NU, NZ, OM, PA, PE, PF, PG, PH, PK, PL, PM, PN, PR, PS, PT, PW, PY, QA, RE, RO, RS, RU, RW, SA, SB, SC, SD, SE, SG, SH, SI, SJ, SK, SL, SM, SN, SO, SR, SS, ST, SV, SX, SY, SZ, TC, TD, TF, TG, TH, TJ, TK, TL, TM, TN, TO, TR, TT, TV, TW, TZ, UA, UG, UM, US, UY, UZ, VA, VC, VE, VG, VI, VN, VU, WF, WS, XK, YE, YT, ZA, ZM, ZW,
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package oss.krtirtho.spotube.glance.widgets
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.util.Base64
|
||||||
|
import androidx.glance.ImageProvider
|
||||||
|
|
||||||
|
@Suppress("FunctionName")
|
||||||
|
fun Base64ImageProvider(base64: String): ImageProvider {
|
||||||
|
var bytes = Base64.decode(base64, Base64.DEFAULT);
|
||||||
|
|
||||||
|
var bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size);
|
||||||
|
|
||||||
|
return ImageProvider(bitmap)
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package oss.krtirtho.spotube.glance.widgets
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import androidx.glance.ImageProvider
|
||||||
|
|
||||||
|
@Suppress("FunctionName")
|
||||||
|
fun FlutterAssetImageProvider(context: Context, path: String): ImageProvider {
|
||||||
|
var inputStream = context.assets.open("flutter_assets/$path")
|
||||||
|
|
||||||
|
return ImageProvider(
|
||||||
|
BitmapFactory.decodeStream(inputStream)
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package oss.krtirtho.spotube.glance.widgets
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.glance.GlanceModifier
|
||||||
|
import androidx.glance.GlanceTheme
|
||||||
|
import androidx.glance.Image
|
||||||
|
import androidx.glance.ImageProvider
|
||||||
|
import androidx.glance.LocalContext
|
||||||
|
import androidx.glance.LocalSize
|
||||||
|
import androidx.glance.appwidget.cornerRadius
|
||||||
|
import androidx.glance.layout.Alignment
|
||||||
|
import androidx.glance.layout.Row
|
||||||
|
import androidx.glance.layout.Column
|
||||||
|
import androidx.glance.layout.ContentScale
|
||||||
|
import androidx.glance.layout.Spacer
|
||||||
|
import androidx.glance.layout.size
|
||||||
|
import androidx.glance.text.FontWeight
|
||||||
|
import androidx.glance.text.Text
|
||||||
|
import androidx.glance.text.TextStyle
|
||||||
|
import oss.krtirtho.spotube.glance.Breakpoints
|
||||||
|
import oss.krtirtho.spotube.glance.models.Track
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TrackDetailsView(activeTrack: Track?) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val size = LocalSize.current
|
||||||
|
|
||||||
|
val artistStr = activeTrack?.artists?.map { it.name }?.joinToString(", ") ?: "<No Artist>"
|
||||||
|
val imgLocalPath = activeTrack?.album?.images?.get(0)?.path;
|
||||||
|
val title = activeTrack?.name ?: "<No Track>"
|
||||||
|
|
||||||
|
|
||||||
|
Image(
|
||||||
|
provider =
|
||||||
|
if (imgLocalPath == null)
|
||||||
|
ImageProvider(
|
||||||
|
BitmapFactory.decodeResource(
|
||||||
|
context.resources,
|
||||||
|
android.R.drawable.ic_delete
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else ImageProvider(BitmapFactory.decodeFile(imgLocalPath)),
|
||||||
|
contentDescription = "Album Art",
|
||||||
|
modifier = GlanceModifier.cornerRadius(8.dp)
|
||||||
|
.size(
|
||||||
|
if (size.height < 200.dp) 50.dp
|
||||||
|
else 100.dp
|
||||||
|
),
|
||||||
|
contentScale = ContentScale.Fit
|
||||||
|
)
|
||||||
|
Spacer(modifier = GlanceModifier.size(6.dp))
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = GlanceTheme.colors.onBackground
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if (size != Breakpoints.SMALL_SQUARE) {
|
||||||
|
Spacer(modifier = GlanceModifier.size(6.dp))
|
||||||
|
Text(
|
||||||
|
text = artistStr,
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = GlanceTheme.colors.onBackground
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package oss.krtirtho.spotube.glance.widgets
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.glance.GlanceModifier
|
||||||
|
import androidx.glance.GlanceTheme
|
||||||
|
import androidx.glance.LocalSize
|
||||||
|
import androidx.glance.appwidget.LinearProgressIndicator
|
||||||
|
import androidx.glance.layout.Column
|
||||||
|
import androidx.glance.layout.Row
|
||||||
|
import androidx.glance.layout.Spacer
|
||||||
|
import androidx.glance.layout.fillMaxWidth
|
||||||
|
import androidx.glance.layout.size
|
||||||
|
import androidx.glance.text.Text
|
||||||
|
import androidx.glance.text.TextStyle
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
import oss.krtirtho.spotube.glance.Breakpoints
|
||||||
|
|
||||||
|
fun Duration.format(): String {
|
||||||
|
return this.toComponents { hour, minutes, seconds, nanoseconds ->
|
||||||
|
var paddedSeconds = seconds.toString().padStart(2, '0')
|
||||||
|
var paddedMinutes = minutes.toString().padStart(2, '0')
|
||||||
|
var paddedHour = hour.toString().padStart(2, '0')
|
||||||
|
if (hour == 0L) {
|
||||||
|
"$paddedMinutes:$paddedSeconds"
|
||||||
|
} else {
|
||||||
|
"$paddedHour:$paddedMinutes:$paddedSeconds"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TrackProgress(prefs: SharedPreferences) {
|
||||||
|
val size = LocalSize.current
|
||||||
|
val position = prefs.getInt("position", 0).seconds
|
||||||
|
var duration = prefs.getInt("duration", 0).seconds
|
||||||
|
|
||||||
|
var progress = position.inWholeSeconds.toFloat() / max(duration.inWholeSeconds.toFloat(), 1.0f)
|
||||||
|
|
||||||
|
var textStyle =
|
||||||
|
TextStyle(
|
||||||
|
color = GlanceTheme.colors.onBackground,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (size == Breakpoints.HORIZONTAL_RECTANGLE) {
|
||||||
|
Row(modifier = GlanceModifier.fillMaxWidth()) {
|
||||||
|
Text(text = position.format(), style = textStyle)
|
||||||
|
Spacer(modifier = GlanceModifier.size(6.dp))
|
||||||
|
LinearProgressIndicator(
|
||||||
|
progress = progress,
|
||||||
|
modifier = GlanceModifier.defaultWeight(),
|
||||||
|
color = GlanceTheme.colors.primary,
|
||||||
|
backgroundColor = GlanceTheme.colors.primaryContainer,
|
||||||
|
)
|
||||||
|
Spacer(modifier = GlanceModifier.size(6.dp))
|
||||||
|
Text(text = duration.format(), style = textStyle)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Column(modifier = GlanceModifier.fillMaxWidth()) {
|
||||||
|
LinearProgressIndicator(
|
||||||
|
progress = progress,
|
||||||
|
modifier = GlanceModifier.fillMaxWidth(),
|
||||||
|
color = GlanceTheme.colors.primary,
|
||||||
|
backgroundColor = GlanceTheme.colors.primaryContainer,
|
||||||
|
)
|
||||||
|
Spacer(modifier = GlanceModifier.size(6.dp))
|
||||||
|
Row(modifier = GlanceModifier.fillMaxWidth()) {
|
||||||
|
Text(text = position.format(), style = textStyle)
|
||||||
|
Spacer(modifier = GlanceModifier.defaultWeight())
|
||||||
|
Text(text = duration.format(), style = textStyle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 162 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 247 KiB |
Before Width: | Height: | Size: 3.1 MiB After Width: | Height: | Size: 3.0 MiB |
@ -6,7 +6,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item android:bottom="0dp">
|
||||||
<bitmap android:gravity="bottom" android:src="@drawable/branding"/>
|
<bitmap android:gravity="bottom" android:src="@drawable/branding"/>
|
||||||
</item>
|
</item>
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 162 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 247 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 3.1 MiB After Width: | Height: | Size: 3.0 MiB |
@ -6,7 +6,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item android:bottom="0dp">
|
||||||
<bitmap android:gravity="bottom" android:src="@drawable/branding"/>
|
<bitmap android:gravity="bottom" android:src="@drawable/branding"/>
|
||||||
</item>
|
</item>
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground>
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
<inset
|
||||||
|
android:drawable="@drawable/ic_launcher_foreground"
|
||||||
|
android:inset="16%" />
|
||||||
|
</foreground>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 23 KiB |
@ -5,6 +5,10 @@
|
|||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
Flutter draws its first frame -->
|
Flutter draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
This theme determines the color of the Android Window while your
|
This theme determines the color of the Android Window while your
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:initialLayout="@layout/glance_default_loading_layout"
|
||||||
|
android:minWidth="100dp"
|
||||||
|
android:minHeight="100dp"
|
||||||
|
android:resizeMode="horizontal|vertical"
|
||||||
|
android:updatePeriodMillis="10000">
|
||||||
|
</appwidget-provider>
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 220 KiB After Width: | Height: | Size: 181 KiB |