Compare commits
229 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bac50cb86e | ||
|
|
3e43029b96 | ||
|
|
b3e82b7a8e | ||
|
|
2d3622487d | ||
|
|
683f5b3369 | ||
|
|
01250cfc35 | ||
|
|
e726b4b0ea | ||
|
|
648ed898d9 | ||
|
|
a563562edb | ||
|
|
a16599ef96 | ||
|
|
30a69f4174 | ||
|
|
008bf0bf6e | ||
|
|
1dde1d2635 | ||
|
|
f9e1526d54 | ||
|
|
1e2993305c | ||
|
|
cc36e2a89a | ||
|
|
e8c0048229 | ||
|
|
6a1a21eec3 | ||
|
|
2d838240fd | ||
|
|
3a8308b66c | ||
|
|
a5ed230f90 | ||
|
|
7b37aa749c | ||
|
|
74b11075cd | ||
|
|
7d85dff1f7 | ||
|
|
9ef3ce60ca | ||
|
|
eb331f1540 | ||
|
|
4b9683d5b6 | ||
|
|
8fc68951b0 | ||
|
|
2270943066 | ||
|
|
c551d1635a | ||
|
|
f100925eff | ||
|
|
48f504ede9 | ||
|
|
b8fdbf17a2 | ||
|
|
6a3b481219 | ||
|
|
bf95846e6c | ||
|
|
2c712f6a07 | ||
|
|
61a30eaa06 | ||
|
|
a9b4bef96f | ||
|
|
4e0296e226 | ||
|
|
f4c755e131 | ||
|
|
ad0d214528 | ||
|
|
965c647b07 | ||
|
|
2094b36384 | ||
|
|
394e64d26b | ||
|
|
a7c134e53c | ||
|
|
6cc79ef8c6 | ||
|
|
842c78bf1d | ||
|
|
0218342f46 | ||
|
|
432118ae47 | ||
|
|
f481f2ceb9 | ||
|
|
d768b0ffdf | ||
|
|
a0229d8ece | ||
|
|
50ad24a7bc | ||
|
|
76854e72df | ||
|
|
88bbb9f56f | ||
|
|
fb97f818b3 | ||
|
|
2e3110e298 | ||
|
|
a167d94165 | ||
|
|
614a1b9ab9 | ||
|
|
8f2008a179 | ||
|
|
0cc98b85b1 | ||
|
|
b34497a6bc | ||
|
|
504b88510c | ||
|
|
481b5c6490 | ||
|
|
10278aefbb | ||
|
|
9117344e82 | ||
|
|
bb4a380a7c | ||
|
|
62ac6d1ade | ||
|
|
22ebd600b9 | ||
|
|
452177f993 | ||
|
|
a6a54ac02e | ||
|
|
96eccb5503 | ||
|
|
43a8ebe513 | ||
|
|
63f7ab7bf6 | ||
|
|
676ff316d7 | ||
|
|
3eccb0bd4a | ||
|
|
351c61f613 | ||
|
|
333e762539 | ||
|
|
280a222210 | ||
|
|
4853eeb0a8 | ||
|
|
9e9bf01b5b | ||
|
|
8989442fc1 | ||
|
|
befe5b5977 | ||
|
|
11585f284c | ||
|
|
9503943b71 | ||
|
|
d38cf80818 | ||
|
|
3e03f15825 | ||
|
|
99e0538596 | ||
|
|
2a209519e1 | ||
|
|
461dc6f384 | ||
|
|
409aa51f1c | ||
|
|
3620fcb5b0 | ||
|
|
ca7c22b0d7 | ||
|
|
318aeb4187 | ||
|
|
1fb3bf63da | ||
|
|
aa5c4861db | ||
|
|
a7c5afdd83 | ||
|
|
38fc655a53 | ||
|
|
4c34d33ac0 | ||
|
|
eb9f84f650 | ||
|
|
443e2db167 | ||
|
|
347c570cde | ||
|
|
f8e2fa6128 | ||
|
|
9dbb979764 | ||
|
|
062db5d13a | ||
|
|
788c7c9440 | ||
|
|
aed602c55e | ||
|
|
24f4648274 | ||
|
|
d29d601513 | ||
|
|
f21d38b080 | ||
|
|
a0c64508c9 | ||
|
|
11084f04e9 | ||
|
|
2a8aa5f3c1 | ||
|
|
c64b8026ae | ||
|
|
ed76d85fd8 | ||
|
|
b2b3723ba8 | ||
|
|
ca9ce54061 | ||
|
|
8199c451b1 | ||
|
|
66173d5a38 | ||
|
|
a5fba341d3 | ||
|
|
689d3bd39b | ||
|
|
0b077ae973 | ||
|
|
9a6bb033bf | ||
|
|
7b84bab217 | ||
|
|
1a56a8996e | ||
|
|
37bcc5e026 | ||
|
|
6a158f5176 | ||
|
|
0b7ca6cb14 | ||
|
|
303f78c3bc | ||
|
|
c0c87e2c10 | ||
|
|
babce57c80 | ||
|
|
ca5866ac13 | ||
|
|
508e255c8b | ||
|
|
aeeea4fd95 | ||
|
|
6029ee539e | ||
|
|
c9a596111c | ||
|
|
b8a73cc003 | ||
|
|
817eb4d9e8 | ||
|
|
20e3e736c2 | ||
|
|
20352c0301 | ||
|
|
e7b7000f46 | ||
|
|
fb3bec623a | ||
|
|
976c066004 | ||
|
|
304a9744a9 | ||
|
|
ee11cae8dc | ||
|
|
04314f116d | ||
|
|
b645244378 | ||
|
|
110ff56aa1 | ||
|
|
bbbeb9524f | ||
|
|
90fc7532ba | ||
|
|
fac622ef97 | ||
|
|
e29b94a576 | ||
|
|
911b3691b3 | ||
|
|
3a84376223 | ||
|
|
0f5a8e44f1 | ||
|
|
5753fb2714 | ||
|
|
4008660a35 | ||
|
|
1cb58e1e0f | ||
|
|
08e9170a80 | ||
|
|
4198a5bac6 | ||
|
|
295ae13705 | ||
|
|
0894f0e777 | ||
|
|
2966ecc651 | ||
|
|
49c168b5b2 | ||
|
|
8e0017e928 | ||
|
|
982add8fbb | ||
|
|
8065e19c85 | ||
|
|
9e59439226 | ||
|
|
5087c299d3 | ||
|
|
bddf5874d4 | ||
|
|
6c469e5d0d | ||
|
|
8c88ece3dc | ||
|
|
eb28a44cc8 | ||
|
|
240e9e93d9 | ||
|
|
eebec73fd2 | ||
|
|
1b226791b4 | ||
|
|
ad9885ce92 | ||
|
|
16418ab205 | ||
|
|
d9d82a1679 | ||
|
|
ff52516324 | ||
|
|
abd3c24f68 | ||
|
|
04ef618295 | ||
|
|
a58c93be8a | ||
|
|
5a8d6b34c1 | ||
|
|
9233e4d373 | ||
|
|
6e50af16a7 | ||
|
|
046479071b | ||
|
|
ffbed73669 | ||
|
|
03371cf645 | ||
|
|
eb606b5f6c | ||
|
|
df927516b1 | ||
|
|
fa23e095e5 | ||
|
|
2298176c3b | ||
|
|
3f7caf4ad4 | ||
|
|
db9363e7af | ||
|
|
ce36761c64 | ||
|
|
4e38cfdb5c | ||
|
|
1c7c88a9ca | ||
|
|
c7d208de23 | ||
|
|
a7639344a1 | ||
|
|
ff76531962 | ||
|
|
dc77f16a34 | ||
|
|
bcd03e7e60 | ||
|
|
92bea5d715 | ||
|
|
37cd4dff6f | ||
|
|
d62bf10eaf | ||
|
|
3afbe5674b | ||
|
|
6d5a699db6 | ||
|
|
e19885a594 | ||
|
|
6e27eb751c | ||
|
|
8e3586f315 | ||
|
|
e994ab6214 | ||
|
|
4906f9dc27 | ||
|
|
55c6da07d9 | ||
|
|
8dc83a5d5a | ||
|
|
94d5732f6a | ||
|
|
f4a9f84061 | ||
|
|
f147481606 | ||
|
|
ebb0b63411 | ||
|
|
8248816577 | ||
|
|
db454b0ebc | ||
|
|
74664612fd | ||
|
|
f60b8795dc | ||
|
|
a3ad273b5f | ||
|
|
50f1638805 | ||
|
|
cf8762560e | ||
|
|
f8262dbe7f | ||
|
|
37b44da41f | ||
|
|
9693fefa81 |
1
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* @ricoberger
|
||||
1
.github/FUNDING.yml
vendored
@@ -1,2 +1,3 @@
|
||||
---
|
||||
github: [ricoberger]
|
||||
custom: ["https://www.paypal.me/ricoberger"]
|
||||
|
||||
BIN
.github/assets/badge-app-store.png
vendored
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 6.6 KiB |
47
.github/assets/badge-app-store.svg
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 120 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M110.135,0L9.535,0C9.168,0 8.806,0 8.44,0.002C8.134,0.004 7.83,0.01 7.521,0.015C6.85,0.023 6.18,0.082 5.517,0.191C4.856,0.304 4.215,0.515 3.616,0.818C3.019,1.124 2.472,1.522 1.998,1.997C1.52,2.471 1.122,3.018 0.819,3.618C0.515,4.217 0.305,4.859 0.194,5.521C0.083,6.183 0.023,6.853 0.015,7.523C0.006,7.83 0.005,8.138 0,8.444L0,31.559C0.005,31.869 0.006,32.17 0.015,32.481C0.023,33.151 0.083,33.821 0.194,34.482C0.304,35.145 0.515,35.788 0.819,36.387C1.122,36.985 1.52,37.53 1.998,38.001C2.471,38.478 3.017,38.876 3.616,39.18C4.215,39.484 4.855,39.697 5.517,39.81C6.18,39.919 6.85,39.978 7.521,39.987C7.83,39.994 8.134,39.998 8.44,39.998C8.806,40 9.168,40 9.535,40L110.135,40C110.494,40 110.859,40 111.219,39.998C111.523,39.998 111.836,39.994 112.141,39.987C112.811,39.979 113.479,39.92 114.141,39.81C114.804,39.696 115.448,39.483 116.049,39.18C116.647,38.876 117.194,38.478 117.666,38.001C118.142,37.528 118.541,36.983 118.848,36.387C119.15,35.787 119.358,35.145 119.467,34.482C119.578,33.821 119.64,33.151 119.652,32.481C119.656,32.17 119.656,31.869 119.656,31.559C119.664,31.195 119.664,30.834 119.664,30.465L119.664,9.536C119.664,9.17 119.664,8.807 119.656,8.444C119.656,8.138 119.656,7.83 119.652,7.523C119.64,6.852 119.578,6.183 119.467,5.521C119.358,4.859 119.149,4.217 118.848,3.618C118.23,2.415 117.251,1.436 116.049,0.818C115.448,0.516 114.804,0.304 114.141,0.191C113.48,0.081 112.811,0.022 112.141,0.015C111.836,0.01 111.523,0.004 111.219,0.002C110.859,-0 110.494,-0 110.135,-0L110.135,0Z" style="fill:rgb(166,166,166);fill-rule:nonzero;"/>
|
||||
<path d="M8.445,39.125C8.14,39.125 7.843,39.121 7.541,39.114C6.914,39.106 6.29,39.052 5.671,38.951C5.095,38.852 4.537,38.667 4.015,38.403C3.498,38.142 3.026,37.798 2.618,37.387C2.204,36.98 1.859,36.508 1.597,35.99C1.333,35.469 1.149,34.91 1.054,34.333C0.951,33.713 0.896,33.086 0.888,32.458C0.881,32.247 0.873,31.545 0.873,31.545L0.873,8.444C0.873,8.444 0.882,7.753 0.888,7.55C0.895,6.922 0.951,6.297 1.053,5.678C1.149,5.099 1.332,4.539 1.597,4.016C1.857,3.498 2.2,3.026 2.612,2.618C3.023,2.206 3.496,1.861 4.014,1.595C4.535,1.332 5.092,1.149 5.667,1.051C6.288,0.95 6.915,0.895 7.543,0.887L8.445,0.875L111.214,0.875L112.127,0.888C112.75,0.895 113.371,0.949 113.985,1.05C114.566,1.149 115.13,1.334 115.656,1.598C116.694,2.133 117.539,2.979 118.071,4.018C118.332,4.538 118.512,5.094 118.606,5.667C118.71,6.291 118.768,6.922 118.78,7.554C118.783,7.837 118.783,8.142 118.783,8.444C118.791,8.819 118.791,9.176 118.791,9.536L118.791,30.465C118.791,30.828 118.791,31.183 118.783,31.54C118.783,31.865 118.783,32.163 118.779,32.47C118.768,33.091 118.71,33.71 118.608,34.323C118.515,34.904 118.333,35.468 118.068,35.993C117.805,36.506 117.462,36.973 117.053,37.379C116.644,37.793 116.172,38.138 115.653,38.401C115.128,38.667 114.566,38.853 113.985,38.951C113.367,39.052 112.742,39.107 112.116,39.114C111.823,39.121 111.517,39.125 111.219,39.125L110.135,39.127L8.445,39.125Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g id="_Group_">
|
||||
<g id="_Group_2_" serif:id="_Group_2">
|
||||
<g id="_Group_3_" serif:id="_Group_3">
|
||||
<path id="_Path_" d="M24.769,20.301C24.791,18.606 25.682,17.036 27.125,16.149C26.21,14.842 24.729,14.041 23.134,13.991C21.455,13.815 19.827,14.996 18.971,14.996C18.099,14.996 16.782,14.008 15.363,14.038C13.498,14.098 11.797,15.135 10.89,16.766C8.956,20.114 10.399,25.035 12.251,27.742C13.178,29.067 14.261,30.547 15.679,30.495C17.066,30.437 17.584,29.61 19.258,29.61C20.917,29.61 21.403,30.495 22.849,30.462C24.338,30.437 25.275,29.13 26.17,27.792C26.836,26.848 27.348,25.804 27.688,24.7C25.924,23.954 24.771,22.216 24.769,20.301Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path id="_Path_2_" serif:id="_Path_2" d="M22.037,12.211C22.848,11.237 23.249,9.984 23.152,8.72C21.912,8.85 20.766,9.443 19.944,10.38C19.132,11.304 18.721,12.514 18.8,13.741C20.056,13.754 21.25,13.189 22.037,12.211Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M42.302,27.14L37.569,27.14L36.432,30.496L34.427,30.496L38.911,18.078L40.994,18.078L45.477,30.496L43.438,30.496L42.302,27.14ZM38.059,25.591L41.811,25.591L39.961,20.144L39.91,20.144L38.059,25.591Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M55.16,25.97C55.16,28.783 53.654,30.591 51.381,30.591C50.209,30.652 49.099,30.035 48.533,29.007L48.49,29.007L48.49,33.491L46.631,33.491L46.631,21.442L48.43,21.442L48.43,22.948L48.464,22.948C49.056,21.926 50.166,21.31 51.347,21.348C53.645,21.348 55.16,23.164 55.16,25.97ZM53.25,25.97C53.25,24.137 52.302,22.932 50.857,22.932C49.437,22.932 48.482,24.162 48.482,25.97C48.482,27.794 49.437,29.016 50.857,29.016C52.302,29.016 53.25,27.819 53.25,25.97Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M65.125,25.97C65.125,28.783 63.619,30.591 61.346,30.591C60.174,30.652 59.064,30.035 58.498,29.007L58.455,29.007L58.455,33.491L56.596,33.491L56.596,21.442L58.395,21.442L58.395,22.948L58.429,22.948C59.021,21.926 60.131,21.31 61.312,21.348C63.61,21.348 65.125,23.164 65.125,25.97ZM63.214,25.97C63.214,24.137 62.267,22.932 60.822,22.932C59.402,22.932 58.447,24.162 58.447,25.97C58.447,27.794 59.402,29.016 60.822,29.016C62.267,29.016 63.214,27.819 63.214,25.97L63.214,25.97Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M71.71,27.036C71.848,28.268 73.044,29.076 74.679,29.076C76.246,29.076 77.373,28.268 77.373,27.157C77.373,26.193 76.693,25.616 75.084,25.221L73.474,24.833C71.194,24.282 70.135,23.216 70.135,21.485C70.135,19.343 72.002,17.871 74.654,17.871C77.278,17.871 79.077,19.343 79.137,21.485L77.261,21.485C77.149,20.246 76.125,19.498 74.627,19.498C73.13,19.498 72.106,20.255 72.106,21.356C72.106,22.234 72.76,22.751 74.361,23.146L75.729,23.482C78.277,24.085 79.335,25.108 79.335,26.925C79.335,29.248 77.485,30.703 74.542,30.703C71.788,30.703 69.928,29.282 69.808,27.036L71.71,27.036Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M83.346,19.3L83.346,21.442L85.068,21.442L85.068,22.914L83.346,22.914L83.346,27.905C83.346,28.681 83.691,29.042 84.448,29.042C84.652,29.038 84.856,29.024 85.059,28.999L85.059,30.462C84.719,30.525 84.373,30.554 84.027,30.548C82.194,30.548 81.479,29.859 81.479,28.103L81.479,22.914L80.163,22.914L80.163,21.442L81.479,21.442L81.479,19.3L83.346,19.3Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M86.065,25.97C86.065,23.121 87.743,21.331 90.359,21.331C92.984,21.331 94.654,23.121 94.654,25.97C94.654,28.826 92.993,30.608 90.359,30.608C87.726,30.608 86.065,28.826 86.065,25.97ZM92.76,25.97C92.76,24.016 91.865,22.862 90.359,22.862C88.853,22.862 87.959,24.024 87.959,25.97C87.959,27.932 88.853,29.076 90.359,29.076C91.865,29.076 92.76,27.932 92.76,25.97L92.76,25.97Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M96.186,21.442L97.959,21.442L97.959,22.983L98.002,22.983C98.248,21.995 99.161,21.309 100.179,21.348C100.393,21.347 100.607,21.37 100.816,21.417L100.816,23.155C100.546,23.073 100.264,23.035 99.981,23.043C99.956,23.042 99.93,23.041 99.905,23.041C98.878,23.041 98.032,23.887 98.032,24.914C98.032,24.985 98.036,25.056 98.044,25.126L98.044,30.496L96.186,30.496L96.186,21.442Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M109.384,27.837C109.134,29.48 107.534,30.608 105.486,30.608C102.852,30.608 101.217,28.844 101.217,26.013C101.217,23.173 102.861,21.331 105.408,21.331C107.913,21.331 109.488,23.052 109.488,25.797L109.488,26.434L103.093,26.434L103.093,26.546C103.087,26.615 103.084,26.684 103.084,26.754C103.084,28.047 104.149,29.112 105.442,29.112C105.471,29.112 105.5,29.111 105.529,29.11C106.428,29.195 107.282,28.675 107.62,27.837L109.384,27.837ZM103.102,25.135L107.628,25.135C107.631,25.094 107.632,25.054 107.632,25.014C107.632,23.819 106.649,22.836 105.455,22.836C105.439,22.836 105.423,22.837 105.408,22.837C105.403,22.837 105.399,22.837 105.394,22.837C104.137,22.837 103.102,23.872 103.102,25.129C103.102,25.131 103.102,25.133 103.102,25.135L103.102,25.135Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="_Group_4_" serif:id="_Group_4">
|
||||
<g>
|
||||
<path d="M37.826,8.731C37.889,8.726 37.952,8.724 38.015,8.724C39.463,8.724 40.655,9.916 40.655,11.364C40.655,11.475 40.648,11.586 40.634,11.696C40.634,13.602 39.604,14.698 37.826,14.698L35.671,14.698L35.671,8.731L37.826,8.731ZM36.598,13.854L37.723,13.854C37.76,13.856 37.797,13.857 37.835,13.857C38.864,13.857 39.71,13.011 39.71,11.981C39.71,11.89 39.704,11.799 39.69,11.708C39.702,11.623 39.708,11.538 39.708,11.452C39.708,10.42 38.859,9.571 37.827,9.571C37.792,9.571 37.757,9.572 37.723,9.574L36.598,9.574L36.598,13.854Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M41.681,12.444C41.674,12.377 41.671,12.309 41.671,12.241C41.671,11.071 42.634,10.108 43.804,10.108C44.974,10.108 45.937,11.071 45.937,12.241C45.937,12.309 45.934,12.377 45.928,12.444C45.934,12.513 45.938,12.582 45.938,12.651C45.938,13.822 44.975,14.785 43.804,14.785C42.634,14.785 41.671,13.822 41.671,12.651C41.671,12.582 41.674,12.513 41.681,12.444ZM45.014,12.444C45.014,11.468 44.575,10.897 43.806,10.897C43.033,10.897 42.599,11.468 42.599,12.444C42.599,13.428 43.033,13.995 43.806,13.995C44.575,13.995 45.014,13.424 45.014,12.444L45.014,12.444Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M51.573,14.698L50.651,14.698L49.721,11.381L49.65,11.381L48.724,14.698L47.811,14.698L46.569,10.195L47.471,10.195L48.277,13.631L48.344,13.631L49.27,10.195L50.122,10.195L51.048,13.631L51.118,13.631L51.921,10.195L52.81,10.195L51.573,14.698Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M53.854,10.195L54.709,10.195L54.709,10.91L54.775,10.91C55.006,10.385 55.547,10.061 56.119,10.108C56.156,10.105 56.192,10.104 56.229,10.104C57.032,10.104 57.694,10.765 57.694,11.568C57.694,11.64 57.688,11.712 57.678,11.783L57.678,14.698L56.789,14.698L56.789,12.006C56.789,11.282 56.475,10.922 55.817,10.922C55.801,10.922 55.785,10.921 55.769,10.921C55.203,10.921 54.736,11.388 54.736,11.954C54.736,11.991 54.738,12.027 54.742,12.063L54.742,14.698L53.854,14.698L53.854,10.195Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<rect x="59.094" y="8.437" width="0.889" height="6.261" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M61.218,12.444C61.211,12.377 61.208,12.309 61.208,12.241C61.208,11.071 62.171,10.108 63.342,10.108C64.512,10.108 65.475,11.071 65.475,12.241C65.475,12.309 65.472,12.377 65.465,12.444C65.472,12.513 65.475,12.582 65.475,12.651C65.475,13.821 64.512,14.785 63.342,14.785C62.171,14.785 61.208,13.821 61.208,12.651C61.208,12.582 61.211,12.513 61.218,12.444ZM64.551,12.444C64.551,11.468 64.112,10.897 63.343,10.897C62.57,10.897 62.136,11.468 62.136,12.444C62.136,13.428 62.57,13.995 63.343,13.995C64.112,13.995 64.551,13.424 64.551,12.444L64.551,12.444Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M66.401,13.424C66.401,12.614 67.004,12.146 68.076,12.08L69.295,12.01L69.295,11.621C69.295,11.146 68.981,10.877 68.374,10.877C67.877,10.877 67.534,11.059 67.435,11.377L66.575,11.377C66.666,10.604 67.393,10.108 68.415,10.108C69.543,10.108 70.18,10.67 70.18,11.621L70.18,14.698L69.325,14.698L69.325,14.065L69.254,14.065C68.964,14.526 68.446,14.797 67.902,14.772C67.855,14.777 67.808,14.779 67.761,14.779C67.017,14.779 66.404,14.169 66.401,13.424ZM69.295,13.04L69.295,12.663L68.196,12.733C67.576,12.775 67.294,12.986 67.294,13.383C67.294,13.788 67.646,14.024 68.129,14.024C68.165,14.028 68.201,14.029 68.236,14.029C68.792,14.029 69.258,13.594 69.295,13.04Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M71.348,12.444C71.348,11.021 72.08,10.12 73.217,10.12C73.791,10.094 74.33,10.402 74.598,10.91L74.665,10.91L74.665,8.437L75.553,8.437L75.553,14.698L74.702,14.698L74.702,13.986L74.631,13.986C74.343,14.49 73.797,14.793 73.217,14.772C72.072,14.772 71.348,13.871 71.348,12.444ZM72.266,12.444C72.266,13.399 72.716,13.974 73.469,13.974C74.218,13.974 74.681,13.391 74.681,12.448C74.681,11.51 74.213,10.918 73.469,10.918C72.721,10.918 72.266,11.497 72.266,12.444L72.266,12.444Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M79.23,12.444C79.224,12.377 79.22,12.309 79.22,12.241C79.22,11.071 80.183,10.108 81.354,10.108C82.524,10.108 83.487,11.071 83.487,12.241C83.487,12.309 83.484,12.377 83.477,12.444C83.484,12.513 83.487,12.582 83.487,12.651C83.487,13.822 82.524,14.785 81.354,14.785C80.183,14.785 79.22,13.822 79.22,12.651C79.22,12.582 79.223,12.513 79.23,12.444ZM82.563,12.444C82.563,11.468 82.125,10.897 81.355,10.897C80.583,10.897 80.148,11.468 80.148,12.444C80.148,13.428 80.583,13.995 81.355,13.995C82.125,13.995 82.563,13.424 82.563,12.444Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M84.669,10.195L85.525,10.195L85.525,10.91L85.591,10.91C85.822,10.385 86.363,10.061 86.935,10.108C86.972,10.105 87.008,10.104 87.045,10.104C87.848,10.104 88.509,10.765 88.509,11.568C88.509,11.64 88.504,11.712 88.494,11.783L88.494,14.698L87.605,14.698L87.605,12.006C87.605,11.282 87.291,10.922 86.633,10.922C86.617,10.922 86.601,10.921 86.585,10.921C86.019,10.921 85.552,11.388 85.552,11.954C85.552,11.991 85.554,12.027 85.558,12.063L85.558,14.698L84.669,14.698L84.669,10.195Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M93.515,9.074L93.515,10.215L94.491,10.215L94.491,10.964L93.515,10.964L93.515,13.279C93.515,13.751 93.709,13.958 94.152,13.958C94.265,13.957 94.378,13.95 94.491,13.937L94.491,14.677C94.331,14.706 94.169,14.721 94.007,14.723C93.019,14.723 92.626,14.375 92.626,13.507L92.626,10.964L91.911,10.964L91.911,10.215L92.626,10.215L92.626,9.074L93.515,9.074Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M95.705,8.437L96.585,8.437L96.585,10.918L96.656,10.918C96.897,10.388 97.448,10.065 98.029,10.112C98.056,10.11 98.082,10.11 98.109,10.11C98.923,10.11 99.593,10.779 99.593,11.593C99.593,11.659 99.588,11.725 99.58,11.791L99.58,14.698L98.69,14.698L98.69,12.01C98.69,11.291 98.355,10.926 97.727,10.926C97.699,10.924 97.67,10.923 97.641,10.923C97.064,10.923 96.589,11.398 96.589,11.975C96.589,12.006 96.591,12.037 96.593,12.068L96.593,14.698L95.705,14.698L95.705,8.437Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M104.761,13.482C104.513,14.328 103.687,14.88 102.81,14.785C102.792,14.785 102.774,14.785 102.756,14.785C101.634,14.785 100.711,13.862 100.711,12.74C100.711,12.647 100.717,12.553 100.73,12.46C100.718,12.369 100.712,12.277 100.712,12.185C100.712,11.045 101.649,10.108 102.788,10.108C102.794,10.108 102.8,10.108 102.806,10.108C104.059,10.108 104.815,10.964 104.815,12.378L104.815,12.688L101.635,12.688L101.635,12.738C101.632,12.771 101.631,12.804 101.631,12.838C101.631,13.491 102.168,14.028 102.821,14.028C102.826,14.028 102.83,14.028 102.834,14.028C103.268,14.08 103.693,13.864 103.906,13.482L104.761,13.482ZM101.635,12.031L103.91,12.031C103.912,12.004 103.913,11.977 103.913,11.95C103.913,11.354 103.422,10.864 102.826,10.864C102.818,10.864 102.81,10.864 102.801,10.864C102.796,10.864 102.792,10.864 102.787,10.864C102.155,10.864 101.635,11.384 101.635,12.016C101.635,12.021 101.635,12.026 101.635,12.031L101.635,12.031Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
BIN
.github/assets/badge-desktop.png
vendored
|
Before Width: | Height: | Size: 19 KiB |
BIN
.github/assets/badge-flathub.png
vendored
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
27
.github/assets/badge-flathub.svg
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 300 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;">
|
||||
<g transform="matrix(1,0,0,1,-348.1,-151.685)">
|
||||
<path d="M646.159,168.627C646.159,159.796 638.99,152.627 630.159,152.627L365.042,152.627C356.211,152.627 349.042,159.796 349.042,168.627L349.042,234.41C349.042,243.241 356.211,250.41 365.042,250.41L630.159,250.41C638.99,250.41 646.159,243.241 646.159,234.41L646.159,168.627Z" style="stroke:rgb(136,138,133);stroke-width:1.88px;"/>
|
||||
<g>
|
||||
<path d="M464.084,188.646C467.784,188.646 469.925,186.351 469.925,182.556C469.925,178.778 467.785,176.501 464.209,176.501L460.153,176.501L460.153,188.646L464.084,188.646ZM461.985,187.045L461.985,178.102L464.096,178.102C466.753,178.102 468.123,179.692 468.123,182.556C468.123,185.432 466.753,187.045 463.983,187.045L461.985,187.045ZM478.644,188.83C481.212,188.83 482.89,186.95 482.89,184.133C482.89,181.299 481.212,179.419 478.644,179.419C476.077,179.419 474.398,181.299 474.398,184.133C474.398,186.95 476.077,188.83 478.644,188.83ZM478.65,187.341C476.972,187.341 476.19,185.877 476.19,184.127C476.19,182.384 476.972,180.901 478.65,180.901C480.317,180.901 481.1,182.384 481.1,184.127C481.1,185.877 480.317,187.341 478.65,187.341ZM489.308,188.646L491.111,188.646L492.961,182.07L493.097,182.07L494.947,188.646L496.757,188.646L499.431,179.537L497.598,179.537L495.825,186.197L495.736,186.197L493.957,179.537L492.125,179.537L490.334,186.227L490.245,186.227L488.46,179.537L486.628,179.537L489.308,188.646ZM505.63,183.238C505.63,181.785 506.519,180.955 507.752,180.955C508.956,180.955 509.685,181.743 509.685,183.065L509.685,188.645L511.458,188.645L511.458,182.852C511.458,180.599 510.219,179.419 508.358,179.419C506.988,179.419 506.092,180.053 505.671,181.019L505.558,181.019L505.558,179.537L503.856,179.537L503.856,188.646L505.629,188.646L505.63,183.238ZM518.35,176.501L516.578,176.501L516.578,188.646L518.351,188.646L518.35,176.501ZM527.319,188.83C529.887,188.83 531.565,186.95 531.565,184.133C531.565,181.299 529.887,179.419 527.319,179.419C524.751,179.419 523.073,181.299 523.073,184.133C523.073,186.95 524.751,188.83 527.319,188.83ZM527.325,187.341C525.647,187.341 524.864,185.877 524.864,184.127C524.864,182.384 525.647,180.901 527.324,180.901C528.991,180.901 529.774,182.384 529.774,184.127C529.774,185.877 528.991,187.341 527.324,187.341L527.325,187.341ZM538.937,188.848C540.444,188.848 541.292,188.083 541.63,187.401L541.7,187.401L541.7,188.646L543.432,188.646L543.432,182.597C543.432,179.947 541.345,179.419 539.898,179.419C538.25,179.419 536.732,180.083 536.138,181.743L537.805,182.123C538.065,181.477 538.73,180.853 539.922,180.853C541.066,180.853 541.653,181.453 541.653,182.485L541.653,182.526C541.653,183.173 540.989,183.161 539.353,183.35C537.627,183.552 535.86,184.003 535.86,186.072C535.86,187.863 537.206,188.848 538.938,188.848L538.937,188.848ZM539.323,187.424C538.321,187.424 537.597,186.974 537.597,186.096C537.597,185.147 538.439,184.809 539.465,184.673C540.04,184.596 541.405,184.442 541.659,184.187L541.659,185.361C541.659,186.44 540.799,187.424 539.323,187.424ZM551.95,188.824C553.61,188.824 554.262,187.81 554.583,187.229L554.731,187.229L554.731,188.646L556.462,188.646L556.462,176.5L554.689,176.5L554.689,181.013L554.583,181.013C554.263,180.45 553.658,179.418 551.962,179.418C549.762,179.418 548.142,181.155 548.142,184.108C548.142,187.056 549.738,188.823 551.95,188.823L551.95,188.824ZM552.341,187.312C550.758,187.312 549.933,185.918 549.933,184.092C549.933,182.283 550.74,180.925 552.341,180.925C553.889,180.925 554.719,182.188 554.719,184.092C554.719,186.007 553.871,187.312 552.341,187.312ZM572.744,188.83C575.312,188.83 576.99,186.95 576.99,184.133C576.99,181.299 575.312,179.419 572.744,179.419C570.176,179.419 568.498,181.299 568.498,184.133C568.498,186.95 570.176,188.83 572.744,188.83ZM572.75,187.341C571.072,187.341 570.289,185.877 570.289,184.127C570.289,182.384 571.072,180.901 572.749,180.901C574.416,180.901 575.199,182.384 575.199,184.127C575.199,185.877 574.416,187.341 572.749,187.341L572.75,187.341ZM583.485,183.238C583.485,181.785 584.375,180.955 585.608,180.955C586.812,180.955 587.541,181.743 587.541,183.065L587.541,188.645L589.314,188.645L589.314,182.852C589.314,180.599 588.074,179.419 586.213,179.419C584.843,179.419 583.947,180.053 583.526,181.019L583.414,181.019L583.414,179.537L581.712,179.537L581.712,188.646L583.485,188.646L583.485,183.238Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M460.153,222.645L464.983,222.645L464.983,211.32L476.06,211.32L476.06,207.272L464.982,207.272L464.982,200.034L477.232,200.034L477.232,195.986L460.152,195.986L460.153,222.645Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<rect x="481.41" y="195.986" width="4.712" height="26.66" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M496.718,223.049C499.855,223.049 501.73,221.578 502.588,219.899L502.745,219.899L502.745,222.645L507.275,222.645L507.275,209.263C507.275,203.978 502.966,202.39 499.152,202.39C494.948,202.39 491.719,204.265 490.678,207.91L495.078,208.535C495.546,207.168 496.874,205.996 499.178,205.996C501.365,205.996 502.563,207.116 502.563,209.081L502.563,209.159C502.563,210.513 501.143,210.579 497.616,210.956C493.737,211.372 490.027,212.531 490.027,217.035C490.027,220.965 492.904,223.049 496.718,223.049ZM497.942,219.586C495.976,219.586 494.57,218.688 494.57,216.956C494.57,215.147 496.145,214.392 498.254,214.093C499.491,213.923 501.964,213.611 502.576,213.117L502.576,215.473C502.576,217.699 500.779,219.586 497.942,219.586Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M522.01,202.65L518.066,202.65L518.066,197.86L513.354,197.86L513.354,202.65L510.516,202.65L510.516,206.296L513.354,206.296L513.354,217.412C513.328,221.174 516.062,223.022 519.602,222.918C520.943,222.879 521.867,222.619 522.375,222.45L521.581,218.766C521.321,218.831 520.787,218.948 520.201,218.948C519.016,218.948 518.066,218.532 518.066,216.631L518.066,206.296L522.01,206.296L522.01,202.65Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M531.083,210.93C531.083,208.04 532.88,206.374 535.405,206.374C537.878,206.374 539.336,207.949 539.336,210.644L539.336,222.645L544.049,222.645L544.049,209.915C544.049,205.085 541.315,202.39 537.162,202.39C534.09,202.39 532.138,203.783 531.214,206.048L530.979,206.048L530.979,195.986L526.371,195.986L526.371,222.646L531.083,222.646L531.083,210.93Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M561.583,214.236C561.583,217.282 559.409,218.792 557.326,218.792C555.061,218.792 553.551,217.191 553.551,214.652L553.551,202.651L548.839,202.651L548.839,215.381C548.839,220.185 551.572,222.905 555.504,222.905C558.498,222.905 560.606,221.33 561.518,219.091L561.726,219.091L561.726,222.645L566.295,222.645L566.295,202.651L561.583,202.651L561.583,214.236Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M571.346,222.645L575.98,222.645L575.98,219.495L576.253,219.495C576.995,220.953 578.544,222.997 581.981,222.997C586.693,222.997 590.221,219.261 590.221,212.674C590.221,206.009 586.589,202.39 581.968,202.39C578.44,202.39 576.969,204.512 576.253,205.957L576.058,205.957L576.058,195.986L571.346,195.986L571.346,222.645ZM575.966,212.648C575.966,208.768 577.633,206.256 580.666,206.256C583.803,206.256 585.417,208.925 585.417,212.648C585.417,216.397 583.777,219.13 580.666,219.13C577.659,219.13 575.966,216.527 575.966,212.648Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1.24662,0,0,1.24662,192.661,67.896)">
|
||||
<circle cx="166.688" cy="95.647" r="9.048" style="fill:white;"/>
|
||||
<path d="M174.826,111.309C174.826,109.371 173.253,107.798 171.315,107.798L161.925,107.798C159.987,107.798 158.414,109.371 158.414,111.309L158.414,120.699C158.414,122.637 159.987,124.21 161.925,124.21L171.315,124.21C173.253,124.21 174.826,122.637 174.826,120.699L174.826,111.309Z" style="fill:white;"/>
|
||||
<g transform="matrix(0.925902,0.53457,0.53457,-0.925902,99.8266,110.693)">
|
||||
<path d="M69.514,58.833L57.486,58.833C56.614,58.833 55.805,58.366 55.369,57.611C54.933,56.856 54.933,55.922 55.369,55.167L56.259,53.625L61.383,44.75C61.819,43.994 62.628,43.527 63.5,43.527C64.372,43.527 65.181,43.994 65.617,44.75L66.507,46.292L71.631,55.167C72.067,55.922 72.067,56.856 71.631,57.611C71.195,58.366 70.386,58.833 69.514,58.833Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<path d="M194.992,116.11C194.992,116.99 194.284,117.697 193.404,117.697L180.704,117.697C179.824,117.697 179.117,116.989 179.117,116.11C179.117,115.23 179.825,114.522 180.704,114.522L193.404,114.522C194.284,114.522 194.992,115.23 194.992,116.11ZM187.054,108.172C187.934,108.172 188.642,108.88 188.642,109.76L188.642,122.46C188.642,123.34 187.934,124.047 187.054,124.047C186.174,124.047 185.467,123.339 185.467,122.46L185.467,109.76C185.467,108.88 186.175,108.172 187.054,108.172Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.1 KiB |
BIN
.github/assets/badge-google-play.png
vendored
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 7.0 KiB |
25
.github/assets/badge-google-play.svg
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 563 167" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-miterlimit:2;">
|
||||
<g transform="matrix(4.16667,0,0,4.16667,0,-4)">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M130,41L4.999,41C2.249,41 0,38.75 0,36L0,6C0,3.25 2.249,1 4.999,1L130,1C132.75,1 135,3.25 135,6L135,36C135,38.75 132.75,41 130,41Z" style="fill:rgb(3,4,4);fill-rule:nonzero;"/>
|
||||
<path d="M130,1L4.999,1C2.249,1 0,3.25 0,6L0,36C0,38.75 2.249,41 4.999,41L130,41C132.75,41 135,38.75 135,36L135,6C135,3.25 132.75,1 130,1ZM130,1.8C132.316,1.8 134.2,3.685 134.2,6L134.2,36C134.2,38.316 132.316,40.201 130,40.201L4.999,40.201C2.684,40.201 0.8,38.316 0.8,36L0.8,6C0.8,3.685 2.684,1.8 4.999,1.8L130,1.8Z" style="fill:rgb(167,165,166);fill-rule:nonzero;"/>
|
||||
<path d="M47.376,10.791L44.468,10.791L44.468,11.512L46.647,11.512C46.588,12.098 46.353,12.559 45.96,12.894C45.566,13.229 45.063,13.397 44.468,13.397C43.814,13.397 43.261,13.171 42.809,12.718C42.365,12.257 42.138,11.688 42.138,11C42.138,10.313 42.365,9.743 42.809,9.282C43.261,8.83 43.814,8.604 44.468,8.604C44.803,8.604 45.122,8.662 45.415,8.788C45.708,8.914 45.943,9.09 46.127,9.316L46.68,8.763C46.429,8.478 46.11,8.26 45.717,8.101C45.323,7.942 44.912,7.866 44.468,7.866C43.596,7.866 42.859,8.168 42.256,8.771C41.652,9.375 41.351,10.12 41.351,11C41.351,11.88 41.652,12.626 42.256,13.229C42.859,13.833 43.596,14.134 44.468,14.134C45.381,14.134 46.11,13.841 46.672,13.246C47.166,12.752 47.418,12.081 47.418,11.243C47.418,11.101 47.401,10.95 47.376,10.791Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M48.524,8L48.524,14L52.027,14L52.027,13.263L49.295,13.263L49.295,11.361L51.758,11.361L51.758,10.64L49.295,10.64L49.295,8.738L52.027,8.738L52.027,8L48.524,8Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M56.953,8.738L56.953,8L52.83,8L52.83,8.738L54.506,8.738L54.506,14L55.277,14L55.277,8.738L56.953,8.738Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<rect x="59.937" y="8" width="0.771" height="6" style="fill:white;"/>
|
||||
<path d="M65.803,8.738L65.803,8L61.68,8L61.68,8.738L63.356,8.738L63.356,14L64.127,14L64.127,8.738L65.803,8.738Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M73.605,8.78C73.01,8.168 72.281,7.866 71.409,7.866C70.538,7.866 69.808,8.168 69.213,8.771C68.618,9.366 68.325,10.112 68.325,11C68.325,11.889 68.618,12.634 69.213,13.229C69.808,13.833 70.538,14.134 71.409,14.134C72.272,14.134 73.01,13.833 73.605,13.229C74.2,12.634 74.493,11.889 74.493,11C74.493,10.12 74.2,9.375 73.605,8.78ZM69.767,9.282C70.211,8.83 70.755,8.604 71.409,8.604C72.063,8.604 72.607,8.83 73.043,9.282C73.487,9.727 73.705,10.305 73.705,11C73.705,11.696 73.487,12.274 73.043,12.718C72.607,13.171 72.063,13.397 71.409,13.397C70.755,13.397 70.211,13.171 69.767,12.718C69.331,12.266 69.113,11.696 69.113,11C69.113,10.305 69.331,9.735 69.767,9.282Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M76.345,10.263L76.312,9.106L76.345,9.106L79.396,14L80.2,14L80.2,8L79.429,8L79.429,11.512L79.463,12.668L79.429,12.668L76.513,8L75.575,8L75.575,14L76.345,14L76.345,10.263Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M47.376,10.791L44.468,10.791L44.468,11.512L46.647,11.512C46.588,12.098 46.353,12.559 45.96,12.894C45.566,13.229 45.063,13.397 44.468,13.397C43.814,13.397 43.261,13.171 42.809,12.718C42.365,12.257 42.138,11.688 42.138,11C42.138,10.313 42.365,9.743 42.809,9.282C43.261,8.83 43.814,8.604 44.468,8.604C44.803,8.604 45.122,8.662 45.415,8.788C45.708,8.914 45.943,9.09 46.127,9.316L46.68,8.763C46.429,8.478 46.11,8.26 45.717,8.101C45.323,7.942 44.912,7.866 44.468,7.866C43.596,7.866 42.859,8.168 42.256,8.771C41.652,9.375 41.351,10.12 41.351,11C41.351,11.88 41.652,12.626 42.256,13.229C42.859,13.833 43.596,14.134 44.468,14.134C45.381,14.134 46.11,13.841 46.672,13.246C47.166,12.752 47.418,12.081 47.418,11.243C47.418,11.101 47.401,10.95 47.376,10.791ZM48.524,8L48.524,14L52.027,14L52.027,13.263L49.295,13.263L49.295,11.361L51.758,11.361L51.758,10.64L49.295,10.64L49.295,8.738L52.027,8.738L52.027,8L48.524,8ZM56.953,8.738L56.953,8L52.831,8L52.831,8.738L54.507,8.738L54.507,14L55.277,14L55.277,8.738L56.953,8.738ZM60.708,8L59.937,8L59.937,14L60.708,14L60.708,8ZM65.803,8.738L65.803,8L61.68,8L61.68,8.738L63.356,8.738L63.356,14L64.127,14L64.127,8.738L65.803,8.738ZM73.605,8.78C73.01,8.168 72.281,7.866 71.409,7.866C70.537,7.866 69.808,8.168 69.213,8.771C68.618,9.366 68.325,10.112 68.325,11C68.325,11.889 68.618,12.634 69.213,13.229C69.808,13.833 70.537,14.134 71.409,14.134C72.272,14.134 73.01,13.833 73.605,13.229C74.2,12.634 74.493,11.889 74.493,11C74.493,10.12 74.2,9.375 73.605,8.78ZM69.767,9.282C70.211,8.83 70.755,8.604 71.409,8.604C72.063,8.604 72.607,8.83 73.043,9.282C73.487,9.727 73.705,10.305 73.705,11C73.705,11.696 73.487,12.274 73.043,12.718C72.607,13.171 72.063,13.397 71.409,13.397C70.755,13.397 70.211,13.171 69.767,12.718C69.331,12.266 69.113,11.696 69.113,11C69.113,10.305 69.331,9.735 69.767,9.282ZM76.346,10.263L76.312,9.106L76.346,9.106L79.396,14L80.2,14L80.2,8L79.429,8L79.429,11.512L79.463,12.668L79.429,12.668L76.513,8L75.575,8L75.575,14L76.346,14L76.346,10.263Z" style="fill:none;stroke:white;stroke-width:0.2px;"/>
|
||||
<path d="M106.936,31.001L108.802,31.001L108.802,18.499L106.936,18.499L106.936,31.001ZM123.743,23.002L121.604,28.422L121.54,28.422L119.32,23.002L117.31,23.002L120.64,30.578L118.741,34.791L120.687,34.791L125.818,23.002L123.743,23.002ZM113.16,29.581C112.55,29.581 111.697,29.275 111.697,28.519C111.697,27.554 112.759,27.184 113.675,27.184C114.495,27.184 114.882,27.361 115.38,27.602C115.235,28.76 114.238,29.581 113.16,29.581ZM113.386,22.729C112.035,22.729 110.636,23.324 110.057,24.643L111.713,25.334C112.067,24.643 112.726,24.418 113.418,24.418C114.383,24.418 115.364,24.997 115.38,26.026L115.38,26.155C115.042,25.962 114.318,25.672 113.434,25.672C111.648,25.672 109.831,26.653 109.831,28.487C109.831,30.16 111.295,31.237 112.935,31.237C114.189,31.237 114.882,30.674 115.315,30.015L115.38,30.015L115.38,30.98L117.182,30.98L117.182,26.187C117.182,23.967 115.524,22.729 113.386,22.729ZM101.854,24.524L99.2,24.524L99.2,20.239L101.854,20.239C103.249,20.239 104.041,21.394 104.041,22.382C104.041,23.351 103.249,24.524 101.854,24.524ZM101.806,18.499L97.334,18.499L97.334,31.001L99.2,31.001L99.2,26.264L101.806,26.264C103.874,26.264 105.907,24.767 105.907,22.382C105.907,19.997 103.874,18.499 101.806,18.499ZM77.424,29.583C76.135,29.583 75.056,28.503 75.056,27.022C75.056,25.523 76.135,24.428 77.424,24.428C78.697,24.428 79.696,25.523 79.696,27.022C79.696,28.503 78.697,29.583 77.424,29.583ZM79.567,23.703L79.502,23.703C79.083,23.203 78.278,22.752 77.263,22.752C75.136,22.752 73.187,24.621 73.187,27.022C73.187,29.406 75.136,31.258 77.263,31.258C78.278,31.258 79.083,30.807 79.502,30.292L79.567,30.292L79.567,30.904C79.567,32.531 78.697,33.401 77.295,33.401C76.152,33.401 75.443,32.58 75.153,31.887L73.526,32.563C73.993,33.691 75.233,35.077 77.295,35.077C79.487,35.077 81.339,33.788 81.339,30.646L81.339,23.01L79.567,23.01L79.567,23.703ZM82.628,31.001L84.497,31.001L84.497,18.499L82.628,18.499L82.628,31.001ZM87.251,26.876C87.204,25.233 88.525,24.396 89.475,24.396C90.216,24.396 90.844,24.766 91.054,25.297L87.251,26.876ZM93.051,25.459C92.697,24.508 91.618,22.752 89.411,22.752C87.219,22.752 85.399,24.476 85.399,27.005C85.399,29.39 87.204,31.258 89.62,31.258C91.569,31.258 92.697,30.066 93.165,29.374L91.714,28.407C91.231,29.116 90.571,29.583 89.62,29.583C88.669,29.583 87.993,29.148 87.558,28.294L93.245,25.942L93.051,25.459ZM47.743,24.057L47.743,25.861L52.061,25.861C51.932,26.876 51.594,27.617 51.078,28.133C50.45,28.761 49.467,29.454 47.743,29.454C45.085,29.454 43.007,27.312 43.007,24.653C43.007,21.995 45.085,19.852 47.743,19.852C49.177,19.852 50.224,20.416 50.998,21.141L52.27,19.868C51.191,18.837 49.757,18.048 47.743,18.048C44.102,18.048 41.041,21.012 41.041,24.653C41.041,28.294 44.102,31.258 47.743,31.258C49.708,31.258 51.191,30.614 52.351,29.406C53.543,28.213 53.914,26.538 53.914,25.185C53.914,24.766 53.881,24.379 53.817,24.057L47.743,24.057ZM58.822,29.583C57.533,29.583 56.421,28.52 56.421,27.005C56.421,25.475 57.533,24.428 58.822,24.428C60.111,24.428 61.223,25.475 61.223,27.005C61.223,28.52 60.111,29.583 58.822,29.583ZM58.822,22.752C56.47,22.752 54.553,24.541 54.553,27.005C54.553,29.454 56.47,31.258 58.822,31.258C61.174,31.258 63.091,29.454 63.091,27.005C63.091,24.541 61.174,22.752 58.822,22.752ZM68.135,29.583C66.847,29.583 65.735,28.52 65.735,27.005C65.735,25.475 66.847,24.428 68.135,24.428C69.424,24.428 70.536,25.475 70.536,27.005C70.536,28.52 69.424,29.583 68.135,29.583ZM68.135,22.752C65.783,22.752 63.866,24.541 63.866,27.005C63.866,29.454 65.783,31.258 68.135,31.258C70.488,31.258 72.405,29.454 72.405,27.005C72.405,24.541 70.488,22.752 68.135,22.752Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M20.717,20.425L10.07,31.725C10.071,31.727 10.071,31.729 10.072,31.731C10.398,32.959 11.519,33.862 12.849,33.862C13.381,33.862 13.88,33.718 14.308,33.466L14.342,33.446L26.326,26.531L20.717,20.425Z" style="fill:rgb(235,67,53);fill-rule:nonzero;"/>
|
||||
<path d="M31.488,18.501L31.478,18.494L26.304,15.494L20.475,20.681L26.324,26.529L31.471,23.56C32.373,23.073 32.985,22.122 32.985,21.025C32.985,19.936 32.381,18.989 31.488,18.501Z" style="fill:rgb(250,188,19);fill-rule:nonzero;"/>
|
||||
<path d="M10.07,10.278C10.006,10.514 9.972,10.761 9.972,11.018L9.972,30.985C9.972,31.242 10.005,31.49 10.07,31.725L21.083,20.714L10.07,10.278Z" style="fill:rgb(84,125,191);fill-rule:nonzero;"/>
|
||||
<path d="M20.795,21.001L26.306,15.492L14.335,8.552C13.9,8.291 13.393,8.141 12.849,8.141C11.519,8.141 10.397,9.046 10.07,10.275L10.07,10.278L20.795,21.001Z" style="fill:rgb(48,168,81);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
BIN
.github/assets/badge-mac-app-store.png
vendored
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
52
.github/assets/badge-mac-app-store.svg
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 157 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M146.571,0L9.535,0C9.168,0 8.806,0 8.44,0.002C8.134,0.004 7.83,0.01 7.521,0.015C6.85,0.023 6.18,0.082 5.517,0.191C4.856,0.304 4.215,0.515 3.616,0.818C3.019,1.124 2.472,1.522 1.998,1.997C1.52,2.471 1.122,3.018 0.819,3.618C0.515,4.217 0.305,4.859 0.194,5.521C0.083,6.183 0.023,6.853 0.015,7.523C0.006,7.83 0.005,8.138 0,8.444L0,31.559C0.005,31.869 0.006,32.17 0.015,32.481C0.023,33.151 0.083,33.821 0.194,34.482C0.304,35.145 0.515,35.788 0.819,36.387C1.122,36.985 1.52,37.53 1.998,38.001C2.471,38.478 3.017,38.876 3.616,39.18C4.215,39.484 4.855,39.697 5.517,39.811C6.18,39.919 6.85,39.978 7.521,39.987C7.83,39.994 8.134,39.998 8.44,39.998C8.806,40 9.168,40 9.535,40L146.571,40C146.931,40 147.296,40 147.655,39.998C147.96,39.998 148.272,39.994 148.577,39.987C149.247,39.979 149.916,39.92 150.577,39.81C151.241,39.696 151.884,39.483 152.485,39.18C153.084,38.876 153.63,38.478 154.103,38.001C154.579,37.528 154.977,36.983 155.284,36.387C155.586,35.787 155.795,35.145 155.903,34.482C156.015,33.821 156.077,33.151 156.089,32.481C156.093,32.17 156.093,31.869 156.093,31.559C156.101,31.195 156.101,30.834 156.101,30.465L156.101,9.536C156.101,9.17 156.101,8.807 156.093,8.444C156.093,8.138 156.093,7.83 156.089,7.523C156.077,6.852 156.015,6.183 155.903,5.521C155.795,4.859 155.586,4.217 155.284,3.618C154.667,2.415 153.688,1.436 152.485,0.818C151.884,0.516 151.241,0.304 150.577,0.191C149.916,0.081 149.247,0.022 148.577,0.015C148.272,0.01 147.96,0.004 147.655,0.002C147.296,-0 146.931,-0 146.571,-0L146.571,0Z" style="fill:rgb(166,166,166);fill-rule:nonzero;"/>
|
||||
<path d="M8.445,39.125C8.14,39.125 7.843,39.121 7.541,39.114C6.914,39.106 6.29,39.052 5.671,38.951C5.095,38.852 4.537,38.667 4.015,38.403C3.498,38.142 3.026,37.798 2.618,37.387C2.204,36.98 1.859,36.508 1.597,35.99C1.333,35.469 1.149,34.91 1.054,34.333C0.951,33.713 0.896,33.086 0.888,32.458C0.881,32.247 0.873,31.545 0.873,31.545L0.873,8.444C0.873,8.444 0.882,7.753 0.888,7.55C0.895,6.922 0.951,6.297 1.053,5.678C1.149,5.099 1.332,4.539 1.597,4.016C1.857,3.498 2.2,3.026 2.612,2.618C3.023,2.206 3.496,1.861 4.014,1.595C4.535,1.332 5.092,1.149 5.667,1.051C6.288,0.95 6.915,0.895 7.543,0.887L8.445,0.875L147.65,0.875L148.563,0.888C149.186,0.895 149.807,0.949 150.422,1.05C151.003,1.149 151.566,1.334 152.093,1.598C153.13,2.133 153.975,2.979 154.508,4.018C154.768,4.538 154.949,5.094 155.043,5.667C155.147,6.291 155.205,6.922 155.217,7.554C155.22,7.837 155.22,8.142 155.22,8.444C155.228,8.819 155.228,9.176 155.228,9.536L155.228,30.465C155.228,30.828 155.228,31.183 155.22,31.54C155.22,31.865 155.22,32.163 155.216,32.47C155.204,33.091 155.147,33.71 155.045,34.323C154.952,34.904 154.769,35.468 154.505,35.993C154.241,36.506 153.898,36.973 153.489,37.379C153.081,37.793 152.608,38.138 152.09,38.401C151.565,38.667 151.002,38.853 150.422,38.951C149.804,39.052 149.179,39.107 148.553,39.114C148.26,39.121 147.953,39.125 147.655,39.125L146.571,39.127L8.445,39.125Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g id="_Group_">
|
||||
<g id="_Group_2_" serif:id="_Group_2">
|
||||
<g id="_Group_3_" serif:id="_Group_3">
|
||||
<g id="_Group_4_" serif:id="_Group_4">
|
||||
<path id="_Path_" d="M24.769,20.301C24.791,18.606 25.682,17.036 27.125,16.149C26.21,14.842 24.729,14.041 23.134,13.991C21.455,13.815 19.827,14.996 18.971,14.996C18.099,14.996 16.782,14.008 15.363,14.038C13.498,14.098 11.797,15.135 10.89,16.766C8.956,20.114 10.399,25.035 12.251,27.742C13.178,29.067 14.261,30.547 15.679,30.495C17.066,30.437 17.584,29.61 19.258,29.61C20.917,29.61 21.403,30.495 22.849,30.462C24.338,30.437 25.275,29.13 26.17,27.792C26.836,26.848 27.348,25.804 27.688,24.7C25.924,23.954 24.771,22.216 24.769,20.301Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path id="_Path_2_" serif:id="_Path_2" d="M22.037,12.211C22.848,11.237 23.249,9.984 23.152,8.72C21.912,8.85 20.766,9.443 19.944,10.38C19.132,11.304 18.721,12.514 18.8,13.741C20.056,13.754 21.25,13.189 22.037,12.211Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M46.149,30.496L46.149,21.356L46.088,21.356L42.345,30.401L40.917,30.401L37.164,21.356L37.104,21.356L37.104,30.496L35.348,30.496L35.348,18.078L37.578,18.078L41.596,27.88L41.665,27.88L45.675,18.078L47.913,18.078L47.913,30.496L46.149,30.496Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M49.396,27.923C49.396,26.34 50.609,25.384 52.761,25.255L55.24,25.117L55.24,24.429C55.24,23.422 54.577,22.853 53.449,22.853C52.588,22.739 51.768,23.289 51.547,24.128L49.809,24.128C49.861,22.492 51.384,21.331 53.501,21.331C55.661,21.331 57.089,22.51 57.089,24.291L57.089,30.496L55.308,30.496L55.308,29.007L55.265,29.007C54.685,30.034 53.587,30.666 52.408,30.651C52.319,30.66 52.23,30.664 52.14,30.664C50.636,30.664 49.398,29.427 49.396,27.923ZM55.24,27.105L55.24,26.408L53.01,26.546C51.9,26.615 51.272,27.097 51.272,27.871C51.272,28.663 51.926,29.18 52.924,29.18C52.973,29.183 53.022,29.185 53.071,29.185C54.227,29.185 55.191,28.261 55.24,27.105L55.24,27.105Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M64.893,24.558C64.726,23.536 63.791,22.805 62.759,22.889C61.331,22.889 60.384,24.085 60.384,25.97C60.384,27.897 61.339,29.059 62.776,29.059C63.795,29.161 64.729,28.443 64.893,27.433L66.683,27.433C66.453,29.367 64.699,30.787 62.759,30.608C60.177,30.608 58.491,28.844 58.491,25.97C58.491,23.155 60.177,21.331 62.742,21.331C64.696,21.165 66.451,22.608 66.667,24.558L64.893,24.558Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M78.759,27.14L74.026,27.14L72.889,30.496L70.884,30.496L75.368,18.078L77.451,18.078L81.934,30.496L79.895,30.496L78.759,27.14ZM74.516,25.591L78.268,25.591L76.419,20.144L76.367,20.144L74.516,25.591Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M91.617,25.97C91.617,28.783 90.111,30.591 87.838,30.591C86.666,30.652 85.556,30.035 84.99,29.007L84.947,29.007L84.947,33.491L83.088,33.491L83.088,21.442L84.887,21.442L84.887,22.948L84.921,22.948C85.513,21.926 86.623,21.31 87.804,21.348C90.102,21.348 91.617,23.164 91.617,25.97ZM89.707,25.97C89.707,24.137 88.759,22.932 87.314,22.932C85.894,22.932 84.939,24.162 84.939,25.97C84.939,27.794 85.894,29.016 87.314,29.016C88.759,29.016 89.707,27.819 89.707,25.97Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M101.582,25.97C101.582,28.783 100.076,30.591 97.803,30.591C96.631,30.652 95.521,30.035 94.955,29.007L94.912,29.007L94.912,33.491L93.053,33.491L93.053,21.442L94.852,21.442L94.852,22.948L94.886,22.948C95.478,21.926 96.588,21.31 97.769,21.348C100.067,21.348 101.582,23.164 101.582,25.97ZM99.671,25.97C99.671,24.137 98.724,22.932 97.279,22.932C95.859,22.932 94.904,24.162 94.904,25.97C94.904,27.794 95.859,29.016 97.279,29.016C98.724,29.016 99.671,27.819 99.671,25.97L99.671,25.97Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M108.168,27.036C108.305,28.268 109.502,29.076 111.136,29.076C112.703,29.076 113.83,28.268 113.83,27.157C113.83,26.193 113.15,25.616 111.541,25.221L109.931,24.833C107.651,24.282 106.592,23.216 106.592,21.485C106.592,19.343 108.459,17.871 111.111,17.871C113.735,17.871 115.534,19.343 115.594,21.485L113.718,21.485C113.606,20.246 112.582,19.498 111.084,19.498C109.587,19.498 108.563,20.255 108.563,21.356C108.563,22.234 109.217,22.751 110.818,23.146L112.186,23.482C114.734,24.085 115.792,25.108 115.792,26.925C115.792,29.248 113.942,30.703 110.999,30.703C108.245,30.703 106.385,29.282 106.265,27.036L108.168,27.036Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M119.803,19.3L119.803,21.442L121.525,21.442L121.525,22.914L119.803,22.914L119.803,27.905C119.803,28.681 120.148,29.042 120.905,29.042C121.109,29.038 121.313,29.024 121.516,28.999L121.516,30.462C121.176,30.525 120.83,30.554 120.484,30.548C118.651,30.548 117.936,29.859 117.936,28.103L117.936,22.914L116.62,22.914L116.62,21.442L117.936,21.442L117.936,19.3L119.803,19.3Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M122.521,25.97C122.521,23.121 124.199,21.331 126.815,21.331C129.44,21.331 131.11,23.121 131.11,25.97C131.11,28.826 129.449,30.608 126.815,30.608C124.182,30.608 122.521,28.826 122.521,25.97ZM129.216,25.97C129.216,24.016 128.321,22.862 126.815,22.862C125.309,22.862 124.414,24.024 124.414,25.97C124.414,27.932 125.309,29.076 126.815,29.076C128.321,29.076 129.216,27.932 129.216,25.97L129.216,25.97Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M132.643,21.442L134.416,21.442L134.416,22.983L134.459,22.983C134.705,21.995 135.618,21.309 136.636,21.348C136.85,21.347 137.064,21.37 137.273,21.417L137.273,23.155C137.003,23.073 136.721,23.035 136.438,23.043C136.413,23.042 136.387,23.041 136.362,23.041C135.335,23.041 134.489,23.887 134.489,24.914C134.489,24.985 134.493,25.056 134.501,25.126L134.501,30.496L132.643,30.496L132.643,21.442Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M145.84,27.837C145.59,29.48 143.99,30.608 141.942,30.608C139.308,30.608 137.673,28.844 137.673,26.013C137.673,23.173 139.317,21.331 141.864,21.331C144.369,21.331 145.944,23.052 145.944,25.797L145.944,26.434L139.549,26.434L139.549,26.546C139.543,26.615 139.54,26.684 139.54,26.754C139.54,28.047 140.605,29.112 141.898,29.112C141.927,29.112 141.956,29.111 141.985,29.11C142.884,29.195 143.738,28.675 144.076,27.837L145.84,27.837ZM139.558,25.135L144.084,25.135C144.087,25.094 144.088,25.054 144.088,25.014C144.088,23.819 143.105,22.836 141.911,22.836C141.895,22.836 141.879,22.837 141.864,22.837C141.859,22.837 141.855,22.837 141.85,22.837C140.593,22.837 139.558,23.872 139.558,25.129C139.558,25.131 139.558,25.133 139.558,25.135L139.558,25.135Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="_Group_5_" serif:id="_Group_5">
|
||||
<g>
|
||||
<path d="M37.826,8.731C37.889,8.726 37.952,8.724 38.015,8.724C39.463,8.724 40.655,9.916 40.655,11.364C40.655,11.475 40.648,11.586 40.634,11.696C40.634,13.602 39.604,14.698 37.826,14.698L35.671,14.698L35.671,8.731L37.826,8.731ZM36.598,13.854L37.723,13.854C37.76,13.856 37.797,13.857 37.835,13.857C38.864,13.857 39.71,13.011 39.71,11.981C39.71,11.89 39.704,11.799 39.69,11.708C39.702,11.623 39.708,11.538 39.708,11.452C39.708,10.42 38.859,9.571 37.827,9.571C37.792,9.571 37.757,9.572 37.723,9.574L36.598,9.574L36.598,13.854Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M41.681,12.444C41.674,12.377 41.671,12.309 41.671,12.241C41.671,11.071 42.634,10.108 43.804,10.108C44.974,10.108 45.937,11.071 45.937,12.241C45.937,12.309 45.934,12.377 45.928,12.444C45.934,12.513 45.938,12.582 45.938,12.651C45.938,13.822 44.975,14.785 43.804,14.785C42.634,14.785 41.671,13.822 41.671,12.651C41.671,12.582 41.674,12.513 41.681,12.444ZM45.014,12.444C45.014,11.468 44.575,10.897 43.806,10.897C43.033,10.897 42.599,11.468 42.599,12.444C42.599,13.428 43.033,13.995 43.806,13.995C44.575,13.995 45.014,13.424 45.014,12.444L45.014,12.444Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M51.573,14.698L50.651,14.698L49.721,11.381L49.65,11.381L48.724,14.698L47.811,14.698L46.569,10.195L47.471,10.195L48.277,13.631L48.344,13.631L49.27,10.195L50.122,10.195L51.048,13.631L51.118,13.631L51.921,10.195L52.81,10.195L51.573,14.698Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M53.854,10.195L54.709,10.195L54.709,10.91L54.775,10.91C55.006,10.385 55.547,10.061 56.119,10.108C56.156,10.105 56.192,10.104 56.229,10.104C57.032,10.104 57.694,10.765 57.694,11.568C57.694,11.64 57.688,11.712 57.678,11.783L57.678,14.698L56.789,14.698L56.789,12.006C56.789,11.282 56.475,10.922 55.817,10.922C55.801,10.922 55.785,10.921 55.769,10.921C55.203,10.921 54.736,11.388 54.736,11.954C54.736,11.991 54.738,12.027 54.742,12.063L54.742,14.698L53.854,14.698L53.854,10.195Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<rect x="59.094" y="8.437" width="0.889" height="6.261" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M61.218,12.444C61.211,12.377 61.208,12.309 61.208,12.241C61.208,11.071 62.171,10.108 63.341,10.108C64.512,10.108 65.475,11.071 65.475,12.241C65.475,12.309 65.471,12.377 65.465,12.444C65.472,12.513 65.475,12.582 65.475,12.651C65.475,13.822 64.512,14.785 63.341,14.785C62.171,14.785 61.208,13.822 61.208,12.651C61.208,12.582 61.211,12.513 61.218,12.444ZM64.551,12.444C64.551,11.468 64.112,10.897 63.343,10.897C62.57,10.897 62.136,11.468 62.136,12.444C62.136,13.428 62.57,13.995 63.343,13.995C64.112,13.995 64.551,13.424 64.551,12.444L64.551,12.444Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M66.4,13.424C66.4,12.614 67.004,12.146 68.075,12.08L69.295,12.01L69.295,11.621C69.295,11.146 68.98,10.877 68.373,10.877C67.877,10.877 67.533,11.059 67.435,11.377L66.574,11.377C66.665,10.604 67.393,10.108 68.414,10.108C69.543,10.108 70.18,10.67 70.18,11.621L70.18,14.698L69.324,14.698L69.324,14.065L69.254,14.065C68.964,14.526 68.446,14.797 67.901,14.772C67.855,14.777 67.808,14.779 67.761,14.779C67.016,14.779 66.403,14.169 66.4,13.424ZM69.295,13.04L69.295,12.663L68.195,12.733C67.575,12.775 67.294,12.986 67.294,13.383C67.294,13.788 67.646,14.024 68.129,14.024C68.164,14.028 68.2,14.029 68.236,14.029C68.791,14.029 69.257,13.594 69.295,13.04Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M71.348,12.444C71.348,11.021 72.079,10.12 73.217,10.12C73.79,10.094 74.33,10.402 74.598,10.91L74.664,10.91L74.664,8.437L75.553,8.437L75.553,14.698L74.701,14.698L74.701,13.986L74.631,13.986C74.343,14.49 73.797,14.793 73.217,14.772C72.071,14.772 71.348,13.871 71.348,12.444ZM72.266,12.444C72.266,13.399 72.716,13.974 73.469,13.974C74.218,13.974 74.681,13.391 74.681,12.448C74.681,11.51 74.213,10.918 73.469,10.918C72.721,10.918 72.266,11.497 72.266,12.444L72.266,12.444Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M79.23,12.444C79.223,12.377 79.22,12.309 79.22,12.241C79.22,11.071 80.183,10.108 81.353,10.108C82.524,10.108 83.487,11.071 83.487,12.241C83.487,12.309 83.484,12.377 83.477,12.444C83.484,12.513 83.487,12.582 83.487,12.651C83.487,13.821 82.524,14.785 81.353,14.785C80.183,14.785 79.219,13.821 79.219,12.651C79.219,12.582 79.223,12.513 79.23,12.444ZM82.563,12.444C82.563,11.468 82.124,10.897 81.355,10.897C80.582,10.897 80.148,11.468 80.148,12.444C80.148,13.428 80.582,13.995 81.355,13.995C82.124,13.995 82.563,13.424 82.563,12.444L82.563,12.444Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M84.669,10.195L85.525,10.195L85.525,10.91L85.591,10.91C85.822,10.385 86.363,10.061 86.935,10.108C86.972,10.105 87.008,10.104 87.045,10.104C87.848,10.104 88.509,10.765 88.509,11.568C88.509,11.64 88.504,11.712 88.494,11.783L88.494,14.698L87.605,14.698L87.605,12.006C87.605,11.282 87.291,10.922 86.633,10.922C86.617,10.922 86.601,10.921 86.585,10.921C86.019,10.921 85.552,11.388 85.552,11.954C85.552,11.991 85.554,12.027 85.558,12.063L85.558,14.698L84.669,14.698L84.669,10.195Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M93.515,9.074L93.515,10.215L94.491,10.215L94.491,10.964L93.515,10.964L93.515,13.279C93.515,13.751 93.709,13.958 94.152,13.958C94.265,13.957 94.378,13.95 94.491,13.937L94.491,14.677C94.331,14.706 94.169,14.721 94.007,14.723C93.019,14.723 92.626,14.375 92.626,13.507L92.626,10.964L91.911,10.964L91.911,10.215L92.626,10.215L92.626,9.074L93.515,9.074Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M95.705,8.437L96.585,8.437L96.585,10.918L96.656,10.918C96.897,10.388 97.448,10.065 98.029,10.112C98.056,10.11 98.082,10.11 98.109,10.11C98.923,10.11 99.593,10.779 99.593,11.593C99.593,11.659 99.588,11.725 99.58,11.791L99.58,14.698L98.69,14.698L98.69,12.01C98.69,11.291 98.355,10.926 97.727,10.926C97.699,10.924 97.67,10.923 97.641,10.923C97.064,10.923 96.589,11.398 96.589,11.975C96.589,12.006 96.591,12.037 96.593,12.068L96.593,14.698L95.705,14.698L95.705,8.437Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M104.761,13.482C104.513,14.328 103.687,14.88 102.81,14.785C102.792,14.785 102.774,14.785 102.756,14.785C101.634,14.785 100.711,13.862 100.711,12.74C100.711,12.647 100.717,12.553 100.73,12.46C100.718,12.369 100.712,12.277 100.712,12.185C100.712,11.045 101.649,10.108 102.788,10.108C102.794,10.108 102.8,10.108 102.806,10.108C104.059,10.108 104.815,10.964 104.815,12.378L104.815,12.688L101.635,12.688L101.635,12.738C101.632,12.771 101.631,12.804 101.631,12.838C101.631,13.491 102.168,14.028 102.821,14.028C102.826,14.028 102.83,14.028 102.834,14.028C103.268,14.08 103.693,13.864 103.906,13.482L104.761,13.482ZM101.635,12.031L103.91,12.031C103.912,12.004 103.913,11.977 103.913,11.95C103.913,11.354 103.422,10.864 102.826,10.864C102.818,10.864 102.81,10.864 102.801,10.864C102.796,10.864 102.792,10.864 102.787,10.864C102.155,10.864 101.635,11.384 101.635,12.016C101.635,12.021 101.635,12.026 101.635,12.031L101.635,12.031Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 17 KiB |
BIN
.github/assets/badge-web.png
vendored
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
23
.github/assets/badge-web.svg
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 135 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M130.2,40L4.73,40C2.137,39.995 0.005,37.863 -0,35.27L0,4.73C-0,2.135 2.135,0 4.73,0L130.2,0C132.807,-0 134.962,2.123 135,4.73L135,35.27C134.957,37.875 132.805,39.995 130.2,40Z" style="fill:rgb(166,166,166);fill-rule:nonzero;"/>
|
||||
<path d="M134,35.27C134,37.371 132.271,39.1 130.17,39.1L4.73,39.1C2.627,39.1 0.895,37.373 0.89,35.27L0.89,4.72C0.895,2.617 2.627,0.89 4.73,0.89L130.2,0.89C132.29,0.906 134,2.63 134,4.72L134,35.27Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M40.56,25.26L41.82,22.08L45.45,22.08L43.73,17.26L45.88,11.81L52.05,28.19L47.5,28.19L46.5,25.26L40.56,25.26Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M36.68,28.19L43.29,11.81L38.91,11.81L34.39,22.4L31.18,11.81L27.81,11.81L24.36,22.4L21.93,17.57L19.73,24.35L22,28.19L26.31,28.19L29.43,18.7L32.43,28.19L36.68,28.19Z" style="fill:rgb(90,15,200);fill-rule:nonzero;"/>
|
||||
<path d="M12.7,22.57L15.4,22.57C16.136,22.582 16.871,22.488 17.58,22.29L18.28,20.14L20.23,14.14C20.08,13.902 19.91,13.678 19.72,13.47C18.57,12.302 16.965,11.695 15.33,11.81L8.55,11.81L8.55,28.19L12.7,28.19L12.7,22.57ZM16.27,15.57C16.674,15.994 16.888,16.565 16.86,17.15C16.892,17.723 16.706,18.288 16.34,18.73C15.77,19.23 15.014,19.466 14.26,19.38L12.7,19.38L12.7,15L14.27,15C14.987,14.922 15.706,15.13 16.27,15.58L16.27,15.57Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M61.25,14.45L61.25,8.74L61.91,8.74L61.91,13.85L64.43,13.85L64.43,14.45L61.25,14.45Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M67.94,14.45L67.81,13.84C67.645,14.069 67.43,14.258 67.18,14.39C66.925,14.489 66.653,14.536 66.38,14.53C66.017,14.55 65.66,14.432 65.38,14.2C65.131,13.957 64.999,13.617 65.02,13.27C65.02,12.4 65.71,11.95 67.1,11.91L67.82,11.91L67.82,11.64C67.842,11.376 67.768,11.113 67.61,10.9C67.421,10.725 67.167,10.637 66.91,10.66C66.489,10.68 66.076,10.789 65.7,10.98L65.5,10.49C65.719,10.366 65.955,10.275 66.2,10.22C66.444,10.152 66.697,10.119 66.95,10.12C67.359,10.087 67.766,10.208 68.09,10.46C68.358,10.757 68.492,11.151 68.46,11.55L68.46,14.47L67.94,14.45ZM66.47,14C66.836,14.03 67.198,13.906 67.47,13.66C67.712,13.413 67.839,13.075 67.82,12.73L67.82,12.35L67.17,12.35C66.783,12.339 66.399,12.422 66.05,12.59C65.827,12.738 65.697,12.993 65.71,13.26C65.696,13.459 65.773,13.654 65.92,13.79C66.075,13.918 66.269,13.992 66.47,14Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M70.36,10.17L70.36,13C70.337,13.281 70.423,13.56 70.6,13.78C70.801,13.964 71.068,14.058 71.34,14.04C71.712,14.077 72.081,13.94 72.34,13.67C72.576,13.317 72.685,12.893 72.65,12.47L72.65,10.17L73.3,10.17L73.3,14.45L72.76,14.45L72.67,13.88C72.539,14.092 72.348,14.259 72.12,14.36C71.866,14.476 71.589,14.534 71.31,14.53C70.887,14.564 70.467,14.431 70.14,14.16C69.841,13.85 69.682,13.43 69.7,13L69.7,10.2L70.36,10.17Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M77.59,14.45L77.59,11.68C77.613,11.399 77.527,11.12 77.35,10.9C77.149,10.716 76.882,10.622 76.61,10.64C76.238,10.603 75.869,10.74 75.61,11.01C75.37,11.361 75.26,11.786 75.3,12.21L75.3,14.45L74.65,14.45L74.65,10.17L75.18,10.17L75.28,10.76C75.41,10.545 75.602,10.374 75.83,10.27C76.079,10.148 76.353,10.087 76.63,10.09C77.055,10.059 77.475,10.195 77.8,10.47C78.09,10.795 78.232,11.226 78.19,11.66L78.19,14.45L77.59,14.45Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M81.28,14.53C80.746,14.573 80.219,14.379 79.84,14C79.474,13.543 79.292,12.965 79.33,12.38C79.294,11.782 79.479,11.191 79.85,10.72C80.231,10.314 80.774,10.098 81.33,10.13C81.535,10.134 81.739,10.157 81.94,10.2C82.11,10.23 82.275,10.284 82.43,10.36L82.23,10.91C82.077,10.848 81.92,10.798 81.76,10.76C81.612,10.731 81.461,10.714 81.31,10.71C80.44,10.71 80,11.26 80,12.37C79.974,12.797 80.087,13.221 80.32,13.58C80.56,13.881 80.937,14.04 81.32,14C81.698,13.998 82.072,13.92 82.42,13.77L82.42,14.35C82.061,14.504 81.669,14.566 81.28,14.53Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M86.31,14.45L86.31,11.68C86.333,11.399 86.247,11.12 86.07,10.9C85.869,10.716 85.602,10.622 85.33,10.64C84.958,10.608 84.591,10.744 84.33,11.01C84.093,11.363 83.983,11.786 84.02,12.21L84.02,14.45L83.37,14.45L83.37,8.37L84,8.37L84,10.21C84.013,10.393 84.013,10.577 84,10.76C84.135,10.556 84.321,10.39 84.54,10.28C84.792,10.157 85.07,10.096 85.35,10.1C85.776,10.068 86.199,10.2 86.53,10.47C86.846,10.783 87.017,11.215 87,11.66L87,14.45L86.31,14.45Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M93,14.45L92.87,13.84C92.704,14.073 92.485,14.261 92.23,14.39C91.979,14.49 91.71,14.537 91.44,14.53C91.077,14.552 90.719,14.434 90.44,14.2C90.191,13.957 90.059,13.617 90.08,13.27C90.08,12.4 90.77,11.95 92.15,11.91L92.88,11.91L92.88,11.64C92.909,11.374 92.829,11.107 92.66,10.9C92.476,10.723 92.224,10.635 91.97,10.66C91.548,10.678 91.135,10.787 90.76,10.98L90.56,10.49C90.776,10.368 91.008,10.277 91.25,10.22C91.498,10.152 91.753,10.119 92.01,10.12C92.416,10.084 92.821,10.206 93.14,10.46C93.413,10.754 93.547,11.151 93.51,11.55L93.51,14.47L93,14.45ZM91.54,14C91.889,14.016 92.23,13.894 92.49,13.66C92.73,13.412 92.853,13.074 92.83,12.73L92.83,12.35L92.19,12.35C91.803,12.339 91.419,12.422 91.07,12.59C90.844,12.735 90.713,12.992 90.73,13.26C90.716,13.459 90.793,13.654 90.94,13.79C91.108,13.931 91.321,14.005 91.54,14Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M97.58,13.28C97.596,13.646 97.426,13.996 97.13,14.21C96.757,14.444 96.32,14.556 95.88,14.53C95.424,14.554 94.97,14.461 94.56,14.26L94.56,13.66C94.974,13.876 95.433,13.993 95.9,14C96.171,14.015 96.44,13.956 96.68,13.83C96.858,13.732 96.966,13.543 96.96,13.34C96.957,13.171 96.876,13.012 96.74,12.91C96.477,12.731 96.191,12.59 95.89,12.49C95.581,12.391 95.283,12.26 95,12.1C94.851,12.006 94.724,11.879 94.63,11.73C94.542,11.575 94.497,11.399 94.5,11.22C94.516,10.88 94.705,10.57 95,10.4C95.346,10.173 95.757,10.064 96.17,10.09C96.634,10.094 97.093,10.189 97.52,10.37L97.29,10.9C96.921,10.736 96.524,10.647 96.12,10.64C95.881,10.621 95.642,10.669 95.43,10.78C95.282,10.858 95.189,11.013 95.19,11.18C95.188,11.284 95.219,11.386 95.28,11.47C95.355,11.566 95.451,11.645 95.56,11.7C95.802,11.825 96.052,11.932 96.31,12.02C96.68,12.133 97.021,12.324 97.31,12.58C97.487,12.769 97.584,13.021 97.58,13.28Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M71,28.45L69.23,28.45L67.53,22.45C67.46,22.19 67.37,21.82 67.26,21.33C67.15,20.84 67.08,20.5 67.06,20.33C67.06,20.63 66.94,21 66.84,21.47C66.74,21.94 66.65,22.28 66.58,22.47L64.93,28.4L63.15,28.4L61.86,23.4L60.54,18.4L62.21,18.4L63.64,24.22C63.86,25.16 64.02,25.98 64.12,26.7C64.12,26.31 64.25,25.88 64.35,25.4C64.45,24.92 64.54,24.53 64.62,24.24L66.25,18.4L67.87,18.4L69.53,24.26C69.733,25.084 69.89,25.919 70,26.76C70.105,25.92 70.269,25.088 70.49,24.27L72,18.46L73.66,18.46L71,28.45Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M78.15,28.59C77.134,28.657 76.135,28.294 75.4,27.59C74.701,26.819 74.34,25.799 74.4,24.76C74.341,23.707 74.668,22.669 75.32,21.84C75.965,21.125 76.898,20.735 77.86,20.78C78.74,20.735 79.598,21.066 80.22,21.69C80.83,22.378 81.143,23.282 81.09,24.2L81.09,25L76.09,25C76.077,25.621 76.291,26.225 76.69,26.7C77.11,27.111 77.684,27.325 78.27,27.29C78.69,27.294 79.11,27.251 79.52,27.16C79.947,27.065 80.363,26.924 80.76,26.74L80.76,28.05C80.382,28.231 79.982,28.362 79.57,28.44C79.103,28.538 78.627,28.588 78.15,28.59ZM77.86,22C77.399,21.978 76.95,22.157 76.63,22.49C76.296,22.886 76.102,23.382 76.08,23.9L79.51,23.9C79.537,23.388 79.377,22.883 79.06,22.48C78.75,22.152 78.311,21.976 77.86,22Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M86.78,20.75C87.632,20.705 88.454,21.079 88.98,21.75C89.557,22.595 89.835,23.609 89.77,24.63C89.835,25.656 89.553,26.674 88.97,27.52C88.417,28.176 87.602,28.555 86.745,28.555C85.888,28.555 85.073,28.176 84.52,27.52L84.42,27.52L84.12,28.41L82.92,28.41L82.92,17.82L84.52,17.82L84.52,21.88L84.58,21.88C85.073,21.154 85.903,20.728 86.78,20.75ZM86.36,22.07C85.829,22.023 85.307,22.235 84.96,22.64C84.627,23.219 84.474,23.884 84.52,24.55L84.52,24.66C84.467,25.355 84.62,26.051 84.96,26.66C85.301,27.094 85.84,27.327 86.39,27.28C86.913,27.301 87.409,27.042 87.69,26.6C88.026,25.99 88.179,25.295 88.13,24.6C88.13,22.93 87.54,22.07 86.36,22.07Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M101.65,28.45L100.65,25.68L96.83,25.68L95.83,28.45L94.13,28.45L97.87,18.45L99.65,18.45L103.38,28.45L101.65,28.45ZM100.22,24.27L99.28,21.55C99.21,21.37 99.12,21.08 99,20.69C98.88,20.3 98.8,20.01 98.75,19.83C98.612,20.452 98.431,21.063 98.21,21.66L97.31,24.27L100.22,24.27Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M108.37,28.59C107.509,28.641 106.675,28.267 106.14,27.59L106,27.59C106.07,28.22 106.1,28.59 106.1,28.75L106.1,31.85L104.49,31.85L104.49,20.89L105.79,20.89C105.79,21.03 105.9,21.37 106.02,21.89L106.1,21.89C106.592,21.131 107.456,20.691 108.36,20.74C109.211,20.698 110.032,21.071 110.56,21.74C111.137,22.585 111.415,23.599 111.35,24.62C111.409,25.645 111.128,26.661 110.55,27.51C110.04,28.199 109.227,28.601 108.37,28.59ZM108,22.07C107.471,22.022 106.95,22.23 106.6,22.63C106.264,23.163 106.11,23.792 106.16,24.42L106.16,24.66C106.107,25.355 106.26,26.051 106.6,26.66C106.939,27.097 107.479,27.331 108.03,27.28C108.549,27.298 109.04,27.034 109.31,26.59C109.645,25.997 109.802,25.32 109.76,24.64C109.802,23.97 109.646,23.302 109.31,22.72C109.019,22.288 108.52,22.041 108,22.07Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M117.09,28.59C116.229,28.641 115.395,28.267 114.86,27.59L114.77,27.59C114.83,28.22 114.86,28.59 114.86,28.75L114.86,31.85L113.26,31.85L113.26,20.89L114.55,20.89C114.55,21.03 114.67,21.37 114.78,21.89L114.86,21.89C115.354,21.132 116.217,20.693 117.12,20.74C117.972,20.695 118.794,21.069 119.32,21.74C119.897,22.585 120.175,23.599 120.11,24.62C120.169,25.645 119.888,26.661 119.31,27.51C118.791,28.21 117.961,28.614 117.09,28.59ZM116.7,22.07C116.171,22.022 115.65,22.23 115.3,22.63C114.964,23.163 114.81,23.792 114.86,24.42L114.86,24.66C114.807,25.355 114.96,26.051 115.3,26.66C115.641,27.094 116.18,27.327 116.73,27.28C117.249,27.298 117.74,27.034 118.01,26.59C118.345,25.997 118.502,25.32 118.46,24.64C118.505,23.971 118.352,23.303 118.02,22.72C117.723,22.29 117.222,22.043 116.7,22.07Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
BIN
.github/assets/badge-windows-store.png
vendored
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
31
.github/assets/badge-windows-store.svg
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 864 312" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g id="Layer_2">
|
||||
<rect x="2.3" y="2.3" width="859.5" height="307.5"/>
|
||||
<path d="M4.5,4.5L859.5,4.5L859.5,307.5L4.5,307.5L4.5,4.5ZM0,312L864,312L864,0L0,0L0,312Z" style="fill:rgb(210,210,210);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<rect x="70.5" y="68.6" width="83.8" height="83.8" style="fill:rgb(242,80,34);"/>
|
||||
<rect x="163" y="68.6" width="83.8" height="83.8" style="fill:rgb(127,186,0);"/>
|
||||
<rect x="70.5" y="161" width="83.8" height="83.8" style="fill:rgb(0,164,239);"/>
|
||||
<rect x="163" y="161" width="83.8" height="83.8" style="fill:rgb(255,185,0);"/>
|
||||
<path d="M408.3,163C408.3,160.5 409.2,158.5 411,156.8C412.8,155.1 414.9,154.3 417.4,154.3C420,154.3 422.2,155.2 423.9,156.9C425.6,158.6 426.5,160.7 426.5,163C426.5,165.4 425.6,167.5 423.8,169.1C422,170.8 419.9,171.6 417.3,171.6C414.7,171.6 412.6,170.8 410.8,169.1C409.2,167.4 408.3,165.4 408.3,163M424.8,244.8L409.9,244.8L409.9,181.3L424.8,181.3L424.8,244.8Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M470.2,233.9C472.4,233.9 474.9,233.4 477.6,232.4C480.3,231.4 482.7,230 485,228.3L485,242.2C482.6,243.6 479.9,244.6 476.9,245.3C473.9,246 470.5,246.4 466.9,246.4C457.6,246.4 450,243.4 444.1,237.5C438.2,231.6 435.3,224 435.3,214.9C435.3,204.7 438.3,196.3 444.3,189.7C450.3,183.1 458.7,179.8 469.7,179.8C472.5,179.8 475.3,180.2 478.2,180.9C481.1,181.6 483.3,182.5 485,183.4L485,197.7C482.7,196 480.3,194.7 477.9,193.8C475.5,192.9 473,192.4 470.5,192.4C464.6,192.4 459.9,194.3 456.2,198.1C452.6,201.9 450.8,207.1 450.8,213.6C450.8,220 452.5,225 456,228.6C459.5,232.2 464.2,233.9 470.2,233.9" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M527.5,180.3C528.7,180.3 529.8,180.4 530.7,180.5C531.6,180.7 532.5,180.9 533.1,181.1L533.1,196.2C532.3,195.6 531.2,195.1 529.7,194.6C528.2,194.1 526.4,193.8 524.2,193.8C520.5,193.8 517.4,195.3 514.9,198.4C512.4,201.5 511.1,206.2 511.1,212.7L511.1,244.8L496.2,244.8L496.2,181.3L511.1,181.3L511.1,191.3L511.3,191.3C512.7,187.8 514.7,185.1 517.5,183.2C520.3,181.3 523.6,180.3 527.5,180.3" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M533.9,214C533.9,203.5 536.9,195.2 542.8,189C548.7,182.9 557,179.8 567.5,179.8C577.4,179.8 585.2,182.8 590.8,188.7C596.4,194.6 599.2,202.6 599.2,212.6C599.2,222.9 596.2,231.1 590.3,237.2C584.4,243.3 576.3,246.3 566.1,246.3C556.3,246.3 548.4,243.4 542.7,237.6C536.8,231.9 533.9,224 533.9,214M549.5,213.5C549.5,220.1 551,225.2 554,228.7C557,232.2 561.3,233.9 566.9,233.9C572.3,233.9 576.5,232.2 579.3,228.7C582.1,225.2 583.6,220 583.6,213.1C583.6,206.3 582.1,201.1 579.2,197.6C576.3,194.1 572.1,192.4 566.8,192.4C561.3,192.4 557.1,194.2 554,197.9C551,201.6 549.5,206.7 549.5,213.5" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M621.4,198C621.4,200.1 622.1,201.8 623.4,203C624.8,204.2 627.8,205.7 632.4,207.6C638.4,210 642.6,212.7 645,215.7C647.4,218.7 648.6,222.3 648.6,226.5C648.6,232.5 646.3,237.3 641.7,240.9C637.1,244.5 630.9,246.3 623.1,246.3C620.5,246.3 617.6,246 614.4,245.3C611.2,244.7 608.5,243.8 606.3,242.9L606.3,228.2C609,230.1 611.9,231.6 615.1,232.7C618.2,233.8 621.1,234.4 623.6,234.4C627,234.4 629.5,233.9 631.1,233C632.7,232.1 633.5,230.5 633.5,228.2C633.5,226.1 632.7,224.4 631,223C629.3,221.6 626.2,219.9 621.5,218.1C616,215.8 612.1,213.2 609.8,210.3C607.5,207.4 606.3,203.7 606.3,199.3C606.3,193.6 608.6,188.9 613.1,185.2C617.6,181.5 623.5,179.7 630.8,179.7C633,179.7 635.5,179.9 638.3,180.4C641.1,180.9 643.4,181.5 645.2,182.3L645.2,196.5C643.2,195.2 640.9,194.1 638.3,193.1C635.7,192.2 633,191.7 630.5,191.7C627.7,191.7 625.4,192.3 623.9,193.4C622.2,194.5 621.4,196.1 621.4,198" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M655.2,214C655.2,203.5 658.2,195.2 664.1,189C670,182.9 678.3,179.8 688.8,179.8C698.7,179.8 706.5,182.8 712.1,188.7C717.7,194.6 720.5,202.6 720.5,212.6C720.5,222.9 717.5,231.1 711.6,237.2C705.7,243.3 697.6,246.3 687.4,246.3C677.6,246.3 669.7,243.4 664,237.6C658.1,231.9 655.2,224 655.2,214M670.7,213.5C670.7,220.1 672.2,225.2 675.2,228.7C678.2,232.2 682.5,233.9 688.1,233.9C693.5,233.9 697.7,232.2 700.5,228.7C703.3,225.2 704.8,220 704.8,213.1C704.8,206.3 703.3,201.1 700.4,197.6C697.5,194.1 693.3,192.4 688,192.4C682.5,192.4 678.3,194.2 675.2,197.9C672.2,201.6 670.7,206.7 670.7,213.5" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M769.8,193.6L769.8,226.2C769.8,233 771.4,238 774.5,241.4C777.6,244.8 782.4,246.4 788.7,246.4C790.8,246.4 793,246.2 795.2,245.7C797.4,245.2 799,244.8 799.9,244.2L799.9,231.8C799,232.4 797.9,232.9 796.7,233.3C795.5,233.7 794.4,233.9 793.6,233.9C790.6,233.9 788.3,233.1 786.9,231.5C785.5,229.9 784.8,227.1 784.8,223.2L784.8,193.5L800,193.5L800,181.3L784.9,181.3L784.9,162.5L769.9,167.1L769.9,181.4L747.6,181.4L747.6,173.7C747.6,169.9 748.4,167 750.1,165C751.8,163 754.2,162.1 757.3,162.1C758.9,162.1 760.3,162.3 761.6,162.7C762.8,163.1 763.7,163.5 764.2,163.8L764.2,150.9C763.1,150.5 761.9,150.3 760.5,150.1C759.1,149.9 757.5,149.8 755.7,149.8C748.9,149.8 743.3,151.9 739,156.2C734.7,160.5 732.5,165.9 732.5,172.6L732.5,181.4L721.9,181.4L721.9,193.6L732.5,193.6L732.5,244.9L747.6,244.9L747.6,193.6L769.8,193.6Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M395.5,156.2L395.5,244.8L380.1,244.8L380.1,175.4L379.9,175.4L352.4,244.8L342.2,244.8L314,175.4L313.8,175.4L313.8,244.8L299.6,244.8L299.6,156.2L321.6,156.2L347.1,221.9L347.5,221.9L374.4,156.2L395.5,156.2Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M340,117.9C334.7,120.9 328.7,122.4 322,122.4C316.9,122.4 312.4,121.3 308.4,119C304.5,116.8 301.4,113.6 299.3,109.5C297.2,105.4 296.1,100.8 296.1,95.7C296.1,90.3 297.3,85.4 299.6,81.2C301.9,77 305.2,73.6 309.5,71.2C313.8,68.8 318.6,67.6 324,67.6C326.7,67.6 329.4,67.9 332.1,68.4C334.8,68.9 337,69.6 338.7,70.4L338.7,78.4C334.7,75.7 329.6,74.4 323.4,74.4C319.7,74.4 316.4,75.3 313.4,77.1C310.4,78.9 308.1,81.4 306.5,84.5C304.9,87.7 304.1,91.3 304.1,95.3C304.1,101.6 305.8,106.6 309.1,110.3C312.4,114 317,115.8 322.8,115.8C326.5,115.8 329.8,115.1 332.6,113.6L332.6,99.9L321.3,99.9L321.3,93.3L340,93.3L340,117.9Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M383.1,104.6L356.9,104.6C357,108.4 358.2,111.3 360.2,113.4C362.3,115.4 365,116.5 368.4,116.5C372.9,116.5 376.8,115.2 380.2,112.6L380.2,119C378.7,120 376.8,120.9 374.5,121.5C372.2,122.1 369.8,122.4 367.4,122.4C361.7,122.4 357.2,120.7 354.1,117.3C351,113.9 349.4,109.1 349.4,102.9C349.4,99.1 350.2,95.6 351.7,92.6C353.2,89.5 355.4,87.1 358.1,85.4C360.8,83.7 363.9,82.8 367.2,82.8C372.1,82.8 376,84.4 378.8,87.6C381.6,90.8 383,95.2 383,100.9L383,104.6L383.1,104.6ZM375.8,98.9C375.8,95.6 375,93.1 373.5,91.4C372,89.7 369.9,88.8 367.1,88.8C364.6,88.8 362.4,89.7 360.5,91.5C358.6,93.3 357.5,95.8 356.9,98.9L375.8,98.9Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M411.5,121.1C410.8,121.5 409.9,121.8 408.6,122C407.4,122.2 406.3,122.3 405.3,122.3C398.3,122.3 394.8,118.4 394.8,110.7L394.8,89.7L388.4,89.7L388.4,83.7L394.8,83.7L394.8,74.7L402.1,72.4L402.1,83.6L411.4,83.6L411.4,89.6L402.1,89.6L402.1,109.4C402.1,111.9 402.5,113.7 403.3,114.7C404.1,115.7 405.5,116.2 407.6,116.2C409,116.2 410.3,115.8 411.4,115L411.4,121.1L411.5,121.1Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M447.7,70.3C447.7,71.5 447.3,72.5 446.4,73.4C445.5,74.3 444.4,74.7 443.1,74.7C441.8,74.7 440.7,74.3 439.8,73.4C438.9,72.6 438.5,71.5 438.5,70.2C438.5,68.9 439,67.8 439.9,67C440.8,66.2 441.9,65.7 443.1,65.7C444.3,65.7 445.4,66.1 446.3,67C447.3,68 447.7,69 447.7,70.3M446.7,121.5L439.4,121.5L439.4,83.7L446.7,83.7L446.7,121.5Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M478.4,121.1C477.7,121.5 476.8,121.8 475.5,122C474.3,122.2 473.2,122.3 472.2,122.3C465.2,122.3 461.7,118.4 461.7,110.7L461.7,89.7L455.3,89.7L455.3,83.7L461.7,83.7L461.7,74.7L469,72.4L469,83.6L478.3,83.6L478.3,89.6L469,89.6L469,109.4C469,111.9 469.4,113.7 470.2,114.7C471,115.7 472.4,116.2 474.5,116.2C475.9,116.2 477.2,115.8 478.3,115L478.3,121.1L478.4,121.1Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M526.2,71.7C525,71.1 523.7,70.7 522.2,70.7C518.1,70.7 516.1,73.2 516.1,78.2L516.1,83.6L524.7,83.6L524.7,89.6L516.1,89.6L516.1,121.4L508.8,121.4L508.8,89.7L502.4,89.7L502.4,83.7L508.8,83.7L508.8,77.9C508.8,73.9 509.9,70.7 512.2,68.3C514.5,65.9 517.6,64.7 521.6,64.7C523.6,64.7 525.1,64.9 526.3,65.4L526.3,71.7L526.2,71.7Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M553.1,91C552.7,90.7 551.9,90.4 550.9,90.1C549.9,89.9 549,89.7 548.3,89.7C545.7,89.7 543.6,90.9 542,93.2C540.4,95.5 539.6,98.5 539.6,102.2L539.6,121.5L532.3,121.5L532.3,83.7L539.6,83.7L539.6,91.3L539.8,91.3C540.6,88.7 541.9,86.7 543.6,85.2C545.3,83.7 547.3,83 549.5,83C551,83 552.2,83.2 553,83.5L553,91L553.1,91Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M594.8,102.4C594.8,108.5 593,113.4 589.5,117C586,120.6 581.3,122.4 575.5,122.4C569.8,122.4 565.2,120.6 561.8,117.1C558.4,113.6 556.7,108.9 556.7,103C556.7,96.7 558.5,91.8 562,88.2C565.5,84.6 570.3,82.8 576.4,82.8C582.1,82.8 586.6,84.5 589.8,88C593.2,91.5 594.8,96.3 594.8,102.4M587.3,102.6C587.3,98.1 586.3,94.7 584.4,92.4C582.4,90.1 579.7,88.9 576.1,88.9C572.5,88.9 569.6,90.1 567.5,92.6C565.4,95 564.4,98.5 564.4,102.9C564.4,107.2 565.4,110.5 567.5,112.9C569.6,115.3 572.4,116.5 576.1,116.5C579.8,116.5 582.6,115.3 584.5,113C586.3,110.5 587.3,107.1 587.3,102.6" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M660.1,121.5L652.8,121.5L652.8,100C652.8,96.1 652.2,93.3 651,91.5C649.8,89.7 647.7,88.9 644.8,88.9C642.4,88.9 640.3,90 638.7,92.2C637,94.4 636.2,97.1 636.2,100.1L636.2,121.5L628.9,121.5L628.9,99.3C628.9,92.4 626.2,88.9 620.9,88.9C618.4,88.9 616.3,90 614.7,92.1C613.1,94.2 612.3,96.9 612.3,100.2L612.3,121.6L605,121.6L605,83.7L612.3,83.7L612.3,89.6L612.4,89.6C615.1,85 619.1,82.8 624.2,82.8C626.6,82.8 628.8,83.5 630.8,84.8C632.7,86.1 634.1,88 635,90.4C636.5,87.8 638.2,85.9 640.3,84.6C642.4,83.4 644.8,82.7 647.6,82.7C655.9,82.7 660,87.8 660,98.1L660,121.5L660.1,121.5Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
25
.github/dependabot.yml
vendored
@@ -1,9 +1,14 @@
|
||||
---
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
assignees:
|
||||
- "ricoberger"
|
||||
labels:
|
||||
- "changelog: changed"
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
@@ -13,6 +18,10 @@ updates:
|
||||
directory: "/app"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
assignees:
|
||||
- "ricoberger"
|
||||
labels:
|
||||
- "changelog: changed"
|
||||
groups:
|
||||
pub:
|
||||
patterns:
|
||||
@@ -22,6 +31,10 @@ updates:
|
||||
directory: "/supabase/functions/_cmd"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
assignees:
|
||||
- "ricoberger"
|
||||
labels:
|
||||
- "changelog: changed"
|
||||
groups:
|
||||
docker:
|
||||
patterns:
|
||||
@@ -31,8 +44,12 @@ updates:
|
||||
directory: "/landing"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
assignees:
|
||||
- "ricoberger"
|
||||
labels:
|
||||
- "changelog: changed"
|
||||
groups:
|
||||
npm-landing:
|
||||
npm:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
@@ -40,7 +57,11 @@ updates:
|
||||
directory: "/supabase/email-templates"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
assignees:
|
||||
- "ricoberger"
|
||||
labels:
|
||||
- "changelog: changed"
|
||||
groups:
|
||||
npm-email-templates:
|
||||
npm:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
9
.github/release.yaml
vendored
@@ -1,3 +1,4 @@
|
||||
---
|
||||
name-template: "$RESOLVED_VERSION"
|
||||
tag-template: "$RESOLVED_VERSION"
|
||||
version-template: "v$MAJOR.$MINOR.$PATCH"
|
||||
@@ -15,15 +16,15 @@ version-resolver:
|
||||
minor:
|
||||
labels:
|
||||
- "changelog: added"
|
||||
- "changelog: changed"
|
||||
patch:
|
||||
labels:
|
||||
- "changelog: changed"
|
||||
- "changelog: fixed"
|
||||
default: patch
|
||||
category-template: "### $TITLE"
|
||||
change-template: '- #$NUMBER: $TITLE @$AUTHOR'
|
||||
change-template: "- #$NUMBER: $TITLE @$AUTHOR"
|
||||
template: |
|
||||
$CHANGES
|
||||
replacers:
|
||||
- search: ':warning:'
|
||||
replace: ':warning: _Breaking change:_ :warning:'
|
||||
- search: ":warning:"
|
||||
replace: ":warning: _Breaking change:_ :warning:"
|
||||
|
||||
396
.github/workflows/continuous-delivery.yaml
vendored
@@ -1,3 +1,4 @@
|
||||
---
|
||||
name: Continuous Delivery
|
||||
|
||||
on:
|
||||
@@ -5,35 +6,41 @@ on:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
jobs:
|
||||
# The "Docker" job builds the Docker image and pushes it to the GitHub Container Registry. The job only runs when a
|
||||
# commit is pushed to the main branch or a new tag is created.
|
||||
# The "Docker" job builds the Docker image and pushes it to the GitHub
|
||||
# Container Registry. The job only runs when a commit is pushed to the main
|
||||
# branch or a new tag is created.
|
||||
docker:
|
||||
name: Docker
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main' || (github.event_name == 'release' && github.event.action == 'published')
|
||||
if:
|
||||
github.ref == 'refs/heads/main' || (github.event_name == 'release' &&
|
||||
github.event.action == 'published')
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set Docker Tag
|
||||
id: tag
|
||||
run: |
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
echo TAG=${GITHUB_REF:10} >> $GITHUB_ENV
|
||||
else
|
||||
echo TAG=main >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Docker Metadata
|
||||
id: metadata
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=semver,pattern={{raw}}
|
||||
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
@@ -41,7 +48,7 @@ jobs:
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -50,26 +57,30 @@ jobs:
|
||||
|
||||
- name: Build and Push Docker Image
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
push: true
|
||||
context: ./supabase/functions
|
||||
file: ./supabase/functions/_cmd/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64/v8
|
||||
tags: ghcr.io/${{ github.repository_owner }}/feeddeck:${{ env.TAG }}
|
||||
tags: ${{ steps.metadata.outputs.tags }}
|
||||
labels: ${{ steps.metadata.outputs.labels }}
|
||||
|
||||
# The "Supabase" job runs the database migrations and deploys all Supabase functions. The job only runs when a commit
|
||||
# is pushed to the main branch or a new tag is created.
|
||||
# The "Supabase" job runs the database migrations and deploys all Supabase
|
||||
# functions. The job only runs when a commit is pushed to the main branch or
|
||||
# a new tag is created.
|
||||
supabase:
|
||||
name: Supabase
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main' || (github.event_name == 'release' && github.event.action == 'published')
|
||||
if:
|
||||
github.ref == 'refs/heads/main' || (github.event_name == 'release' &&
|
||||
github.event.action == 'published')
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -87,19 +98,22 @@ jobs:
|
||||
|
||||
supabase db push
|
||||
|
||||
supabase functions deploy add-source-v1 --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy delete-user-v1 --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy generate-magic-link-v1 --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy image-proxy-v1 --no-verify-jwt --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy profile-v1 --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy profile-v2 --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy revenuecat-webhooks-v1 --no-verify-jwt --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy stripe-create-billing-portal-link-v1 --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy stripe-create-checkout-session-v1 --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy stripe-webhooks-v1 --no-verify-jwt --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy add-or-update-source-v1 --project-ref $PROJECT_ID
|
||||
# supabase functions deploy add-source-v1 --project-ref $PROJECT_ID
|
||||
supabase functions deploy delete-user-v1 --project-ref $PROJECT_ID
|
||||
supabase functions deploy generate-magic-link-v1 --project-ref $PROJECT_ID
|
||||
supabase functions deploy image-proxy-v1 --no-verify-jwt --project-ref $PROJECT_ID
|
||||
# supabase functions deploy profile-v1 --project-ref $PROJECT_ID
|
||||
supabase functions deploy profile-v2 --project-ref $PROJECT_ID
|
||||
supabase functions deploy revenuecat-webhooks-v1 --no-verify-jwt --project-ref $PROJECT_ID
|
||||
supabase functions deploy stripe-create-billing-portal-link-v1 --project-ref $PROJECT_ID
|
||||
supabase functions deploy stripe-create-checkout-session-v1 --project-ref $PROJECT_ID
|
||||
supabase functions deploy stripe-webhooks-v1 --no-verify-jwt --project-ref $PROJECT_ID
|
||||
|
||||
- name: Push Database Migration and Deploy Functions
|
||||
if: ${{ github.event_name == 'release' && github.event.action == 'published' }}
|
||||
if:
|
||||
${{ github.event_name == 'release' && github.event.action ==
|
||||
'published' }}
|
||||
env:
|
||||
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
|
||||
SUPABASE_DB_PASSWORD: ${{ secrets.SUPABASE_PROD_DB_PASSWORD }}
|
||||
@@ -109,23 +123,30 @@ jobs:
|
||||
|
||||
supabase db push
|
||||
|
||||
supabase functions deploy add-source-v1 --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy delete-user-v1 --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy generate-magic-link-v1 --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy image-proxy-v1 --no-verify-jwt --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy profile-v1 --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy profile-v2 --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy revenuecat-webhooks-v1 --no-verify-jwt --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy stripe-create-billing-portal-link-v1 --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy stripe-create-checkout-session-v1 --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy stripe-webhooks-v1 --no-verify-jwt --project-ref $PROJECT_ID --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy add-or-update-source-v1 --project-ref $PROJECT_ID
|
||||
supabase functions deploy add-source-v1 --project-ref $PROJECT_ID
|
||||
supabase functions deploy delete-user-v1 --project-ref $PROJECT_ID
|
||||
supabase functions deploy generate-magic-link-v1 --project-ref $PROJECT_ID
|
||||
supabase functions deploy image-proxy-v1 --no-verify-jwt --project-ref $PROJECT_ID
|
||||
supabase functions deploy profile-v1 --project-ref $PROJECT_ID
|
||||
supabase functions deploy profile-v2 --project-ref $PROJECT_ID
|
||||
supabase functions deploy revenuecat-webhooks-v1 --no-verify-jwt --project-ref $PROJECT_ID
|
||||
supabase functions deploy stripe-create-billing-portal-link-v1 --project-ref $PROJECT_ID
|
||||
supabase functions deploy stripe-create-checkout-session-v1 --project-ref $PROJECT_ID
|
||||
supabase functions deploy stripe-webhooks-v1 --no-verify-jwt --project-ref $PROJECT_ID
|
||||
|
||||
# The "Web" job builds the Flutter web app and publishes it to Cloudflare Pages. The job only runs when a commit is
|
||||
# pushed to the main branch or a new tag is created.
|
||||
# The "Web" job builds the Flutter web app and publishes it to Cloudflare
|
||||
# Pages. The job only runs on pull requests or when a commit is pushed to the
|
||||
# main branch or a new tag is created.
|
||||
#
|
||||
# When the job runs on a pull request it only builds the app but doesn't
|
||||
# upload the build to Cloudflare.
|
||||
web:
|
||||
name: Web
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main' || (github.event_name == 'release' && github.event.action == 'published')
|
||||
if:
|
||||
github.event_name == 'pull_request' || github.ref == 'refs/heads/main' ||
|
||||
(github.event_name == 'release' && github.event.action == 'published')
|
||||
permissions:
|
||||
contents: read
|
||||
defaults:
|
||||
@@ -134,7 +155,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -166,11 +187,12 @@ jobs:
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: '3.13.7'
|
||||
channel: 'stable'
|
||||
flutter-version: "3.32.7"
|
||||
channel: "stable"
|
||||
cache: true
|
||||
cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
|
||||
cache-path: '${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:'
|
||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
||||
cache-path:
|
||||
"${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
@@ -183,6 +205,9 @@ jobs:
|
||||
|
||||
- name: Publish to Cloudflare Pages
|
||||
uses: cloudflare/pages-action@v1
|
||||
if:
|
||||
github.ref == 'refs/heads/main' || (github.event_name == 'release' &&
|
||||
github.event.action == 'published')
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
@@ -190,12 +215,15 @@ jobs:
|
||||
directory: ./app/build/web
|
||||
branch: main
|
||||
|
||||
# The "macOS" job builds the Flutter macOS app and uploads it to the GitHub release or the pull request. The job only
|
||||
# runs for pull requests and when a new release is published.
|
||||
# The "macOS" job builds the Flutter macOS app and uploads it to the GitHub
|
||||
# release or the pull request. The job only runs for pull requests and when a
|
||||
# new release is published.
|
||||
macos:
|
||||
name: macOS
|
||||
runs-on: macos-latest
|
||||
if: github.event_name == 'pull_request' || (github.event_name == 'release' && github.event.action == 'published')
|
||||
runs-on: macos-14
|
||||
if:
|
||||
github.event_name == 'pull_request' || (github.event_name == 'release' &&
|
||||
github.event.action == 'published')
|
||||
permissions:
|
||||
contents: write
|
||||
defaults:
|
||||
@@ -204,16 +232,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: '3.13.7'
|
||||
channel: 'stable'
|
||||
flutter-version: "3.32.7"
|
||||
channel: "stable"
|
||||
cache: true
|
||||
cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
|
||||
cache-path: '${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:'
|
||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
||||
cache-path:
|
||||
"${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
@@ -222,6 +251,7 @@ jobs:
|
||||
- name: Build
|
||||
run: |
|
||||
flutter config --enable-macos-desktop
|
||||
flutter config --enable-swift-package-manager
|
||||
FLUTTER_XCODE_CODE_SIGN_IDENTITY="" FLUTTER_XCODE_CODE_SIGNING_REQUIRED=NO flutter build macos --release --dart-define SUPABASE_URL=${{ secrets.SUPABASE_PROD_URL }} --dart-define SUPABASE_ANON_KEY=${{ secrets.SUPABASE_PROD_ANON_KEY }} --dart-define SUPABASE_SITE_URL=${{ secrets.SUPABASE_PROD_SITE_URL }} --dart-define GOOGLE_CLIENT_ID=${{ secrets.SUPABASE_PROD_GOOGLE_CLIENT_ID }}
|
||||
|
||||
- name: Package
|
||||
@@ -230,7 +260,7 @@ jobs:
|
||||
|
||||
- name: Upload Artifacts (PR)
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: feeddeck-macos-universal.zip
|
||||
path: app/build/macos/Build/Products/Release/feeddeck-macos-universal.zip
|
||||
@@ -238,17 +268,22 @@ jobs:
|
||||
|
||||
- name: Upload Artifacts (Release)
|
||||
uses: shogo82148/actions-upload-release-asset@v1
|
||||
if: ${{ github.event_name == 'release' && github.event.action == 'published' }}
|
||||
if:
|
||||
${{ github.event_name == 'release' && github.event.action ==
|
||||
'published' }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: app/build/macos/Build/Products/Release/feeddeck-macos-universal.zip
|
||||
|
||||
# The "Linux" job builds the Flutter Linux app and uploads it to the GitHub release or the pull request. The job only
|
||||
# runs for pull requests and when a new release is published.
|
||||
linux:
|
||||
name: Linux
|
||||
# The "Linux (x86_64)" job builds the Flutter Linux app and uploads it to the
|
||||
# GitHub release or the pull request. The job only runs for pull requests and
|
||||
# when a new release is published.
|
||||
linux-x86_64:
|
||||
name: Linux (x86_64)
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request' || (github.event_name == 'release' && github.event.action == 'published')
|
||||
if:
|
||||
github.event_name == 'pull_request' || (github.event_name == 'release' &&
|
||||
github.event.action == 'published')
|
||||
permissions:
|
||||
contents: write
|
||||
defaults:
|
||||
@@ -257,22 +292,27 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Packages
|
||||
run: |
|
||||
# Required for Flutter
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y ninja-build libgtk-3-dev
|
||||
# Required for Package "media_kit" which is used via
|
||||
# "just_audio_media_kit" for Linux and Windows:
|
||||
# See: https://pub.dev/packages/media_kit and https://pub.dev/packages/just_audio_media_kit
|
||||
sudo apt-get install -y libmpv-dev mpv
|
||||
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: '3.13.7'
|
||||
channel: 'stable'
|
||||
flutter-version: "3.32.7"
|
||||
channel: "stable"
|
||||
cache: true
|
||||
cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
|
||||
cache-path: '${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:'
|
||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
||||
cache-path:
|
||||
"${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
@@ -294,7 +334,7 @@ jobs:
|
||||
|
||||
- name: Upload Artifacts (PR)
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: feeddeck-linux-x86_64.tar.gz
|
||||
path: app/build/feeddeck-linux-x86_64.tar.gz
|
||||
@@ -302,17 +342,26 @@ jobs:
|
||||
|
||||
- name: Upload Artifacts (Release)
|
||||
uses: shogo82148/actions-upload-release-asset@v1
|
||||
if: ${{ github.event_name == 'release' && github.event.action == 'published' }}
|
||||
if:
|
||||
${{ github.event_name == 'release' && github.event.action ==
|
||||
'published' }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: app/build/feeddeck-linux-x86_64.tar.gz
|
||||
|
||||
# The "Windows" job builds the Flutter Windows app and uploads it to the GitHub release or the pull request. The job
|
||||
# only runs for pull requests and when a new release is published.
|
||||
windows:
|
||||
name: Windows
|
||||
runs-on: windows-latest
|
||||
if: github.event_name == 'pull_request' || (github.event_name == 'release' && github.event.action == 'published')
|
||||
# The "Linux (arm64)" job builds the Flutter Linux app and uploads it to the
|
||||
# GitHub release or the pull request. The job only runs for pull requests and
|
||||
# when a new release is published.
|
||||
#
|
||||
# NOTE: Normally this job should run for every pull request and when a new
|
||||
# release is published, but since we have to pay for the
|
||||
# "ubicloud-standard-2-arm" runner, we only run the job when a new release is
|
||||
# published.
|
||||
linux-arm64:
|
||||
name: Linux (arm64)
|
||||
runs-on: ubicloud-standard-2-arm
|
||||
if: github.event_name == 'release' && github.event.action == 'published'
|
||||
# if: github.event_name == 'pull_request' || (github.event_name == 'release' && github.event.action == 'published')
|
||||
permissions:
|
||||
contents: write
|
||||
defaults:
|
||||
@@ -321,16 +370,91 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Packages
|
||||
run: |
|
||||
# Required for Flutter
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y ninja-build libgtk-3-dev
|
||||
# Required for Package "media_kit" which is used via
|
||||
# "just_audio_media_kit" for Linux and Windows:
|
||||
# See: https://pub.dev/packages/media_kit and https://pub.dev/packages/just_audio_media_kit
|
||||
sudo apt-get install -y libmpv-dev mpv
|
||||
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: '3.13.7'
|
||||
channel: 'stable'
|
||||
flutter-version: "3.32.7"
|
||||
channel: "master"
|
||||
cache: true
|
||||
cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
|
||||
cache-path: '${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:'
|
||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
||||
cache-path:
|
||||
"${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
flutter pub get
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
flutter config --enable-linux-desktop
|
||||
flutter build linux --release --dart-define SUPABASE_URL=${{ secrets.SUPABASE_PROD_URL }} --dart-define SUPABASE_ANON_KEY=${{ secrets.SUPABASE_PROD_ANON_KEY }} --dart-define SUPABASE_SITE_URL=${{ secrets.SUPABASE_PROD_SITE_URL }} --dart-define GOOGLE_CLIENT_ID=${{ secrets.SUPABASE_PROD_GOOGLE_CLIENT_ID }}
|
||||
|
||||
- name: Package
|
||||
run: |
|
||||
cp linux/flatpak/app.feeddeck.feeddeck.desktop build/linux/arm64/release/bundle/
|
||||
cp linux/flatpak/app.feeddeck.feeddeck.metainfo.xml build/linux/arm64/release/bundle/
|
||||
cp linux/flatpak/app.feeddeck.feeddeck.svg build/linux/arm64/release/bundle/
|
||||
cd build
|
||||
cp -r linux/arm64/release/bundle/ feeddeck-linux-arm64
|
||||
tar -czf feeddeck-linux-arm64.tar.gz feeddeck-linux-arm64
|
||||
|
||||
- name: Upload Artifacts (PR)
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: feeddeck-linux-arm64.tar.gz
|
||||
path: app/build/feeddeck-linux-arm64.tar.gz
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Artifacts (Release)
|
||||
uses: shogo82148/actions-upload-release-asset@v1
|
||||
if:
|
||||
${{ github.event_name == 'release' && github.event.action ==
|
||||
'published' }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: app/build/feeddeck-linux-arm64.tar.gz
|
||||
|
||||
# The "Windows" job builds the Flutter Windows app and uploads it to the
|
||||
# GitHub release or the pull request. The job only runs for pull requests and
|
||||
# when a new release is published.
|
||||
windows:
|
||||
name: Windows
|
||||
runs-on: windows-latest
|
||||
if:
|
||||
github.event_name == 'pull_request' || (github.event_name == 'release' &&
|
||||
github.event.action == 'published')
|
||||
permissions:
|
||||
contents: write
|
||||
defaults:
|
||||
run:
|
||||
working-directory: "app"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: "3.32.7"
|
||||
channel: "stable"
|
||||
cache: true
|
||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
||||
cache-path:
|
||||
"${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
@@ -340,24 +464,122 @@ jobs:
|
||||
run: |
|
||||
flutter config --enable-windows-desktop
|
||||
flutter build windows --release --dart-define SUPABASE_URL=${{ secrets.SUPABASE_PROD_URL }} --dart-define SUPABASE_ANON_KEY=${{ secrets.SUPABASE_PROD_ANON_KEY }} --dart-define SUPABASE_SITE_URL=${{ secrets.SUPABASE_PROD_SITE_URL }} --dart-define GOOGLE_CLIENT_ID=${{ secrets.SUPABASE_PROD_GOOGLE_CLIENT_ID }}
|
||||
- name: Create Archive
|
||||
run: |
|
||||
Compress-Archive -Path build/windows/x64/runner/Release/* -Destination feeddeck-windows-x86_64.zip
|
||||
|
||||
- name: Package
|
||||
run: |
|
||||
flutter pub run msix:create --output-path build --output-name feeddeck
|
||||
cd build
|
||||
7z a -tzip feeddeck-windows-x86_64.zip feeddeck.msix
|
||||
7z a -tzip feeddeck-windows-x86_64-msix.zip feeddeck.msix
|
||||
|
||||
- name: Upload Artifacts (PR)
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: feeddeck-windows-x86_64.zip
|
||||
path: app/build/feeddeck-windows-x86_64.zip
|
||||
path: app/feeddeck-windows-x86_64.zip
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Artifacts (Release)
|
||||
uses: shogo82148/actions-upload-release-asset@v1
|
||||
if: ${{ github.event_name == 'release' && github.event.action == 'published' }}
|
||||
if:
|
||||
${{ github.event_name == 'release' && github.event.action ==
|
||||
'published' }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: app/build/feeddeck-windows-x86_64.zip
|
||||
asset_path: app/feeddeck-windows-x86_64.zip
|
||||
|
||||
- name: Upload Artifacts (PR)
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: feeddeck-windows-x86_64-msix.zip
|
||||
path: app/build/feeddeck-windows-x86_64-msix.zip
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Artifacts (Release)
|
||||
uses: shogo82148/actions-upload-release-asset@v1
|
||||
if:
|
||||
${{ github.event_name == 'release' && github.event.action ==
|
||||
'published' }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: app/build/feeddeck-windows-x86_64-msix.zip
|
||||
|
||||
# The "iOS" job builds the Flutter iOS app on every pull request. This is only
|
||||
# used to test that the build of the iOS app works. The artifact of the build
|
||||
# isn't uploaded / used.
|
||||
ios:
|
||||
name: iOS
|
||||
runs-on: macos-14
|
||||
if:
|
||||
github.event_name == 'pull_request' || (github.event_name == 'release' &&
|
||||
github.event.action == 'published')
|
||||
defaults:
|
||||
run:
|
||||
working-directory: "app"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: "3.32.7"
|
||||
channel: "stable"
|
||||
cache: true
|
||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
||||
cache-path:
|
||||
"${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
flutter pub get
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
flutter config --enable-ios
|
||||
flutter config --enable-swift-package-manager
|
||||
flutter build ipa --no-codesign --release --dart-define SUPABASE_URL=${{ secrets.SUPABASE_PROD_URL }} --dart-define SUPABASE_ANON_KEY=${{ secrets.SUPABASE_PROD_ANON_KEY }} --dart-define SUPABASE_SITE_URL=${{ secrets.SUPABASE_PROD_SITE_URL }} --dart-define GOOGLE_CLIENT_ID=${{ secrets.SUPABASE_PROD_GOOGLE_CLIENT_ID }}
|
||||
|
||||
# The "Android" job builds the Flutter Android app on every pull request. This
|
||||
# is only used to test that the build of the Android app works. The artifact
|
||||
# of the build isn't uploaded / used.
|
||||
android:
|
||||
name: Android
|
||||
runs-on: ubuntu-latest
|
||||
if:
|
||||
github.event_name == 'pull_request' || (github.event_name == 'release' &&
|
||||
github.event.action == 'published')
|
||||
defaults:
|
||||
run:
|
||||
working-directory: "app"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Java
|
||||
run: echo "JAVA_HOME=$JAVA_HOME_17_X64" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: "3.32.7"
|
||||
channel: "stable"
|
||||
cache: true
|
||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
||||
cache-path:
|
||||
"${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
flutter pub get
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
flutter config --enable-android
|
||||
flutter build appbundle --release --dart-define SUPABASE_URL=${{ secrets.SUPABASE_PROD_URL }} --dart-define SUPABASE_ANON_KEY=${{ secrets.SUPABASE_PROD_ANON_KEY }} --dart-define SUPABASE_SITE_URL=${{ secrets.SUPABASE_PROD_SITE_URL }} --dart-define GOOGLE_CLIENT_ID=${{ secrets.SUPABASE_PROD_GOOGLE_CLIENT_ID }}
|
||||
|
||||
89
.github/workflows/continuous-integration.yaml
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
---
|
||||
name: Continuous Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
flutter:
|
||||
name: Flutter
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: "app"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: "3.32.7"
|
||||
channel: "stable"
|
||||
cache: true
|
||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
||||
cache-path:
|
||||
"${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
flutter pub get
|
||||
|
||||
- name: Lint
|
||||
run: |
|
||||
dart analyze --fatal-infos
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
flutter test
|
||||
|
||||
deno:
|
||||
name: Deno
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
FEEDDECK_SUPABASE_URL: http://localhost:54321
|
||||
FEEDDECK_SUPABASE_ANON_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
|
||||
FEEDDECK_LOG_LEVEL: debug
|
||||
FEEDDECK_SUPABASE_SERVICE_ROLE_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Supabase
|
||||
uses: supabase/setup-cli@v1
|
||||
|
||||
- name: Setup Deno
|
||||
uses: denoland/setup-deno@v2
|
||||
with:
|
||||
deno-version: v1.45.2
|
||||
|
||||
- name: Start Supabase
|
||||
run: |
|
||||
echo "FEEDDECK_LOG_LEVEL=${FEEDDECK_LOG_LEVEL}" >> ./supabase/.env.test
|
||||
echo "FEEDDECK_SUPABASE_URL=${FEEDDECK_SUPABASE_URL}" >> ./supabase/.env.test
|
||||
echo "FEEDDECK_SUPABASE_ANON_KEY=${FEEDDECK_SUPABASE_ANON_KEY}" >> ./supabase/.env.test
|
||||
echo "FEEDDECK_SUPABASE_SERVICE_ROLE_KEY=${FEEDDECK_SUPABASE_SERVICE_ROLE_KEY}" >> ./supabase/.env.test
|
||||
|
||||
supabase start
|
||||
supabase db reset
|
||||
supabase functions serve --no-verify-jwt --env-file supabase/.env.test &
|
||||
|
||||
psql postgresql://postgres:postgres@127.0.0.1:54322/postgres -c "UPDATE settings SET value='http://kong:8000' WHERE name='supabase_api_url'"
|
||||
psql postgresql://postgres:postgres@127.0.0.1:54322/postgres -c "UPDATE settings SET value='${FEEDDECK_SUPABASE_SERVICE_ROLE_KEY}' WHERE name='supabase_service_role_key'"
|
||||
|
||||
- name: Lint
|
||||
working-directory: "supabase/functions"
|
||||
run: |
|
||||
deno task lint
|
||||
|
||||
- name: Test
|
||||
working-directory: "supabase/functions"
|
||||
run: |
|
||||
deno task test
|
||||
15
.github/workflows/release.yaml
vendored
@@ -1,3 +1,4 @@
|
||||
---
|
||||
name: Release
|
||||
|
||||
on:
|
||||
@@ -16,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Update Changelog
|
||||
uses: release-drafter/release-drafter@v5
|
||||
uses: release-drafter/release-drafter@v6
|
||||
with:
|
||||
config-name: release.yaml
|
||||
disable-autolabeler: true
|
||||
@@ -37,17 +38,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: "16"
|
||||
node-version: "20"
|
||||
cache: npm
|
||||
cache-dependency-path: landing/package-lock.json
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v3
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
@@ -58,7 +59,7 @@ jobs:
|
||||
npm run build
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-pages-artifact@v2
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: ./landing/out
|
||||
|
||||
@@ -78,4 +79,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v2
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
7
.gitignore
vendored
@@ -1,8 +1,11 @@
|
||||
# Visual Studio Code Launch Configurations
|
||||
.vscode/launch.json
|
||||
# Visual Studio Code
|
||||
.vscode
|
||||
|
||||
# Environment Variables
|
||||
/supabase/.env.local
|
||||
/supabase/.env.dev
|
||||
/supabase/.env.stage
|
||||
/supabase/.env.prod
|
||||
|
||||
# Deno
|
||||
/coverage_deno
|
||||
|
||||
17
.vscode/settings.json
vendored
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"deno.enable": true,
|
||||
"deno.unstable": true,
|
||||
"deno.lint": true,
|
||||
"deno.enablePaths": [
|
||||
"./supabase/functions"
|
||||
],
|
||||
"deno.importMap": "./supabase/functions/import_map.json",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "denoland.vscode-deno",
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "vscode.json-language-features"
|
||||
},
|
||||
"[html]": {
|
||||
"editor.defaultFormatter": "vscode.html-language-features"
|
||||
}
|
||||
}
|
||||
202
CONTRIBUTING.md
@@ -57,116 +57,46 @@ check your installed version:
|
||||
```sh
|
||||
$ flutter --version
|
||||
|
||||
Flutter 3.13.7 • channel stable • https://github.com/flutter/flutter.git
|
||||
Framework • revision 2f708eb839 (4 days ago) • 2023-10-09 09:58:08 -0500
|
||||
Engine • revision a794cf2681
|
||||
Tools • Dart 3.1.3 • DevTools 2.25.0
|
||||
|
||||
Flutter 3.29.2 • channel stable • https://github.com/flutter/flutter.git
|
||||
Framework • revision c236373904 (2 weeks ago) • 2025-03-13 16:17:06 -0400
|
||||
Engine • revision 18b71d647a
|
||||
Tools • Dart 3.7.2 • DevTools 2.42.3
|
||||
|
||||
$ deno --version
|
||||
deno 1.36.4 (release, aarch64-apple-darwin)
|
||||
v8 11.6.189.12
|
||||
typescript 5.1.6
|
||||
|
||||
deno 1.40.2 (release, aarch64-apple-darwin)
|
||||
v8 12.1.285.6
|
||||
typescript 5.3.3
|
||||
```
|
||||
|
||||
### Working with Flutter
|
||||
|
||||
We are recommending to use the
|
||||
[Visual Studio Code](https://docs.flutter.dev/development/tools/vs-code)
|
||||
extensions for development.
|
||||
To run the app you can use the [`run.sh`](./app/run.sh) script, which will
|
||||
automatically load the `.env` file from the Supabase project and passes the
|
||||
required variables to the `flutter run` command:
|
||||
|
||||
The easiest way to run the Flutter app within Visual Studio Code is to create a
|
||||
`.vscode/launch.json` file. Within the different configurations you have to
|
||||
provide the following arguments: `--dart-define SUPABASE_URL=<SUPABASE_URL>`,
|
||||
`--dart-define SUPABASE_ANON_KEY=<SUPABASE_ANON_KEY>`,
|
||||
`--dart-define SUPABASE_SITE_URL=<SUPABASE_SITE_URL>` and
|
||||
`--dart-define GOOGLE_CLIENT_ID=<GOOGLE_CLIENT_ID>`.
|
||||
|
||||
<details>
|
||||
<summary>Example: `.vscode/launch.json`</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "FeedDeck (Chrome)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"args": [
|
||||
"-d",
|
||||
"chrome",
|
||||
"--web-port",
|
||||
"3000",
|
||||
"--web-browser-flag=--disable-web-security",
|
||||
"--dart-define",
|
||||
"SUPABASE_URL=<SUPABASE_URL>",
|
||||
"--dart-define",
|
||||
"SUPABASE_ANON_KEY=<SUPABASE_ANON_KEY>",
|
||||
"--dart-define",
|
||||
"SUPABASE_SITE_URL=<SUPABASE_SITE_URL>",
|
||||
"--dart-define",
|
||||
"GOOGLE_CLIENT_ID=<GOOGLE_CLIENT_ID>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FeedDeck (Web Server)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"args": [
|
||||
"-d",
|
||||
"web-server",
|
||||
"--web-port",
|
||||
"3000",
|
||||
"--dart-define",
|
||||
"SUPABASE_URL=<SUPABASE_URL>",
|
||||
"--dart-define",
|
||||
"SUPABASE_ANON_KEY=<SUPABASE_ANON_KEY>",
|
||||
"--dart-define",
|
||||
"SUPABASE_SITE_URL=<SUPABASE_SITE_URL>",
|
||||
"--dart-define",
|
||||
"GOOGLE_CLIENT_ID=<GOOGLE_CLIENT_ID>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FeedDeck (iOS)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"args": [
|
||||
"-d",
|
||||
"iPhone 14 Pro Max",
|
||||
"--dart-define",
|
||||
"SUPABASE_URL=<SUPABASE_URL>",
|
||||
"--dart-define",
|
||||
"SUPABASE_ANON_KEY=<SUPABASE_ANON_KEY>",
|
||||
"--dart-define",
|
||||
"SUPABASE_SITE_URL=<SUPABASE_SITE_URL>",
|
||||
"--dart-define",
|
||||
"GOOGLE_CLIENT_ID=<GOOGLE_CLIENT_ID>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FeedDeck (macOS)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"args": [
|
||||
"-d",
|
||||
"macOS",
|
||||
"--dart-define",
|
||||
"SUPABASE_URL=<SUPABASE_URL>",
|
||||
"--dart-define",
|
||||
"SUPABASE_ANON_KEY=<SUPABASE_ANON_KEY>",
|
||||
"--dart-define",
|
||||
"SUPABASE_SITE_URL=<SUPABASE_SITE_URL>",
|
||||
"--dart-define",
|
||||
"GOOGLE_CLIENT_ID=<GOOGLE_CLIENT_ID>"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```sh
|
||||
./run.sh --device="chrome" --environment="local"
|
||||
```
|
||||
|
||||
</details>
|
||||
To run the tests the following command can be used:
|
||||
|
||||
```sh
|
||||
flutter test
|
||||
```
|
||||
|
||||
To check the test coverage the `--coverage` flag can be added to the command and
|
||||
an HTML report can be generated:
|
||||
|
||||
```sh
|
||||
flutter test --coverage
|
||||
|
||||
# To generate the HTML report lcov is required, which can be installed via Homebrew:
|
||||
brew install lcov
|
||||
|
||||
genhtml coverage/lcov.info -o coverage/html
|
||||
open coverage/html/index.html
|
||||
```
|
||||
|
||||
#### Sort all Imports
|
||||
|
||||
@@ -300,6 +230,38 @@ cd supabase/functions/_cmd
|
||||
docker-compose up --build
|
||||
```
|
||||
|
||||
To build the Docker image, the following commands can be run:
|
||||
|
||||
```sh
|
||||
docker build -f supabase/functions/_cmd/Dockerfile -t ghcr.io/feeddeck/feeddeck:latest supabase/functions
|
||||
|
||||
# To build the Docker image for another platform use the following:
|
||||
docker buildx build --platform linux/amd64 -f supabase/functions/_cmd/Dockerfile -t ghcr.io/feeddeck/feeddeck:latest supabase/functions
|
||||
|
||||
# The Docker image can then be used to run the scheduler, worker or tools, e.g.
|
||||
docker run ghcr.io/feeddeck/feeddeck:latest tools get-feed '{"type": "reddit", "options": {"reddit": "/r/kubernetes"}}'
|
||||
```
|
||||
|
||||
To run the tests for our code, the following command can be used:
|
||||
|
||||
```sh
|
||||
deno test --allow-env supabase/functions
|
||||
```
|
||||
|
||||
To check the test coverage the `--coverage` flag can be added to the command and
|
||||
an HTML report can be generated:
|
||||
|
||||
```sh
|
||||
deno test --allow-env supabase/functions --coverage=coverage_deno
|
||||
|
||||
# To generate the HTML report lcov is required, which can be installed via Homebrew:
|
||||
brew install lcov
|
||||
|
||||
deno coverage coverage_deno --lcov --output=coverage_deno/coverage_deno.lcov
|
||||
genhtml -o coverage_deno/html coverage_deno/coverage_deno.lcov
|
||||
open coverage_deno/html/index.html
|
||||
```
|
||||
|
||||
## Hosting
|
||||
|
||||
FeedDeck uses Supabase as backend. For Supabase we can use
|
||||
@@ -321,16 +283,17 @@ supabase secrets set --env-file supabase/.env
|
||||
supabase secrets list
|
||||
|
||||
# Deploy all functions
|
||||
supabase functions deploy add-source-v1 --project-ref <PROJECT-ID> --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy delete-user-v1 --project-ref <PROJECT-ID> --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy generate-magic-link-v1 --project-ref <PROJECT-ID> --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy image-proxy-v1 --no-verify-jwt --project-ref <PROJECT-ID> --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy profile-v1 --project-ref <PROJECT-ID> --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy profile-v2 --project-ref <PROJECT-ID> --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy revenuecat-webhooks-v1 --no-verify-jwt --project-ref <PROJECT-ID> --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy stripe-create-billing-portal-link-v1 --project-ref <PROJECT-ID> --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy stripe-create-checkout-session-v1 --project-ref <PROJECT-ID> --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy stripe-webhooks-v1 --no-verify-jwt --project-ref <PROJECT-ID> --import-map supabase/functions/import_map.json
|
||||
supabase functions deploy add-or-update-source-v1 --project-ref <PROJECT-ID>
|
||||
supabase functions deploy add-source-v1 --project-ref <PROJECT-ID>
|
||||
supabase functions deploy delete-user-v1 --project-ref <PROJECT-ID>
|
||||
supabase functions deploy generate-magic-link-v1 --project-ref <PROJECT-ID>
|
||||
supabase functions deploy image-proxy-v1 --no-verify-jwt --project-ref <PROJECT-ID>
|
||||
supabase functions deploy profile-v1 --project-ref <PROJECT-ID>
|
||||
supabase functions deploy profile-v2 --project-ref <PROJECT-ID>
|
||||
supabase functions deploy revenuecat-webhooks-v1 --no-verify-jwt --project-ref <PROJECT-ID>
|
||||
supabase functions deploy stripe-create-billing-portal-link-v1 --project-ref <PROJECT-ID>
|
||||
supabase functions deploy stripe-create-checkout-session-v1 --project-ref <PROJECT-ID>
|
||||
supabase functions deploy stripe-webhooks-v1 --no-verify-jwt --project-ref <PROJECT-ID>
|
||||
```
|
||||
|
||||
Now we have to do some manual steps to finish the setup of our Supabase project:
|
||||
@@ -422,21 +385,8 @@ Android, macOS, Windows and Linux if you do not want to use the official ones.
|
||||
5. Build the app for Web by running `flutter build web`. The build can be found
|
||||
at `app/build/web` and must be uploaded to your hosting provider.
|
||||
|
||||
6. Build the app for Linux by running `flutter build linux --release`. To build
|
||||
the `arm64` version the following commands can be run on a Raspberry Pi. Once
|
||||
the `feeddeck-linux-arm64.tar.gz` archive was created it can be uploaded to
|
||||
the GitHub release.
|
||||
|
||||
```sh
|
||||
cp linux/flatpak/app.feeddeck.feeddeck.desktop build/linux/x64/release/bundle/
|
||||
cp linux/flatpak/app.feeddeck.feeddeck.metainfo.xml build/linux/x64/release/bundle/
|
||||
cp linux/flatpak/app.feeddeck.feeddeck.svg build/linux/x64/release/bundle/
|
||||
cd build
|
||||
cp -r linux/arm64/release/bundle/ feeddeck-linux-arm64
|
||||
tar -czf feeddeck-linux-arm64.tar.gz feeddeck-linux-arm64
|
||||
```
|
||||
|
||||
Update the `app.feeddeck.feeddeck.yml` file at
|
||||
6. Build the app for Linux by running `flutter build linux --release`. Update
|
||||
the `app.feeddeck.feeddeck.yml` file at
|
||||
[github.com/flathub/app.feeddeck.feeddeck](https://github.com/flathub/app.feeddeck.feeddeck)
|
||||
with the new release.
|
||||
|
||||
|
||||
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Rico Berger
|
||||
Copyright (c) 2026 Rico Berger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
26
README.md
@@ -1,19 +1,25 @@
|
||||
<div align="center">
|
||||
<img src=".github/assets/icon.png" width="200" />
|
||||
<br><br>
|
||||
<img src=".github/assets/icon.png" width="200" />
|
||||
<br><br>
|
||||
|
||||
**FeedDeck** is an open source RSS and social media feed reader, inspired by
|
||||
TweetDeck. FeedDeck allows you to follow your favorite feeds in one place on all
|
||||
platforms.
|
||||
|
||||
<p>
|
||||
<a href="https://feeddeck.app/download" target="_blank"><img src=".github/assets/badge-app-store.png" height="50"></a>
|
||||
<a href="https://feeddeck.app/download" target="_blank"><img src=".github/assets/badge-google-play.png" height="50"></a>
|
||||
<a href="https://feeddeck.app/download" target="_self"><img src=".github/assets/badge-desktop.png" height="50"></a>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://apps.apple.com/us/app/feeddeck/id6451055362" target="_blank"><img src=".github/assets/badge-app-store.png" height="50"></a>
|
||||
<a href="https://play.google.com/store/apps/details?id=app.feeddeck.feeddeck" target="_blank"><img src=".github/assets/badge-google-play.png" height="50"></a>
|
||||
<a href="https://app.feeddeck.app" target="_blank"><img src=".github/assets/badge-web.png" height="50"></a>
|
||||
<a href="https://apps.apple.com/us/app/feeddeck/id6451055362" target="_blank"><img src=".github/assets/badge-mac-app-store.png" height="50"></a>
|
||||
<a href="https://www.microsoft.com/store/apps/9NPHPGRRCT5H" target="_blank"><img src=".github/assets/badge-windows-store.png" height="50"></a>
|
||||
<a href="https://flathub.org/en/apps/app.feeddeck.feeddeck" target="_blank"><img src=".github/assets/badge-flathub.png" height="50"></a>
|
||||
</p>
|
||||
|
||||
<img src=".github/assets/screenshot.png" width="100%" />
|
||||
<br><br>
|
||||
<div align="center">
|
||||
<img src=".github/assets/screenshot.png" width="100%" />
|
||||
<br><br>
|
||||
</div>
|
||||
|
||||
**FeedDeck** is an open source RSS and social media feed reader, inspired by
|
||||
@@ -28,8 +34,8 @@ platforms. FeedDeck is written in [Flutter](https://flutter.dev/) and uses
|
||||
- **RSS and Social Media Feeds:** Follow your favorite RSS and social media
|
||||
feeds.
|
||||
- **News:** Get the latest news from your favorite RSS feeds and Google News.
|
||||
- **Social Media:** Follow your friends and favorite topics on Medium, Nitter,
|
||||
Reddit, Tumblr and X.
|
||||
- **Social Media:** Follow your friends and favorite topics on Medium, Reddit
|
||||
and Tumblr.
|
||||
- **GitHub:** Get your GitHub notifications and follow your repository
|
||||
activities.
|
||||
- **Podcasts:** Follow and listen to your favorite podcasts, via the built-in
|
||||
|
||||
8
app/.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
/coverage
|
||||
3
app/.gitignore
vendored
@@ -5,9 +5,11 @@
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
@@ -31,6 +33,7 @@ migrate_working_dir/
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
/coverage
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
14
app/Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM ghcr.io/cirruslabs/flutter:3.29.3 AS build
|
||||
|
||||
ARG SUPABASE_URL
|
||||
ARG SUPABASE_ANON_KEY
|
||||
ARG SUPABASE_SITE_URL
|
||||
ARG GOOGLE_CLIENT_ID
|
||||
|
||||
RUN mkdir /app/
|
||||
COPY . /app/
|
||||
WORKDIR /app/
|
||||
RUN flutter build web --release --dart-define SUPABASE_URL=${SUPABASE_URL} --dart-define SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY} --dart-define SUPABASE_SITE_URL=${SUPABASE_SITE_URL} --dart-define GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
|
||||
|
||||
FROM nginx:1.26.3
|
||||
COPY --from=build /app/build/web /usr/share/nginx/html
|
||||
1
app/android/.gitignore
vendored
@@ -5,6 +5,7 @@ gradle-wrapper.jar
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
.cxx/
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||
localProperties.load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
def keystoreProperties = new Properties()
|
||||
def keystorePropertiesFile = rootProject.file('key.properties')
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
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')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "app.feeddeck.feeddeck"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
// minSdkVersion flutter.minSdkVersion
|
||||
// targetSdkVersion flutter.targetSdkVersion
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
keyAlias keystoreProperties['keyAlias']
|
||||
keyPassword keystoreProperties['keyPassword']
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
||||
storePassword keystoreProperties['storePassword']
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'com.google.android.gms:play-services-auth:20.6.0'
|
||||
}
|
||||
73
app/android/app/build.gradle.kts
Normal file
@@ -0,0 +1,73 @@
|
||||
import java.util.Properties
|
||||
import java.io.FileInputStream
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
val keystoreProperties = Properties()
|
||||
val keystorePropertiesFile = rootProject.file("key.properties")
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.feeddeck.feeddeck"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
// Use 27.0.12077973 as NDK version instead of the default which is defined in ~/flutter/packages/flutter_tools/gradle/src/main/kotlin/FlutterExtension.kt
|
||||
// ndkVersion = flutter.ndkVersion
|
||||
ndkVersion = "27.0.12077973"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "app.feeddeck.feeddeck"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
minSdk = 21
|
||||
targetSdk = 35
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keyAlias = keystoreProperties["keyAlias"] as String
|
||||
keyPassword = keystoreProperties["keyPassword"] as String
|
||||
storeFile = keystoreProperties["storeFile"]?.let { file(it) }
|
||||
storePassword = keystoreProperties["storePassword"] as String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.google.android.gms:play-services-auth:20.6.0")
|
||||
// This is required for the "file_picker" Flutter package, because without the app crash when exporting a file with the following error:
|
||||
// "java.lang.NoClassDefFoundError: Failed resolution of: Ljavax/xml/stream/XMLResolver;"
|
||||
implementation("javax.xml.stream:stax-api:1.0-2")
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="app.feeddeck.feeddeck">
|
||||
<application
|
||||
android:label="feeddeck"
|
||||
android:label="FeedDeck"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:usesCleartextTraffic="true"
|
||||
|
||||
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
@@ -5,6 +5,10 @@
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.7.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.2.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
21
app/android/build.gradle.kts
Normal file
@@ -0,0 +1,21 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
|
||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||
|
||||
subprojects {
|
||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
include ':app'
|
||||
|
||||
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||
def properties = new Properties()
|
||||
|
||||
assert localPropertiesFile.exists()
|
||||
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
||||
25
app/android/settings.gradle.kts
Normal file
@@ -0,0 +1,25 @@
|
||||
pluginManagement {
|
||||
val flutterSdkPath = run {
|
||||
val properties = java.util.Properties()
|
||||
file("local.properties").inputStream().use { properties.load(it) }
|
||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||
flutterSdkPath
|
||||
}
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.7.3" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||
1
app/devtools_options.yaml
Normal file
@@ -0,0 +1 @@
|
||||
extensions:
|
||||
@@ -21,6 +21,6 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>11.0</string>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
platform :ios, '11.0'
|
||||
platform :ios, '13.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
@@ -27,6 +27,9 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe
|
||||
|
||||
flutter_ios_podfile_setup
|
||||
|
||||
Pod::PICKER_MEDIA = false
|
||||
Pod::PICKER_AUDIO = false
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
use_modular_headers!
|
||||
|
||||
@@ -1,113 +1,64 @@
|
||||
PODS:
|
||||
- app_links (0.0.1):
|
||||
- Flutter
|
||||
- audio_service (0.0.1):
|
||||
- Flutter
|
||||
- audio_session (0.0.1):
|
||||
- Flutter
|
||||
- Flutter (1.0.0)
|
||||
- flutter_native_splash (0.0.1):
|
||||
- media_kit_libs_ios_video (1.0.4):
|
||||
- Flutter
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
- just_audio (0.0.1):
|
||||
- media_kit_video (0.0.1):
|
||||
- Flutter
|
||||
- package_info_plus (0.4.5):
|
||||
- purchases_flutter (9.1.0):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- purchases_flutter (6.0.0):
|
||||
- Flutter
|
||||
- PurchasesHybridCommon (= 7.0.0)
|
||||
- PurchasesHybridCommon (7.0.0):
|
||||
- RevenueCat (= 4.27.0)
|
||||
- RevenueCat (4.27.0)
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- PurchasesHybridCommon (= 16.0.2)
|
||||
- PurchasesHybridCommon (16.0.2):
|
||||
- RevenueCat (= 5.33.1)
|
||||
- RevenueCat (5.33.1)
|
||||
- sign_in_with_apple (0.0.1):
|
||||
- Flutter
|
||||
- sqflite (0.0.3):
|
||||
- Flutter
|
||||
- FMDB (>= 2.7.5)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- webview_flutter_wkwebview (0.0.1):
|
||||
- FlutterMacOS
|
||||
- wakelock_plus (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||
- audio_service (from `.symlinks/plugins/audio_service/ios`)
|
||||
- audio_session (from `.symlinks/plugins/audio_session/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
- just_audio (from `.symlinks/plugins/just_audio/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
||||
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
||||
- purchases_flutter (from `.symlinks/plugins/purchases_flutter/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
|
||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- FMDB
|
||||
- PurchasesHybridCommon
|
||||
- RevenueCat
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
app_links:
|
||||
:path: ".symlinks/plugins/app_links/ios"
|
||||
audio_service:
|
||||
:path: ".symlinks/plugins/audio_service/ios"
|
||||
audio_session:
|
||||
:path: ".symlinks/plugins/audio_session/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_native_splash:
|
||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||
just_audio:
|
||||
:path: ".symlinks/plugins/just_audio/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
media_kit_libs_ios_video:
|
||||
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
|
||||
media_kit_video:
|
||||
:path: ".symlinks/plugins/media_kit_video/ios"
|
||||
purchases_flutter:
|
||||
:path: ".symlinks/plugins/purchases_flutter/ios"
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
sign_in_with_apple:
|
||||
:path: ".symlinks/plugins/sign_in_with_apple/ios"
|
||||
sqflite:
|
||||
:path: ".symlinks/plugins/sqflite/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
webview_flutter_wkwebview:
|
||||
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
|
||||
:path: ".symlinks/plugins/sqflite/darwin"
|
||||
wakelock_plus:
|
||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
app_links: 5ef33d0d295a89d9d16bb81b0e3b0d5f70d6c875
|
||||
audio_service: f509d65da41b9521a61f1c404dd58651f265a567
|
||||
audio_session: 4f3e461722055d21515cf3261b64c973c062f345
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa
|
||||
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
purchases_flutter: 549ccfbbaf5e7cd195043c714b69a35e278c00f1
|
||||
PurchasesHybridCommon: af3b2413f9cb999bc1fdca44770bdaf39dfb89fa
|
||||
RevenueCat: 84fbe2eb9bbf63e1abf346ccd3ff9ee45d633e3b
|
||||
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
||||
sign_in_with_apple: f3bf75217ea4c2c8b91823f225d70230119b8440
|
||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
|
||||
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
|
||||
purchases_flutter: 818eabac676b9037ac7692d4bc48a52a10b276be
|
||||
PurchasesHybridCommon: ae7a0a6e105ecdde3e8816a004e57f0a2a7b9261
|
||||
RevenueCat: b0ed01125b05a45b8264a2951ad68acb61942038
|
||||
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
|
||||
sqflite: c35dad70033b8862124f8337cc994a809fcd9fa3
|
||||
wakelock_plus: fd58c82b1388f4afe3fe8aa2c856503a262a5b03
|
||||
|
||||
PODFILE CHECKSUM: ec83c31511fbc978a9918c6fda235238118483f5
|
||||
PODFILE CHECKSUM: a35dde46ea09af570b675187a949f5fa3bc82280
|
||||
|
||||
COCOAPODS: 1.13.0
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@@ -57,6 +58,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
|
||||
11A5CB70E48EE53811F041B1 /* Pods_Runner.framework in Frameworks */,
|
||||
55F35B592ABF74D1007331B3 /* StoreKit.framework in Frameworks */,
|
||||
);
|
||||
@@ -134,6 +136,9 @@
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
packageProductDependencies = (
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
||||
);
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
@@ -159,9 +164,12 @@
|
||||
|
||||
/* Begin PBXProject section */
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
packageReferences = (
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
|
||||
);
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1430;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
97C146ED1CF9000F007C117D = {
|
||||
@@ -348,7 +356,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -428,7 +436,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -477,7 +485,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -561,6 +569,18 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
/* Begin XCLocalSwiftPackageReference section */
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
|
||||
isa = XCLocalSwiftPackageReference;
|
||||
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
|
||||
};
|
||||
/* End XCLocalSwiftPackageReference section */
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = FlutterGeneratedPluginSwiftPackage;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"originHash" : "34b4b4fd64d12b3f877c43c5c1afbd609747cf0fa590d3e8fb35fc7cd91a1fa3",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "dkcamera",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKCamera",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dkimagepickercontroller",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKImagePickerController",
|
||||
"state" : {
|
||||
"branch" : "4.3.9",
|
||||
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dkphotogallery",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sdwebimage",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SDWebImage/SDWebImage",
|
||||
"state" : {
|
||||
"revision" : "b62cb63bf4ed1f04c961a56c9c6c9d5ab8524ec6",
|
||||
"version" : "5.21.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftygif",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/kirualex/SwiftyGif.git",
|
||||
"state" : {
|
||||
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
|
||||
"version" : "5.4.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "tocropviewcontroller",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/TimOliver/TOCropViewController",
|
||||
"state" : {
|
||||
"revision" : "a634cb7cdfd580006e79a6e74e64417fe9e9783b",
|
||||
"version" : "2.7.4"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
@@ -1,10 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1430"
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<PreActions>
|
||||
<ExecutionAction
|
||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||
<ActionContent
|
||||
title = "Run Prepare Flutter Framework Script"
|
||||
scriptText = "/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ">
|
||||
<EnvironmentBuildable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</EnvironmentBuildable>
|
||||
</ActionContent>
|
||||
</ExecutionAction>
|
||||
</PreActions>
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
@@ -26,6 +44,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
@@ -43,11 +62,13 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"originHash" : "34b4b4fd64d12b3f877c43c5c1afbd609747cf0fa590d3e8fb35fc7cd91a1fa3",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "dkcamera",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKCamera",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dkimagepickercontroller",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKImagePickerController",
|
||||
"state" : {
|
||||
"branch" : "4.3.9",
|
||||
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dkphotogallery",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sdwebimage",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SDWebImage/SDWebImage",
|
||||
"state" : {
|
||||
"revision" : "b62cb63bf4ed1f04c961a56c9c6c9d5ab8524ec6",
|
||||
"version" : "5.21.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftygif",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/kirualex/SwiftyGif.git",
|
||||
"state" : {
|
||||
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
|
||||
"version" : "5.4.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "tocropviewcontroller",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/TimOliver/TOCropViewController",
|
||||
"state" : {
|
||||
"revision" : "a634cb7cdfd580006e79a6e74e64417fe9e9783b",
|
||||
"version" : "2.7.4"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import UIKit
|
||||
import Flutter
|
||||
|
||||
@UIApplicationMain
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 14 KiB |
@@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
@@ -20,14 +22,38 @@
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>app.feeddeck.feeddeck</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<true/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
@@ -42,32 +68,8 @@
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<true/>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>app.feeddeck.feeddeck</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
</array>
|
||||
<false/>
|
||||
<key>UISupportsDocumentBrowser</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -7,15 +7,18 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||
import 'package:flutter_web_plugins/url_strategy.dart';
|
||||
import 'package:just_audio_background/just_audio_background.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import 'package:feeddeck/repositories/app_repository.dart';
|
||||
import 'package:feeddeck/repositories/layout_repository.dart';
|
||||
import 'package:feeddeck/repositories/profile_repository.dart';
|
||||
import 'package:feeddeck/repositories/settings_repository.dart';
|
||||
import 'package:feeddeck/utils/constants.dart';
|
||||
import 'package:feeddeck/widgets/confirmation/confirmation.dart';
|
||||
import 'package:feeddeck/widgets/home/home.dart';
|
||||
import 'package:feeddeck/widgets/item/details/utils/item_audio_palyer/item_audio_player_init/item_audio_player_init.dart';
|
||||
import 'package:feeddeck/widgets/reset_password/reset_password.dart';
|
||||
|
||||
/// Before we are calling [runApp] we have to ensure that the widget bindings
|
||||
@@ -45,13 +48,26 @@ void main() async {
|
||||
});
|
||||
}
|
||||
|
||||
/// Initialize the [media_kit] packages, so that we can play audio and video
|
||||
/// files.
|
||||
MediaKit.ensureInitialized();
|
||||
if (!kIsWeb && (Platform.isLinux || Platform.isWindows)) {
|
||||
ItemAudioPlayerInit().init();
|
||||
}
|
||||
|
||||
/// Initialize the [just_audio_background] package, so that we can play audio
|
||||
/// files in the background.
|
||||
await JustAudioBackground.init(
|
||||
androidNotificationChannelId: 'com.ryanheise.bg_demo.channel.audio',
|
||||
androidNotificationChannelName: 'Audio playback',
|
||||
androidNotificationOngoing: true,
|
||||
);
|
||||
///
|
||||
/// We can not initialize the [just_audio_background] package on Windows and
|
||||
/// Linux, because then the returned duration in the `_player.durationStream`
|
||||
/// isn't working correctly in the [ItemAudioPlayer] widget.
|
||||
if (kIsWeb || Platform.isAndroid || Platform.isIOS || Platform.isMacOS) {
|
||||
await JustAudioBackground.init(
|
||||
androidNotificationChannelId: 'com.ryanheise.bg_demo.channel.audio',
|
||||
androidNotificationChannelName: 'Audio playback',
|
||||
androidNotificationOngoing: true,
|
||||
);
|
||||
}
|
||||
|
||||
/// For the ewb we have to use the path url strategy, so that the redirect
|
||||
/// within Supabase is working in all cases. On all other platforms this is a
|
||||
@@ -75,12 +91,12 @@ void main() async {
|
||||
class FeedDeckScrollBehavior extends MaterialScrollBehavior {
|
||||
@override
|
||||
Set<PointerDeviceKind> get dragDevices => {
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.mouse,
|
||||
PointerDeviceKind.trackpad,
|
||||
PointerDeviceKind.stylus,
|
||||
PointerDeviceKind.unknown,
|
||||
};
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.mouse,
|
||||
PointerDeviceKind.trackpad,
|
||||
PointerDeviceKind.stylus,
|
||||
PointerDeviceKind.unknown,
|
||||
};
|
||||
}
|
||||
|
||||
/// [onGenerateRoute] is used in `onGenerateRoute` and `onGenerateInitialRoutes`
|
||||
@@ -103,15 +119,11 @@ Route onGenerateRoute(RouteSettings settings) {
|
||||
),
|
||||
);
|
||||
case '/reset-password':
|
||||
return MaterialPageRoute(
|
||||
builder: (_) => const ResetPassword(),
|
||||
);
|
||||
return MaterialPageRoute(builder: (_) => const ResetPassword());
|
||||
}
|
||||
}
|
||||
|
||||
return MaterialPageRoute(
|
||||
builder: (_) => const Home(),
|
||||
);
|
||||
return MaterialPageRoute(builder: (_) => const Home());
|
||||
}
|
||||
|
||||
/// The [FeedDeckApp] is the root widget of the app. The widget is used to
|
||||
@@ -124,6 +136,7 @@ class FeedDeckApp extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (_) => LayoutRepository()),
|
||||
ChangeNotifierProvider(create: (_) => AppRepository()),
|
||||
ChangeNotifierProvider(create: (_) => ProfileRepository()),
|
||||
],
|
||||
@@ -142,8 +155,6 @@ class FeedDeckApp extends StatelessWidget {
|
||||
onSecondary: Constants.onSecondary,
|
||||
error: Constants.error,
|
||||
onError: Constants.onError,
|
||||
background: Constants.background,
|
||||
onBackground: Constants.onBackground,
|
||||
surface: Constants.surface,
|
||||
onSurface: Constants.onSurface,
|
||||
),
|
||||
@@ -155,32 +166,26 @@ class FeedDeckApp extends StatelessWidget {
|
||||
elevation: Constants.appBarElevation,
|
||||
),
|
||||
snackBarTheme: const SnackBarThemeData(
|
||||
backgroundColor: Constants.surface,
|
||||
contentTextStyle: TextStyle(
|
||||
color: Constants.onSurface,
|
||||
),
|
||||
backgroundColor: Constants.secondary,
|
||||
contentTextStyle: TextStyle(color: Constants.onSurface),
|
||||
),
|
||||
dialogTheme: const DialogTheme(
|
||||
backgroundColor: Constants.background,
|
||||
surfaceTintColor: Constants.background,
|
||||
contentTextStyle: TextStyle(
|
||||
color: Constants.onBackground,
|
||||
),
|
||||
dialogTheme: const DialogThemeData(
|
||||
backgroundColor: Constants.surface,
|
||||
surfaceTintColor: Constants.surface,
|
||||
contentTextStyle: TextStyle(color: Constants.onSurface),
|
||||
),
|
||||
popupMenuTheme: const PopupMenuThemeData(
|
||||
color: Constants.background,
|
||||
surfaceTintColor: Constants.background,
|
||||
textStyle: TextStyle(
|
||||
color: Constants.onBackground,
|
||||
),
|
||||
color: Constants.surface,
|
||||
surfaceTintColor: Constants.surface,
|
||||
textStyle: TextStyle(color: Constants.onSurface),
|
||||
),
|
||||
drawerTheme: const DrawerThemeData(
|
||||
backgroundColor: Constants.background,
|
||||
surfaceTintColor: Constants.background,
|
||||
backgroundColor: Constants.surface,
|
||||
surfaceTintColor: Constants.surface,
|
||||
),
|
||||
bottomSheetTheme: const BottomSheetThemeData(
|
||||
backgroundColor: Constants.background,
|
||||
surfaceTintColor: Constants.background,
|
||||
backgroundColor: Constants.surface,
|
||||
surfaceTintColor: Constants.surface,
|
||||
),
|
||||
pageTransitionsTheme: const PageTransitionsTheme(
|
||||
builders: {
|
||||
@@ -193,8 +198,9 @@ class FeedDeckApp extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
scrollBehavior: FeedDeckScrollBehavior(),
|
||||
onGenerateInitialRoutes: (initialRoute) =>
|
||||
[onGenerateRoute(RouteSettings(name: initialRoute))],
|
||||
onGenerateInitialRoutes: (initialRoute) => [
|
||||
onGenerateRoute(RouteSettings(name: initialRoute)),
|
||||
],
|
||||
onGenerateRoute: (RouteSettings settings) =>
|
||||
onGenerateRoute(settings),
|
||||
),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:xml/xml.dart';
|
||||
|
||||
import 'package:feeddeck/models/source.dart';
|
||||
|
||||
/// [FDColumn] is the model for a column in our app. The following fields are
|
||||
@@ -26,11 +28,12 @@ class FDColumn {
|
||||
id: data['id'],
|
||||
name: data['name'],
|
||||
position: data['position'],
|
||||
sources: data.containsKey('sources') && data['sources'] != null
|
||||
? List<FDSource>.from(
|
||||
data['sources'].map((source) => FDSource.fromJson(source)),
|
||||
)
|
||||
: [],
|
||||
sources:
|
||||
data.containsKey('sources') && data['sources'] != null
|
||||
? List<FDSource>.from(
|
||||
data['sources'].map((source) => FDSource.fromJson(source)),
|
||||
)
|
||||
: [],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,4 +43,34 @@ class FDColumn {
|
||||
String identifier() {
|
||||
return 'id: $id, sources: ${sources.map((source) => source.id).join(' ')}';
|
||||
}
|
||||
|
||||
factory FDColumn.fromXml(XmlElement element) {
|
||||
final sources = <FDSource>[];
|
||||
|
||||
element.findElements('outline').forEach((outline) {
|
||||
final source = FDSource.fromXml(outline);
|
||||
if (source.type != FDSourceType.none) {
|
||||
sources.add(source);
|
||||
}
|
||||
});
|
||||
|
||||
return FDColumn(
|
||||
id: '',
|
||||
name: element.getAttribute('text') ?? 'Unknown',
|
||||
position: 0,
|
||||
sources: sources,
|
||||
);
|
||||
}
|
||||
|
||||
void toXml(XmlBuilder builder) {
|
||||
builder.element(
|
||||
'outline',
|
||||
nest: () {
|
||||
builder.attribute('text', name);
|
||||
for (var i = 0; i < sources.length; i++) {
|
||||
sources[i].toXml(builder);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:xml/xml.dart';
|
||||
|
||||
import 'package:feeddeck/models/sources/github.dart';
|
||||
import 'package:feeddeck/models/sources/googlenews.dart';
|
||||
import 'package:feeddeck/models/sources/stackoverflow.dart';
|
||||
import 'package:feeddeck/utils/constants.dart';
|
||||
import 'package:feeddeck/utils/fd_icons.dart';
|
||||
|
||||
/// [FDSourceType] is a enum value which defines the source type. A source can
|
||||
/// have one of the following types:
|
||||
/// - [fourchan]
|
||||
/// - [github]
|
||||
/// - [googlenews]
|
||||
/// - [lemmy]
|
||||
/// - [mastodon]
|
||||
/// - [medium]
|
||||
/// - [nitter]
|
||||
/// - [pinterest]
|
||||
/// - [podcast]
|
||||
/// - [reddit]
|
||||
/// - [rss]
|
||||
/// - [stackoverflow]
|
||||
/// - [tumblr]
|
||||
/// - [x]
|
||||
/// - [youtube]
|
||||
///
|
||||
/// The [none] value is not valid and just here as a fallback in case sth. odd
|
||||
@@ -25,17 +30,19 @@ import 'package:feeddeck/utils/constants.dart';
|
||||
/// the list, so that we can loop though the types in a ListView / GridView
|
||||
/// builder via `FDSourceType.values.length - 1`.
|
||||
enum FDSourceType {
|
||||
fourchan,
|
||||
github,
|
||||
googlenews,
|
||||
lemmy,
|
||||
mastodon,
|
||||
medium,
|
||||
nitter,
|
||||
pinterest,
|
||||
podcast,
|
||||
reddit,
|
||||
rss,
|
||||
stackoverflow,
|
||||
tumblr,
|
||||
// x,
|
||||
youtube,
|
||||
none,
|
||||
}
|
||||
@@ -52,16 +59,22 @@ extension FDSourceTypeExtension on FDSourceType {
|
||||
/// [toLocalizedString] returns a localized string for a source type.
|
||||
String toLocalizedString() {
|
||||
switch (this) {
|
||||
case FDSourceType.fourchan:
|
||||
return '4chan';
|
||||
case FDSourceType.github:
|
||||
return 'GitHub';
|
||||
case FDSourceType.googlenews:
|
||||
return 'Google News';
|
||||
case FDSourceType.lemmy:
|
||||
return 'Lemmy';
|
||||
case FDSourceType.mastodon:
|
||||
return 'Mastodon';
|
||||
case FDSourceType.medium:
|
||||
return 'Medium';
|
||||
case FDSourceType.nitter:
|
||||
return 'Nitter';
|
||||
case FDSourceType.pinterest:
|
||||
return 'Pinterest';
|
||||
case FDSourceType.podcast:
|
||||
return 'Podcast';
|
||||
case FDSourceType.reddit:
|
||||
@@ -72,8 +85,6 @@ extension FDSourceTypeExtension on FDSourceType {
|
||||
return 'StackOverflow';
|
||||
case FDSourceType.tumblr:
|
||||
return 'Tumblr';
|
||||
// case FDSourceType.x:
|
||||
// return 'X';
|
||||
case FDSourceType.youtube:
|
||||
return 'YouTube';
|
||||
default:
|
||||
@@ -81,20 +92,61 @@ extension FDSourceTypeExtension on FDSourceType {
|
||||
}
|
||||
}
|
||||
|
||||
/// [color] returns the brand color for a source type, which can be used as
|
||||
/// background color for the icon of a source type.
|
||||
Color get color {
|
||||
/// [icon] returns the icon for a source.
|
||||
IconData get icon {
|
||||
switch (this) {
|
||||
case FDSourceType.fourchan:
|
||||
return FDIcons.fourchan;
|
||||
case FDSourceType.github:
|
||||
return FDIcons.github;
|
||||
case FDSourceType.googlenews:
|
||||
return FDIcons.googlenews;
|
||||
case FDSourceType.lemmy:
|
||||
return FDIcons.lemmy;
|
||||
case FDSourceType.mastodon:
|
||||
return FDIcons.mastodon;
|
||||
case FDSourceType.medium:
|
||||
return FDIcons.medium;
|
||||
case FDSourceType.nitter:
|
||||
return FDIcons.nitter;
|
||||
case FDSourceType.pinterest:
|
||||
return FDIcons.pinterest;
|
||||
case FDSourceType.podcast:
|
||||
return Icons.podcasts;
|
||||
case FDSourceType.reddit:
|
||||
return FDIcons.reddit;
|
||||
case FDSourceType.rss:
|
||||
return FDIcons.rss;
|
||||
case FDSourceType.stackoverflow:
|
||||
return FDIcons.stackoverflow;
|
||||
case FDSourceType.tumblr:
|
||||
return FDIcons.tumblr;
|
||||
case FDSourceType.youtube:
|
||||
return FDIcons.youtube;
|
||||
default:
|
||||
return FDIcons.feeddeck;
|
||||
}
|
||||
}
|
||||
|
||||
/// [bgColor] returns the background color for the source icon.
|
||||
Color get bgColor {
|
||||
switch (this) {
|
||||
case FDSourceType.fourchan:
|
||||
return const Color(0xff880000);
|
||||
case FDSourceType.github:
|
||||
return const Color(0xff000000);
|
||||
case FDSourceType.googlenews:
|
||||
return const Color(0xff4285f4);
|
||||
case FDSourceType.lemmy:
|
||||
return const Color(0xff00bc8c);
|
||||
case FDSourceType.mastodon:
|
||||
return const Color(0xff6364ff);
|
||||
case FDSourceType.medium:
|
||||
return const Color(0xff000000);
|
||||
case FDSourceType.nitter:
|
||||
return const Color(0xffff6c60);
|
||||
case FDSourceType.pinterest:
|
||||
return const Color(0xffe60023);
|
||||
case FDSourceType.podcast:
|
||||
return const Color(0xff872ec4);
|
||||
case FDSourceType.reddit:
|
||||
@@ -105,14 +157,49 @@ extension FDSourceTypeExtension on FDSourceType {
|
||||
return const Color(0xffef8236);
|
||||
case FDSourceType.tumblr:
|
||||
return const Color(0xff34526f);
|
||||
// case FDSourceType.x:
|
||||
// return const Color(0xff000000);
|
||||
case FDSourceType.youtube:
|
||||
return const Color(0xffff0000);
|
||||
default:
|
||||
return Constants.primary;
|
||||
}
|
||||
}
|
||||
|
||||
/// [fgColor] returns the forground color for the source icon. This should be
|
||||
/// used toether with the [bgColor].
|
||||
Color get fgColor {
|
||||
switch (this) {
|
||||
case FDSourceType.fourchan:
|
||||
return const Color(0xffffffff);
|
||||
case FDSourceType.github:
|
||||
return const Color(0xffffffff);
|
||||
case FDSourceType.googlenews:
|
||||
return const Color(0xffffffff);
|
||||
case FDSourceType.lemmy:
|
||||
return const Color(0xffffffff);
|
||||
case FDSourceType.mastodon:
|
||||
return const Color(0xffffffff);
|
||||
case FDSourceType.medium:
|
||||
return const Color(0xffffffff);
|
||||
case FDSourceType.nitter:
|
||||
return const Color(0xffffffff);
|
||||
case FDSourceType.pinterest:
|
||||
return const Color(0xffffffff);
|
||||
case FDSourceType.podcast:
|
||||
return const Color(0xffffffff);
|
||||
case FDSourceType.reddit:
|
||||
return const Color(0xffffffff);
|
||||
case FDSourceType.rss:
|
||||
return const Color(0xffffffff);
|
||||
case FDSourceType.stackoverflow:
|
||||
return const Color(0xffffffff);
|
||||
case FDSourceType.tumblr:
|
||||
return const Color(0xffffffff);
|
||||
case FDSourceType.youtube:
|
||||
return const Color(0xffffffff);
|
||||
default:
|
||||
return Constants.onPrimary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [getSourceTypeFromString] returns the [FDSourceType] from his string
|
||||
@@ -151,20 +238,25 @@ class FDSource {
|
||||
required this.icon,
|
||||
});
|
||||
|
||||
String get prettyTitle => title.replaceAll(RegExp(r'(?! )\s+| \s+'), ' ');
|
||||
|
||||
factory FDSource.fromJson(Map<String, dynamic> data) {
|
||||
return FDSource(
|
||||
id: data['id'],
|
||||
type: getSourceTypeFromString(data['type']),
|
||||
title: data['title'],
|
||||
options: data.containsKey('options') && data['options'] != null
|
||||
? FDSourceOptions.fromJson(data['options'])
|
||||
: null,
|
||||
link: data.containsKey('link') && data['link'] != null
|
||||
? data['link']
|
||||
: null,
|
||||
icon: data.containsKey('icon') && data['icon'] != null
|
||||
? data['icon']
|
||||
: null,
|
||||
options:
|
||||
data.containsKey('options') && data['options'] != null
|
||||
? FDSourceOptions.fromJson(data['options'])
|
||||
: null,
|
||||
link:
|
||||
data.containsKey('link') && data['link'] != null
|
||||
? data['link']
|
||||
: null,
|
||||
icon:
|
||||
data.containsKey('icon') && data['icon'] != null
|
||||
? data['icon']
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -173,58 +265,113 @@ class FDSource {
|
||||
'id': id,
|
||||
'type': type.toShortString(),
|
||||
'title': title,
|
||||
'options': options != null ? options!.toJson() : null,
|
||||
'options': options?.toJson(),
|
||||
'link': link,
|
||||
'icon': icon,
|
||||
};
|
||||
}
|
||||
|
||||
factory FDSource.fromXml(XmlElement element) {
|
||||
final type = getSourceTypeFromString(element.getAttribute('type') ?? '');
|
||||
final text = element.getAttribute('text');
|
||||
|
||||
if (type == FDSourceType.none || text == null) {
|
||||
return FDSource(
|
||||
id: '',
|
||||
type: FDSourceType.none,
|
||||
title: '',
|
||||
options: null,
|
||||
link: null,
|
||||
icon: null,
|
||||
);
|
||||
}
|
||||
|
||||
return FDSource(
|
||||
id: '',
|
||||
type: type,
|
||||
title: text,
|
||||
options: FDSourceOptions.fromXml(element),
|
||||
link: null,
|
||||
icon: null,
|
||||
);
|
||||
}
|
||||
|
||||
void toXml(XmlBuilder builder) {
|
||||
builder.element(
|
||||
'outline',
|
||||
nest: () {
|
||||
builder.attribute('type', type.toShortString());
|
||||
builder.attribute('text', prettyTitle);
|
||||
builder.attribute('htmlUrl', link);
|
||||
|
||||
if (options != null) {
|
||||
options!.toXml(builder);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// [FDSourceOptions] defines all options for the different source types which
|
||||
/// are available.
|
||||
class FDSourceOptions {
|
||||
String? fourchan;
|
||||
FDGitHubOptions? github;
|
||||
FDGoogleNewsOptions? googlenews;
|
||||
String? lemmy;
|
||||
String? mastodon;
|
||||
String? medium;
|
||||
String? nitter;
|
||||
String? pinterest;
|
||||
String? podcast;
|
||||
String? reddit;
|
||||
String? rss;
|
||||
FDStackOverflowOptions? stackoverflow;
|
||||
String? tumblr;
|
||||
String? x;
|
||||
String? youtube;
|
||||
|
||||
FDSourceOptions({
|
||||
this.fourchan,
|
||||
this.github,
|
||||
this.googlenews,
|
||||
this.lemmy,
|
||||
this.mastodon,
|
||||
this.medium,
|
||||
this.nitter,
|
||||
this.pinterest,
|
||||
this.podcast,
|
||||
this.reddit,
|
||||
this.rss,
|
||||
this.stackoverflow,
|
||||
this.tumblr,
|
||||
this.x,
|
||||
this.youtube,
|
||||
});
|
||||
|
||||
factory FDSourceOptions.fromJson(Map<String, dynamic> responseData) {
|
||||
return FDSourceOptions(
|
||||
fourchan:
|
||||
responseData.containsKey('fourchan') &&
|
||||
responseData['fourchan'] != null
|
||||
? responseData['fourchan']
|
||||
: null,
|
||||
github:
|
||||
responseData.containsKey('github') && responseData['github'] != null
|
||||
? FDGitHubOptions.fromJson(responseData['github'])
|
||||
: null,
|
||||
googlenews: responseData.containsKey('googlenews') &&
|
||||
responseData['googlenews'] != null
|
||||
? FDGoogleNewsOptions.fromJson(responseData['googlenews'])
|
||||
: null,
|
||||
mastodon: responseData.containsKey('mastodon') &&
|
||||
responseData['mastodon'] != null
|
||||
? responseData['mastodon']
|
||||
: null,
|
||||
googlenews:
|
||||
responseData.containsKey('googlenews') &&
|
||||
responseData['googlenews'] != null
|
||||
? FDGoogleNewsOptions.fromJson(responseData['googlenews'])
|
||||
: null,
|
||||
lemmy:
|
||||
responseData.containsKey('lemmy') && responseData['lemmy'] != null
|
||||
? responseData['lemmy']
|
||||
: null,
|
||||
mastodon:
|
||||
responseData.containsKey('mastodon') &&
|
||||
responseData['mastodon'] != null
|
||||
? responseData['mastodon']
|
||||
: null,
|
||||
medium:
|
||||
responseData.containsKey('medium') && responseData['medium'] != null
|
||||
? responseData['medium']
|
||||
@@ -233,6 +380,11 @@ class FDSourceOptions {
|
||||
responseData.containsKey('nitter') && responseData['nitter'] != null
|
||||
? responseData['nitter']
|
||||
: null,
|
||||
pinterest:
|
||||
responseData.containsKey('pinterest') &&
|
||||
responseData['pinterest'] != null
|
||||
? responseData['pinterest']
|
||||
: null,
|
||||
podcast:
|
||||
responseData.containsKey('podcast') && responseData['podcast'] != null
|
||||
? responseData['podcast']
|
||||
@@ -241,20 +393,19 @@ class FDSourceOptions {
|
||||
responseData.containsKey('reddit') && responseData['reddit'] != null
|
||||
? responseData['reddit']
|
||||
: null,
|
||||
rss: responseData.containsKey('rss') && responseData['rss'] != null
|
||||
? responseData['rss']
|
||||
: null,
|
||||
stackoverflow: responseData.containsKey('stackoverflow') &&
|
||||
responseData['stackoverflow'] != null
|
||||
? FDStackOverflowOptions.fromJson(responseData['stackoverflow'])
|
||||
: null,
|
||||
rss:
|
||||
responseData.containsKey('rss') && responseData['rss'] != null
|
||||
? responseData['rss']
|
||||
: null,
|
||||
stackoverflow:
|
||||
responseData.containsKey('stackoverflow') &&
|
||||
responseData['stackoverflow'] != null
|
||||
? FDStackOverflowOptions.fromJson(responseData['stackoverflow'])
|
||||
: null,
|
||||
tumblr:
|
||||
responseData.containsKey('tumblr') && responseData['tumblr'] != null
|
||||
? responseData['tumblr']
|
||||
: null,
|
||||
x: responseData.containsKey('x') && responseData['x'] != null
|
||||
? responseData['x']
|
||||
: null,
|
||||
youtube:
|
||||
responseData.containsKey('youtube') && responseData['youtube'] != null
|
||||
? responseData['youtube']
|
||||
@@ -264,18 +415,71 @@ class FDSourceOptions {
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'fourchan': fourchan,
|
||||
'github': github?.toJson(),
|
||||
'googlenews': googlenews?.toJson(),
|
||||
'lemmy': lemmy,
|
||||
'mastodon': mastodon,
|
||||
'medium': medium,
|
||||
'nitter': nitter,
|
||||
'pinterest': pinterest,
|
||||
'podcast': podcast,
|
||||
'reddit': reddit,
|
||||
'rss': rss,
|
||||
'stackoverflow': stackoverflow?.toJson(),
|
||||
'tumblr': tumblr,
|
||||
'x': x,
|
||||
'youtube': youtube,
|
||||
};
|
||||
}
|
||||
|
||||
factory FDSourceOptions.fromXml(XmlElement element) {
|
||||
return FDSourceOptions(
|
||||
fourchan: element.getAttribute('fourchan'),
|
||||
github: FDGitHubOptions.fromXml(element),
|
||||
googlenews: FDGoogleNewsOptions.fromXml(element),
|
||||
lemmy: element.getAttribute('lemmy'),
|
||||
mastodon: element.getAttribute('mastodon'),
|
||||
medium: element.getAttribute('medium'),
|
||||
nitter: element.getAttribute('nitter'),
|
||||
pinterest: element.getAttribute('pinterest'),
|
||||
podcast: element.getAttribute('podcast'),
|
||||
reddit: element.getAttribute('reddit'),
|
||||
rss: element.getAttribute('xmlUrl'),
|
||||
stackoverflow: FDStackOverflowOptions.fromXml(element),
|
||||
tumblr: element.getAttribute('tumblr'),
|
||||
youtube: element.getAttribute('youtube'),
|
||||
);
|
||||
}
|
||||
|
||||
void toXml(XmlBuilder builder) {
|
||||
if (fourchan != null) {
|
||||
builder.attribute('fourchan', fourchan);
|
||||
} else if (github != null) {
|
||||
github!.toXml(builder);
|
||||
} else if (googlenews != null) {
|
||||
googlenews!.toXml(builder);
|
||||
} else if (lemmy != null) {
|
||||
builder.attribute('lemmy', lemmy);
|
||||
} else if (mastodon != null) {
|
||||
builder.attribute('mastodon', mastodon);
|
||||
} else if (medium != null) {
|
||||
builder.attribute('medium', medium);
|
||||
} else if (nitter != null) {
|
||||
builder.attribute('nitter', nitter);
|
||||
} else if (pinterest != null) {
|
||||
builder.attribute('pinterest', pinterest);
|
||||
} else if (podcast != null) {
|
||||
builder.attribute('podcast', podcast);
|
||||
} else if (reddit != null) {
|
||||
builder.attribute('reddit', reddit);
|
||||
} else if (rss != null) {
|
||||
builder.attribute('xmlUrl', rss);
|
||||
} else if (stackoverflow != null) {
|
||||
stackoverflow!.toXml(builder);
|
||||
} else if (tumblr != null) {
|
||||
builder.attribute('tumblr', tumblr);
|
||||
} else if (youtube != null) {
|
||||
builder.attribute('youtube', youtube);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:xml/xml.dart';
|
||||
|
||||
/// [FDGitHubType] is an enum value which defines the type for the GitHub
|
||||
/// source.
|
||||
enum FDGitHubType {
|
||||
@@ -88,31 +90,38 @@ class FDGitHubOptions {
|
||||
|
||||
factory FDGitHubOptions.fromJson(Map<String, dynamic> responseData) {
|
||||
return FDGitHubOptions(
|
||||
type: responseData.containsKey('type') && responseData['type'] != null
|
||||
? getGitHubTypeFromString(responseData['type'])
|
||||
: null,
|
||||
participating: responseData.containsKey('participating') &&
|
||||
responseData['participating'] != null
|
||||
? responseData['participating']
|
||||
: null,
|
||||
repository: responseData.containsKey('repository') &&
|
||||
responseData['repository'] != null
|
||||
? responseData['repository']
|
||||
: null,
|
||||
user: responseData.containsKey('user') && responseData['user'] != null
|
||||
? responseData['user']
|
||||
: null,
|
||||
organization: responseData.containsKey('organization') &&
|
||||
responseData['organization'] != null
|
||||
? responseData['organization']
|
||||
: null,
|
||||
queryName: responseData.containsKey('queryName') &&
|
||||
responseData['queryName'] != null
|
||||
? responseData['queryName']
|
||||
: null,
|
||||
query: responseData.containsKey('query') && responseData['query'] != null
|
||||
? responseData['query']
|
||||
: null,
|
||||
type:
|
||||
responseData.containsKey('type') && responseData['type'] != null
|
||||
? getGitHubTypeFromString(responseData['type'])
|
||||
: null,
|
||||
participating:
|
||||
responseData.containsKey('participating') &&
|
||||
responseData['participating'] != null
|
||||
? responseData['participating']
|
||||
: null,
|
||||
repository:
|
||||
responseData.containsKey('repository') &&
|
||||
responseData['repository'] != null
|
||||
? responseData['repository']
|
||||
: null,
|
||||
user:
|
||||
responseData.containsKey('user') && responseData['user'] != null
|
||||
? responseData['user']
|
||||
: null,
|
||||
organization:
|
||||
responseData.containsKey('organization') &&
|
||||
responseData['organization'] != null
|
||||
? responseData['organization']
|
||||
: null,
|
||||
queryName:
|
||||
responseData.containsKey('queryName') &&
|
||||
responseData['queryName'] != null
|
||||
? responseData['queryName']
|
||||
: null,
|
||||
query:
|
||||
responseData.containsKey('query') && responseData['query'] != null
|
||||
? responseData['query']
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -127,4 +136,40 @@ class FDGitHubOptions {
|
||||
'query': query,
|
||||
};
|
||||
}
|
||||
|
||||
factory FDGitHubOptions.fromXml(XmlElement element) {
|
||||
return FDGitHubOptions(
|
||||
type: getGitHubTypeFromString(element.getAttribute('githubType') ?? ''),
|
||||
participating: element.getAttribute('githubParticipating') == 'true',
|
||||
repository: element.getAttribute('githubRepository'),
|
||||
user: element.getAttribute('githubUser'),
|
||||
organization: element.getAttribute('githubOrganization'),
|
||||
queryName: element.getAttribute('githubQueryName'),
|
||||
query: element.getAttribute('githubQuery'),
|
||||
);
|
||||
}
|
||||
|
||||
void toXml(XmlBuilder builder) {
|
||||
if (type != null) {
|
||||
builder.attribute('githubType', type!.toShortString());
|
||||
}
|
||||
if (participating != null) {
|
||||
builder.attribute('githubParticipating', participating);
|
||||
}
|
||||
if (repository != null) {
|
||||
builder.attribute('githubRepository', repository);
|
||||
}
|
||||
if (user != null) {
|
||||
builder.attribute('githubUser', user);
|
||||
}
|
||||
if (organization != null) {
|
||||
builder.attribute('githubOrganization', organization);
|
||||
}
|
||||
if (queryName != null) {
|
||||
builder.attribute('githubQueryName', queryName);
|
||||
}
|
||||
if (query != null) {
|
||||
builder.attribute('githubQuery', query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import 'package:xml/xml.dart';
|
||||
|
||||
/// [FDGoogleNewsType] is an enum value which defines the type for the Google
|
||||
/// News source.
|
||||
enum FDGoogleNewsType {
|
||||
url,
|
||||
search,
|
||||
}
|
||||
enum FDGoogleNewsType { url, search }
|
||||
|
||||
/// [FDGoogleNewsTypeExtension] defines all extensions which are available for
|
||||
/// the [FDGoogleNewsType] enum type.
|
||||
@@ -61,25 +60,30 @@ class FDGoogleNewsOptions {
|
||||
|
||||
factory FDGoogleNewsOptions.fromJson(Map<String, dynamic> responseData) {
|
||||
return FDGoogleNewsOptions(
|
||||
type: responseData.containsKey('type') && responseData['type'] != null
|
||||
? getGoogleNewsTypeFromString(responseData['type'])
|
||||
: null,
|
||||
url: responseData.containsKey('url') && responseData['url'] != null
|
||||
? responseData['url']
|
||||
: null,
|
||||
type:
|
||||
responseData.containsKey('type') && responseData['type'] != null
|
||||
? getGoogleNewsTypeFromString(responseData['type'])
|
||||
: null,
|
||||
url:
|
||||
responseData.containsKey('url') && responseData['url'] != null
|
||||
? responseData['url']
|
||||
: null,
|
||||
search:
|
||||
responseData.containsKey('search') && responseData['search'] != null
|
||||
? responseData['search']
|
||||
: null,
|
||||
ceid: responseData.containsKey('ceid') && responseData['ceid'] != null
|
||||
? responseData['ceid']
|
||||
: null,
|
||||
gl: responseData.containsKey('gl') && responseData['gl'] != null
|
||||
? responseData['gl']
|
||||
: null,
|
||||
hl: responseData.containsKey('hl') && responseData['hl'] != null
|
||||
? responseData['hl']
|
||||
: null,
|
||||
ceid:
|
||||
responseData.containsKey('ceid') && responseData['ceid'] != null
|
||||
? responseData['ceid']
|
||||
: null,
|
||||
gl:
|
||||
responseData.containsKey('gl') && responseData['gl'] != null
|
||||
? responseData['gl']
|
||||
: null,
|
||||
hl:
|
||||
responseData.containsKey('hl') && responseData['hl'] != null
|
||||
? responseData['hl']
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -93,4 +97,38 @@ class FDGoogleNewsOptions {
|
||||
'hl': hl,
|
||||
};
|
||||
}
|
||||
|
||||
factory FDGoogleNewsOptions.fromXml(XmlElement element) {
|
||||
return FDGoogleNewsOptions(
|
||||
type: getGoogleNewsTypeFromString(
|
||||
element.getAttribute('googlenewsType') ?? '',
|
||||
),
|
||||
url: element.getAttribute('googlenewsUrl'),
|
||||
search: element.getAttribute('googlenewsSearch'),
|
||||
ceid: element.getAttribute('googlenewsCeid'),
|
||||
gl: element.getAttribute('googlenewsGl'),
|
||||
hl: element.getAttribute('googlenewsHl'),
|
||||
);
|
||||
}
|
||||
|
||||
void toXml(XmlBuilder builder) {
|
||||
if (type != null) {
|
||||
builder.attribute('googlenewsType', type!.toShortString());
|
||||
}
|
||||
if (url != null) {
|
||||
builder.attribute('googlenewsUrl', url);
|
||||
}
|
||||
if (search != null) {
|
||||
builder.attribute('googlenewsSearch', search);
|
||||
}
|
||||
if (ceid != null) {
|
||||
builder.attribute('googlenewsCeid', ceid);
|
||||
}
|
||||
if (gl != null) {
|
||||
builder.attribute('googlenewsGl', gl);
|
||||
}
|
||||
if (hl != null) {
|
||||
builder.attribute('googlenewsHl', hl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import 'package:xml/xml.dart';
|
||||
|
||||
/// [FDStackOverflowType] is an enum value which defines the type for the
|
||||
/// StackOverflow source.
|
||||
enum FDStackOverflowType {
|
||||
url,
|
||||
tag,
|
||||
}
|
||||
enum FDStackOverflowType { url, tag }
|
||||
|
||||
/// [FDStackOverflowTypeExtension] defines all extensions which are available for
|
||||
/// the [FDStackOverflowType] enum type.
|
||||
@@ -42,12 +41,7 @@ FDStackOverflowType getStackOverflowTypeFromString(String state) {
|
||||
|
||||
/// [FDStackOverflowSort] is an enum value which defines the available sort
|
||||
/// properties for the [FDStackOverflowOptions]
|
||||
enum FDStackOverflowSort {
|
||||
newest,
|
||||
active,
|
||||
featured,
|
||||
votes,
|
||||
}
|
||||
enum FDStackOverflowSort { newest, active, featured, votes }
|
||||
|
||||
/// [FDStackOverflowSortExtension] defines all extensions which are available
|
||||
/// for the [FDStackOverflowSort] enum type.
|
||||
@@ -100,27 +94,26 @@ class FDStackOverflowOptions {
|
||||
String? tag;
|
||||
FDStackOverflowSort? sort;
|
||||
|
||||
FDStackOverflowOptions({
|
||||
this.type,
|
||||
this.url,
|
||||
this.tag,
|
||||
this.sort,
|
||||
});
|
||||
FDStackOverflowOptions({this.type, this.url, this.tag, this.sort});
|
||||
|
||||
factory FDStackOverflowOptions.fromJson(Map<String, dynamic> responseData) {
|
||||
return FDStackOverflowOptions(
|
||||
type: responseData.containsKey('type') && responseData['type'] != null
|
||||
? getStackOverflowTypeFromString(responseData['type'])
|
||||
: null,
|
||||
url: responseData.containsKey('url') && responseData['url'] != null
|
||||
? responseData['url']
|
||||
: null,
|
||||
tag: responseData.containsKey('tag') && responseData['tag'] != null
|
||||
? responseData['tag']
|
||||
: null,
|
||||
sort: responseData.containsKey('sort') && responseData['sort'] != null
|
||||
? getStackOverflowSortFromString(responseData['sort'])
|
||||
: null,
|
||||
type:
|
||||
responseData.containsKey('type') && responseData['type'] != null
|
||||
? getStackOverflowTypeFromString(responseData['type'])
|
||||
: null,
|
||||
url:
|
||||
responseData.containsKey('url') && responseData['url'] != null
|
||||
? responseData['url']
|
||||
: null,
|
||||
tag:
|
||||
responseData.containsKey('tag') && responseData['tag'] != null
|
||||
? responseData['tag']
|
||||
: null,
|
||||
sort:
|
||||
responseData.containsKey('sort') && responseData['sort'] != null
|
||||
? getStackOverflowSortFromString(responseData['sort'])
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -132,4 +125,32 @@ class FDStackOverflowOptions {
|
||||
'sort': sort?.toShortString(),
|
||||
};
|
||||
}
|
||||
|
||||
factory FDStackOverflowOptions.fromXml(XmlElement element) {
|
||||
return FDStackOverflowOptions(
|
||||
type: getStackOverflowTypeFromString(
|
||||
element.getAttribute('stackoverflowType') ?? '',
|
||||
),
|
||||
url: element.getAttribute('stackoverflowUrl'),
|
||||
tag: element.getAttribute('stackoverflowTag'),
|
||||
sort: getStackOverflowSortFromString(
|
||||
element.getAttribute('stackoverflowSort') ?? '',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void toXml(XmlBuilder builder) {
|
||||
if (type != null) {
|
||||
builder.attribute('stackoverflowType', type!.toShortString());
|
||||
}
|
||||
if (url != null) {
|
||||
builder.attribute('stackoverflowUrl', url);
|
||||
}
|
||||
if (tag != null) {
|
||||
builder.attribute('stackoverflowTag', tag);
|
||||
}
|
||||
if (sort != null) {
|
||||
builder.attribute('stackoverflowSort', sort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,7 @@ import 'package:feeddeck/models/deck.dart';
|
||||
import 'package:feeddeck/models/source.dart';
|
||||
import 'package:feeddeck/utils/api_exception.dart';
|
||||
|
||||
enum FDAppStatus {
|
||||
uninitialized,
|
||||
authenticated,
|
||||
unauthenticated,
|
||||
}
|
||||
enum FDAppStatus { uninitialized, authenticated, unauthenticated }
|
||||
|
||||
/// [AppRepository] is the repository for our app. The repository is responsible
|
||||
/// for managing the state of our app, this includes the authentication status
|
||||
@@ -82,10 +78,7 @@ class AppRepository with ChangeNotifier {
|
||||
///
|
||||
/// If the user was signed in successfully, we run the same logic as in the
|
||||
/// [init] function, to set the active deck for the user.
|
||||
Future<void> signInWithPassword(
|
||||
String email,
|
||||
String password,
|
||||
) async {
|
||||
Future<void> signInWithPassword(String email, String password) async {
|
||||
await Supabase.instance.client.auth.signInWithPassword(
|
||||
email: email,
|
||||
password: password,
|
||||
@@ -115,9 +108,7 @@ class AppRepository with ChangeNotifier {
|
||||
///
|
||||
/// If the user was signed in successfully, we run the same logic as in the
|
||||
/// [init] function, to set the active deck for the user.
|
||||
Future<void> signInWithCallback(
|
||||
Uri uri,
|
||||
) async {
|
||||
Future<void> signInWithCallback(Uri uri) async {
|
||||
await Supabase.instance.client.auth.getSessionFromUrl(
|
||||
uri,
|
||||
storeSession: true,
|
||||
@@ -145,17 +136,16 @@ class AppRepository with ChangeNotifier {
|
||||
/// create a new deck for the user with the given name. After the deck was
|
||||
/// created, the deck is set as the users active deck and the deck is added to
|
||||
/// the list of decks.
|
||||
Future<void> createDeck(
|
||||
String name,
|
||||
) async {
|
||||
final data = await Supabase.instance.client
|
||||
.from('decks')
|
||||
.insert({
|
||||
'name': name,
|
||||
'userId': Supabase.instance.client.auth.currentUser!.id,
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
Future<void> createDeck(String name) async {
|
||||
final data =
|
||||
await Supabase.instance.client
|
||||
.from('decks')
|
||||
.insert({
|
||||
'name': name,
|
||||
'userId': Supabase.instance.client.auth.currentUser!.id,
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
|
||||
final newDeck = FDDeck.fromJson(data);
|
||||
|
||||
@@ -178,17 +168,28 @@ class AppRepository with ChangeNotifier {
|
||||
return List<FDDeck>.from(data.map((deck) => FDDeck.fromJson(deck)));
|
||||
}
|
||||
|
||||
/// [getDecksWithNotifiy] uses the [getDecks] function to get a list of all
|
||||
/// decks for the user, but instead of returning the decks it updates the
|
||||
/// [_decks] property and calls all the registered listeners.
|
||||
///
|
||||
/// This function can be used to trigger an update of the decks when they are
|
||||
/// created outside of the AppRepository, like it is done in the import
|
||||
/// process.
|
||||
Future<void> getDecksWithNotifiy() async {
|
||||
final decks = await getDecks();
|
||||
_decks = decks;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// [updateDeck] is called to update a deck for the user. The function takes
|
||||
/// a [deckId] and a [name] as parameters. The function calls the Supabase
|
||||
/// client to update the name of the deck. After the deck was updated, the
|
||||
/// deck is also updated in the list of decks.
|
||||
Future<void> updateDeck(
|
||||
String deckId,
|
||||
String name,
|
||||
) async {
|
||||
Future<void> updateDeck(String deckId, String name) async {
|
||||
await Supabase.instance.client
|
||||
.from('decks')
|
||||
.update({'name': name}).eq('id', deckId);
|
||||
.update({'name': name})
|
||||
.eq('id', deckId);
|
||||
|
||||
for (var i = 0; i < _decks.length; i++) {
|
||||
if (_decks[i].id == deckId) {
|
||||
@@ -205,9 +206,7 @@ class AppRepository with ChangeNotifier {
|
||||
/// the deck. After the deck was deleted, the deck is also removed from the
|
||||
/// list of decks. If the deleted deck was the active deck, the active deck is
|
||||
/// set to `null`.
|
||||
Future<void> deleteDeck(
|
||||
String deckId,
|
||||
) async {
|
||||
Future<void> deleteDeck(String deckId) async {
|
||||
await Supabase.instance.client.from('decks').delete().eq('id', deckId);
|
||||
|
||||
_decks.removeWhere((deck) => deck.id == deckId);
|
||||
@@ -223,9 +222,7 @@ class AppRepository with ChangeNotifier {
|
||||
/// to get the columns for the deck and all sources for each column. After the
|
||||
/// columns and sources are fetched, the active deck is set to the provided
|
||||
/// deckId and the columns and sources are stored in the repository.
|
||||
Future<void> selectDeck(
|
||||
String deckId,
|
||||
) async {
|
||||
Future<void> selectDeck(String deckId) async {
|
||||
final columns = await getColumns(deckId);
|
||||
for (final column in columns) {
|
||||
column.sources = await getSources(column.id);
|
||||
@@ -243,18 +240,18 @@ class AppRepository with ChangeNotifier {
|
||||
/// function takes a [name] as parameter. The function calls the Supabase
|
||||
/// client to create a new column for the active deck with the given name.
|
||||
/// Finally the newly created column is added to the list of columns.
|
||||
Future<void> createColumn(
|
||||
String name,
|
||||
) async {
|
||||
final data = await Supabase.instance.client.from('columns').insert({
|
||||
'deckId': _activeDeckId,
|
||||
'userId': Supabase.instance.client.auth.currentUser!.id,
|
||||
'name': name,
|
||||
'position': _columns.length,
|
||||
}).select();
|
||||
Future<void> createColumn(String name) async {
|
||||
final data =
|
||||
await Supabase.instance.client.from('columns').insert({
|
||||
'deckId': _activeDeckId,
|
||||
'userId': Supabase.instance.client.auth.currentUser!.id,
|
||||
'name': name,
|
||||
'position': _columns.length,
|
||||
}).select();
|
||||
|
||||
final newColumn =
|
||||
List<FDColumn>.from(data.map((column) => FDColumn.fromJson(column)));
|
||||
final newColumn = List<FDColumn>.from(
|
||||
data.map((column) => FDColumn.fromJson(column)),
|
||||
);
|
||||
_columns.addAll(newColumn);
|
||||
notifyListeners();
|
||||
}
|
||||
@@ -262,9 +259,7 @@ class AppRepository with ChangeNotifier {
|
||||
/// [getColumns] is called to get all columns for the deck with the provided
|
||||
/// [deckId]. The function calls the Supabase client to get all columns for
|
||||
/// the deck. The function returns a list of [FDColumn]s.
|
||||
Future<List<FDColumn>> getColumns(
|
||||
String deckId,
|
||||
) async {
|
||||
Future<List<FDColumn>> getColumns(String deckId) async {
|
||||
final data = await Supabase.instance.client
|
||||
.from('columns')
|
||||
.select('id, name, position')
|
||||
@@ -276,9 +271,7 @@ class AppRepository with ChangeNotifier {
|
||||
/// [deleteColumn] is called to delete a column with the provided [columnId].
|
||||
/// The function calls the Supabase client to delete the column. After the
|
||||
/// column was deleted, the column is also removed from the list of columns.
|
||||
Future<void> deleteColumn(
|
||||
String columnId,
|
||||
) async {
|
||||
Future<void> deleteColumn(String columnId) async {
|
||||
await Supabase.instance.client.from('columns').delete().eq('id', columnId);
|
||||
|
||||
_columns.removeWhere((column) => column.id == columnId);
|
||||
@@ -289,13 +282,11 @@ class AppRepository with ChangeNotifier {
|
||||
/// The function takes a [name] as parameter. The function calls the Supabase
|
||||
/// client to update the name of the column. After the column was updated, the
|
||||
/// column is also updated in the list of columns.
|
||||
Future<void> updateColumn(
|
||||
String columnId,
|
||||
String name,
|
||||
) async {
|
||||
Future<void> updateColumn(String columnId, String name) async {
|
||||
await Supabase.instance.client
|
||||
.from('columns')
|
||||
.update({'name': name}).eq('id', columnId);
|
||||
.update({'name': name})
|
||||
.eq('id', columnId);
|
||||
|
||||
for (var i = 0; i < _columns.length; i++) {
|
||||
if (_columns[i].id == columnId) {
|
||||
@@ -311,22 +302,15 @@ class AppRepository with ChangeNotifier {
|
||||
/// with the provided [index1] and [index2]. The function calls the Supabase
|
||||
/// client to update the positions of the columns. After the columns were
|
||||
/// updated, the columns are also updated in the list of columns.
|
||||
Future<void> updateColumnPositions(
|
||||
int index1,
|
||||
int index2,
|
||||
) async {
|
||||
Future<void> updateColumnPositions(int index1, int index2) async {
|
||||
await Supabase.instance.client
|
||||
.from('columns')
|
||||
.update({'position': _columns[index2].position}).eq(
|
||||
'id',
|
||||
_columns[index1].id,
|
||||
);
|
||||
.update({'position': _columns[index2].position})
|
||||
.eq('id', _columns[index1].id);
|
||||
await Supabase.instance.client
|
||||
.from('columns')
|
||||
.update({'position': _columns[index1].position}).eq(
|
||||
'id',
|
||||
_columns[index2].id,
|
||||
);
|
||||
.update({'position': _columns[index1].position})
|
||||
.eq('id', _columns[index2].id);
|
||||
|
||||
final tmp = _columns[index1];
|
||||
_columns[index1] = _columns[index2];
|
||||
@@ -340,13 +324,19 @@ class AppRepository with ChangeNotifier {
|
||||
/// [getSources] is called to get all sources for the column with the provided
|
||||
/// [columnId]. The function calls the Supabase client to get all sources for
|
||||
/// the column. The function returns a list of [FDSource]s.
|
||||
Future<List<FDSource>> getSources(
|
||||
String columnId,
|
||||
) async {
|
||||
///
|
||||
/// The returned list of sources is ordered by the `position` field. Since the
|
||||
/// position field was added later there might be columns where the field is
|
||||
/// `null`, which will come after all columns with a `position`. The source
|
||||
/// where the position is `null` will be ordered by the `createdAt` date. This
|
||||
/// should retain the order as before the `position` field was added and
|
||||
/// should also work for new sources, which are added without a position.
|
||||
Future<List<FDSource>> getSources(String columnId) async {
|
||||
final data = await Supabase.instance.client
|
||||
.from('sources')
|
||||
.select('id, type, title, options, link, icon')
|
||||
.eq('columnId', columnId)
|
||||
.order('position', ascending: true, nullsFirst: false)
|
||||
.order('createdAt', ascending: true);
|
||||
return List<FDSource>.from(data.map((source) => FDSource.fromJson(source)));
|
||||
}
|
||||
@@ -355,10 +345,7 @@ class AppRepository with ChangeNotifier {
|
||||
/// The function calls the Supabase client to delete the source. After the
|
||||
/// source was deleted, the source is also removed from the list of sources of
|
||||
/// the column with the provided [columnId].
|
||||
Future<void> deleteSource(
|
||||
String columnId,
|
||||
String sourceId,
|
||||
) async {
|
||||
Future<void> deleteSource(String columnId, String sourceId) async {
|
||||
await Supabase.instance.client.from('sources').delete().eq('id', sourceId);
|
||||
|
||||
/// It could take some time before we can retrieve the items after a source
|
||||
@@ -376,21 +363,33 @@ class AppRepository with ChangeNotifier {
|
||||
}
|
||||
|
||||
/// [addSource] is called to add a source to the column with the provided
|
||||
/// [columnId]. The function takes a [source] as parameter. The function calls
|
||||
/// the `add-source-v1` edge function via the Supabase client to create the
|
||||
/// source. When the source was created the newly returned source is added to
|
||||
/// the list of sources of the column with the provided [columnId].
|
||||
/// [columnId]. Next to [columnId] a user must also provide the [type] and
|
||||
/// [options] for the source. The function calls the `add-or-update-source-v1`
|
||||
/// edge function via the Supabase client to create the source. When the
|
||||
/// source was created the newly returned source is added to the list of
|
||||
/// sources of the column with the provided [columnId].
|
||||
///
|
||||
/// The optional [feedData] parameter is used to provide the feed data for the
|
||||
/// source. This is can be used to scrape the source data via the client (app)
|
||||
/// instead of the server (scheduler / worker).
|
||||
Future<void> addSource(
|
||||
String columnId,
|
||||
FDSourceType type,
|
||||
FDSourceOptions options,
|
||||
) async {
|
||||
FDSourceOptions options, [
|
||||
String? feedData,
|
||||
]) async {
|
||||
final result = await Supabase.instance.client.functions.invoke(
|
||||
'add-source-v1',
|
||||
'add-or-update-source-v1',
|
||||
body: {
|
||||
'columnId': columnId,
|
||||
'type': type.toShortString(),
|
||||
'options': options.toJson(),
|
||||
'source': {
|
||||
'id': '',
|
||||
'columnId': columnId,
|
||||
'userId': '',
|
||||
'type': type.toShortString(),
|
||||
'title': '',
|
||||
'options': options.toJson(),
|
||||
},
|
||||
'feedData': feedData,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -411,4 +410,51 @@ class AppRepository with ChangeNotifier {
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// [updateSourcePositions] can be used to reorder the list of sources for a
|
||||
/// column. To achieve this order the list of sources for the provided
|
||||
/// [columnId] locally and update the `position` field of each source
|
||||
/// afterwards in the database.
|
||||
///
|
||||
/// We have to check if the user drags a source from top to bottom ([start]
|
||||
/// is lower then [current]) or from the bottom to the top ([start] is greater
|
||||
/// then [current]), to apply a different logic for the reordering.
|
||||
Future<void> updateSourcePositions(
|
||||
String columnId,
|
||||
int start,
|
||||
int current,
|
||||
) async {
|
||||
final columnIndex = _columns.indexWhere((column) => column.id == columnId);
|
||||
if (columnIndex == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (start < current) {
|
||||
int end = current - 1;
|
||||
FDSource startItem = _columns[columnIndex].sources[start];
|
||||
int i = 0;
|
||||
int local = start;
|
||||
do {
|
||||
_columns[columnIndex].sources[local] =
|
||||
_columns[columnIndex].sources[++local];
|
||||
i++;
|
||||
} while (i < end - start);
|
||||
_columns[columnIndex].sources[end] = startItem;
|
||||
} else if (start > current) {
|
||||
FDSource startItem = _columns[columnIndex].sources[start];
|
||||
for (int i = start; i > current; i--) {
|
||||
_columns[columnIndex].sources[i] = _columns[columnIndex].sources[i - 1];
|
||||
}
|
||||
_columns[columnIndex].sources[current] = startItem;
|
||||
}
|
||||
|
||||
for (var i = 0; i < _columns[columnIndex].sources.length; i++) {
|
||||
await Supabase.instance.client
|
||||
.from('sources')
|
||||
.update({'position': i})
|
||||
.eq('id', _columns[columnIndex].sources[i].id);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,12 +31,7 @@ class ItemsFilters {
|
||||
/// [ItemStateFilter] is a enum value which defines the state filter for items.
|
||||
/// The filter can be [read], [unread] or [bookmarked]. The [none] filter is
|
||||
/// used to return all items regardless if they are read, unread or bookmarked.
|
||||
enum ItemStateFilter {
|
||||
none,
|
||||
read,
|
||||
unread,
|
||||
bookmarked,
|
||||
}
|
||||
enum ItemStateFilter { none, read, unread, bookmarked }
|
||||
|
||||
/// The [ToString] extension defines a [toShortString] function, which returns a
|
||||
/// `String` which can be safely passed within a database query for the specifed
|
||||
@@ -53,11 +48,7 @@ extension ToString on ItemStateFilter {
|
||||
/// - [loading] during the time when the items are retrieved from our database
|
||||
/// - [loadedLast] when there are no more items which can be loaded from the
|
||||
/// database.
|
||||
enum ItemsStatus {
|
||||
loaded,
|
||||
loading,
|
||||
loadedLast,
|
||||
}
|
||||
enum ItemsStatus { loaded, loading, loadedLast }
|
||||
|
||||
/// [now] returns the current Unix timestamp in seconds.
|
||||
int now() {
|
||||
@@ -69,9 +60,7 @@ int now() {
|
||||
/// initialized we have to call the [_init] function to load the items for the
|
||||
/// provided column.
|
||||
class ItemsRepository with ChangeNotifier {
|
||||
ItemsRepository({
|
||||
required this.column,
|
||||
}) {
|
||||
ItemsRepository({required this.column}) {
|
||||
_init();
|
||||
}
|
||||
|
||||
@@ -105,9 +94,9 @@ class ItemsRepository with ChangeNotifier {
|
||||
Future<void> _init() async {
|
||||
final state = ItemsRepositoryStore().get(column.identifier());
|
||||
if (state != null) {
|
||||
_status = ItemsStatus.loaded;
|
||||
_items.addAll(state.items);
|
||||
_status = state.status;
|
||||
_filters = state.filters;
|
||||
_items.addAll(state.items);
|
||||
notifyListeners();
|
||||
} else {
|
||||
await _getItems();
|
||||
@@ -141,7 +130,7 @@ class ItemsRepository with ChangeNotifier {
|
||||
/// selected source which is stored in the [_filters.sourceIdFilter]
|
||||
/// field.
|
||||
if (_filters.sourceIdFilter != '') {
|
||||
filter = filter.eq('sourceId', sourceIdFilter);
|
||||
filter = filter.eq('sourceId', _filters.sourceIdFilter);
|
||||
}
|
||||
|
||||
filter = filter.lte('createdAt', _filters.createdAtFilter);
|
||||
@@ -168,7 +157,7 @@ class ItemsRepository with ChangeNotifier {
|
||||
/// filter to page through all the items.
|
||||
final data = await filter
|
||||
.order('publishedAt')
|
||||
.range(_filters.offsetFilter, _filters.offsetFilter + 50);
|
||||
.range(_filters.offsetFilter, _filters.offsetFilter + 50 - 1);
|
||||
|
||||
/// The returned items are added to the [_items] field and the status is
|
||||
/// set to [ItemsStatus.loaded] or [ItemsStatus.loadedLast] based on the
|
||||
@@ -189,7 +178,12 @@ class ItemsRepository with ChangeNotifier {
|
||||
/// Finally we store the state of the repository, so that we do not have
|
||||
/// to retrieve the items from the database again when therepository is
|
||||
/// reinitialized.
|
||||
ItemsRepositoryStore().set(column.identifier(), _items, _filters);
|
||||
ItemsRepositoryStore().set(
|
||||
column.identifier(),
|
||||
_status,
|
||||
_filters,
|
||||
_items,
|
||||
);
|
||||
notifyListeners();
|
||||
} catch (_) {
|
||||
_status = ItemsStatus.loaded;
|
||||
@@ -270,7 +264,8 @@ class ItemsRepository with ChangeNotifier {
|
||||
try {
|
||||
await Supabase.instance.client
|
||||
.from('items')
|
||||
.update({'isRead': read}).eq('id', itemId);
|
||||
.update({'isRead': read})
|
||||
.eq('id', itemId);
|
||||
for (var i = 0; i < _items.length; i++) {
|
||||
if (_items[i].id == itemId) {
|
||||
_items[i].isRead = read;
|
||||
@@ -278,7 +273,7 @@ class ItemsRepository with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
ItemsRepositoryStore().set(column.id, _items, _filters);
|
||||
ItemsRepositoryStore().set(column.id, _status, _filters, _items);
|
||||
notifyListeners();
|
||||
} catch (err) {
|
||||
rethrow;
|
||||
@@ -300,7 +295,8 @@ class ItemsRepository with ChangeNotifier {
|
||||
for (var i = 0; i < chunks.length; i++) {
|
||||
await Supabase.instance.client
|
||||
.from('items')
|
||||
.update({'isRead': read}).in_('id', chunks[i]);
|
||||
.update({'isRead': read})
|
||||
.inFilter('id', chunks[i]);
|
||||
for (var j = 0; j < _items.length; j++) {
|
||||
if (chunks[i].contains(_items[j].id)) {
|
||||
_items[j].isRead = read;
|
||||
@@ -308,7 +304,7 @@ class ItemsRepository with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
ItemsRepositoryStore().set(column.id, _items, _filters);
|
||||
ItemsRepositoryStore().set(column.id, _status, _filters, _items);
|
||||
notifyListeners();
|
||||
} catch (err) {
|
||||
rethrow;
|
||||
@@ -322,7 +318,8 @@ class ItemsRepository with ChangeNotifier {
|
||||
try {
|
||||
await Supabase.instance.client
|
||||
.from('items')
|
||||
.update({'isBookmarked': bookmarked}).eq('id', itemId);
|
||||
.update({'isBookmarked': bookmarked})
|
||||
.eq('id', itemId);
|
||||
for (var i = 0; i < _items.length; i++) {
|
||||
if (_items[i].id == itemId) {
|
||||
_items[i].isBookmarked = bookmarked;
|
||||
@@ -330,7 +327,7 @@ class ItemsRepository with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
ItemsRepositoryStore().set(column.id, _items, _filters);
|
||||
ItemsRepositoryStore().set(column.id, _status, _filters, _items);
|
||||
notifyListeners();
|
||||
} catch (err) {
|
||||
rethrow;
|
||||
@@ -342,12 +339,14 @@ class ItemsRepository with ChangeNotifier {
|
||||
/// in the [ItemsRepositoryStore]. The state contains all the loaded [items] and
|
||||
/// the [filters] set by user to load the items.
|
||||
class ItemsRepositoryStoreState {
|
||||
List<FDItem> items;
|
||||
ItemsStatus status;
|
||||
ItemsFilters filters;
|
||||
List<FDItem> items;
|
||||
|
||||
ItemsRepositoryStoreState({
|
||||
required this.items,
|
||||
required this.status,
|
||||
required this.filters,
|
||||
required this.items,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -362,7 +361,7 @@ class ItemsRepositoryStore {
|
||||
static final ItemsRepositoryStore _instance =
|
||||
ItemsRepositoryStore._internal();
|
||||
|
||||
final Map<String, ItemsRepositoryStoreState> itemsRepositoryStoreStates = {};
|
||||
final Map<String, ItemsRepositoryStoreState> _itemsRepositoryStoreStates = {};
|
||||
|
||||
factory ItemsRepositoryStore() {
|
||||
return _instance;
|
||||
@@ -375,7 +374,7 @@ class ItemsRepositoryStore {
|
||||
/// returns `null` to indicate that we have to get the items from the
|
||||
/// database.
|
||||
ItemsRepositoryStoreState? get(String columnId) {
|
||||
return itemsRepositoryStoreStates[columnId];
|
||||
return _itemsRepositoryStoreStates[columnId];
|
||||
}
|
||||
|
||||
/// [set] saves the [items] and [filters] for a [columnId] in the store. This
|
||||
@@ -384,10 +383,23 @@ class ItemsRepositoryStore {
|
||||
///
|
||||
/// The best is to call the [set] function right before we call
|
||||
/// `notifyListeners` in the repository.
|
||||
set(String columnId, List<FDItem> items, ItemsFilters filters) {
|
||||
return itemsRepositoryStoreStates[columnId] = ItemsRepositoryStoreState(
|
||||
items: items,
|
||||
ItemsRepositoryStoreState set(
|
||||
String columnId,
|
||||
ItemsStatus status,
|
||||
ItemsFilters filters,
|
||||
List<FDItem> items,
|
||||
) {
|
||||
return _itemsRepositoryStoreStates[columnId] = ItemsRepositoryStoreState(
|
||||
status: status,
|
||||
filters: filters,
|
||||
items: items,
|
||||
);
|
||||
}
|
||||
|
||||
/// [clear] deletes all the stored [_itemsRepositoryStoreStates] from the
|
||||
/// store. This method can be used to clear the cache, e.g. when a user
|
||||
/// changes the active deck or signes out.
|
||||
void clear() {
|
||||
_itemsRepositoryStoreStates.clear();
|
||||
}
|
||||
}
|
||||
|
||||
12
app/lib/repositories/layout_repository.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// The [LayoutRepository] is used to store several layout information of the
|
||||
/// app, which can be modifed or should be modifed within different locations.
|
||||
class LayoutRepository with ChangeNotifier {
|
||||
/// [_deckLayoutSmallInitialTabIndex] stores the selected tab index of the
|
||||
/// [DeckLayoutSmall] widget. This is used that we display the same tab when
|
||||
/// a user switches between the small and large layout (e.g. portrait and
|
||||
/// landscape mode on mobile devices) and that we can reset the tab index when
|
||||
/// a user selects a new deck.
|
||||
int deckLayoutSmallInitialTabIndex = 0;
|
||||
}
|
||||
@@ -8,12 +8,10 @@ class Constants {
|
||||
static const primary = Color(0xff49d3b4);
|
||||
static const onPrimary = Color(0xff1f2229);
|
||||
static const secondary = Color(0xff353a46);
|
||||
static const onSecondary = Color(0xffe2e4e9);
|
||||
static const onSecondary = Color(0xff49d3b4);
|
||||
static const error = Color(0xffde4A40);
|
||||
static const onError = Color(0xffe2e4e9);
|
||||
static const background = Color(0xff1f2229);
|
||||
static const onBackground = Color(0xffe2e4e9);
|
||||
static const surface = Color(0xff353a46);
|
||||
static const surface = Color(0xff1f2229);
|
||||
static const onSurface = Color(0xffe2e4e9);
|
||||
static const canvasColor = Color(0xff1f2229);
|
||||
static const appBarBackgroundColor = Colors.transparent;
|
||||
@@ -23,7 +21,7 @@ class Constants {
|
||||
static const secondaryTextColor = Color(0xff9aa1b2);
|
||||
|
||||
static const dividerColor = Color(0xff2a2e38);
|
||||
static const backgroundContainerBackgroundColor = Color(0xff14161a);
|
||||
static const surfaceContainerBackgroundColor = Color(0xff14161a);
|
||||
|
||||
static const breakpoint = 600.0;
|
||||
static const columnWidth = 352.0;
|
||||
|
||||
@@ -56,15 +56,15 @@ const _htmlAuthFinished = '''
|
||||
</html>
|
||||
''';
|
||||
|
||||
/// The [DesktopLoginManager] is used to authenticate a user with the provided
|
||||
/// The [DesktopSignInManager] is used to authenticate a user with the provided
|
||||
/// OAuth [provider] on desktop platforms.
|
||||
class DesktopLoginManager {
|
||||
final supabase.Provider provider;
|
||||
class DesktopSignInManager {
|
||||
final supabase.OAuthProvider provider;
|
||||
final Map<String, String>? queryParams;
|
||||
|
||||
HttpServer? redirectServer;
|
||||
|
||||
DesktopLoginManager({
|
||||
DesktopSignInManager({
|
||||
required this.provider,
|
||||
required this.queryParams,
|
||||
});
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
/// Flutter icons FDIcons
|
||||
/// Copyright (C) 2023 by original authors @ fluttericon.com, fontello.com
|
||||
/// Copyright (C) 2024 by original authors @ fluttericon.com, fontello.com
|
||||
/// This font was generated by FlutterIcon.com, which is derived from Fontello.
|
||||
///
|
||||
/// To use this font, place it in your fonts/ directory and include the
|
||||
@@ -11,11 +13,6 @@
|
||||
/// fonts:
|
||||
/// - asset: fonts/FDIcons.ttf
|
||||
///
|
||||
///
|
||||
///
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class FDIcons {
|
||||
FDIcons._();
|
||||
|
||||
@@ -32,6 +29,8 @@ class FDIcons {
|
||||
IconData(0xe803, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData googlenews =
|
||||
IconData(0xe804, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData pinterest =
|
||||
IconData(0xe805, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData medium =
|
||||
IconData(0xe806, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData nitter =
|
||||
@@ -64,4 +63,8 @@ class FDIcons {
|
||||
IconData(0xe814, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData mastodon =
|
||||
IconData(0xe815, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData lemmy =
|
||||
IconData(0xe816, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData fourchan =
|
||||
IconData(0xe817, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
}
|
||||
|
||||
55
app/lib/utils/get_feed.dart
Normal file
@@ -0,0 +1,55 @@
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'package:feeddeck/models/source.dart';
|
||||
import 'package:feeddeck/utils/api_exception.dart';
|
||||
|
||||
/// [getFeed] returns the feed for the provided [sourceType] and [options]. It
|
||||
/// can be used to fetch the feed for a source on the client side (app) instead
|
||||
/// of via the corresponding `add-or-update-source-v1` edge function or via our
|
||||
/// worker.
|
||||
///
|
||||
/// The functions for the different sources must implement the same parsing for
|
||||
/// the source options as it is done in the edge function.
|
||||
Future<String> getFeed(FDSourceType sourceType, FDSourceOptions options) async {
|
||||
switch (sourceType) {
|
||||
case FDSourceType.reddit:
|
||||
return getFeedReddit(options.reddit);
|
||||
default:
|
||||
throw const ApiException('Unknown source type', 400);
|
||||
}
|
||||
}
|
||||
|
||||
/// [getFeedReddit] returns the feed for the provided [input]. It is used to
|
||||
/// fetch the RSS feed for a Reddit source, which can be passed to the
|
||||
/// `add-or-update-source-v1` edge function.
|
||||
///
|
||||
/// The function must implement the same parsing logic as it is done in the
|
||||
/// `supabase/functions/_shared/feed/reddit.ts` file.
|
||||
Future<String> getFeedReddit(String? input) async {
|
||||
if (input == null || input.isEmpty) {
|
||||
throw const ApiException('No input provided', 400);
|
||||
}
|
||||
|
||||
String url = '';
|
||||
try {
|
||||
if (input.startsWith('/r/') || input.startsWith('/u/')) {
|
||||
url = 'https://www.reddit.com$input.rss';
|
||||
} else {
|
||||
final inputUri = Uri.parse(input);
|
||||
if (inputUri.host.endsWith('reddit.com')) {
|
||||
if (input.endsWith('.rss')) {
|
||||
url = input;
|
||||
} else {
|
||||
url = '$input.rss';
|
||||
}
|
||||
} else {
|
||||
throw const ApiException('Invalid input', 400);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
throw const ApiException('Invalid input', 400);
|
||||
}
|
||||
|
||||
final response = await http.get(Uri.parse(url));
|
||||
return response.body;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import 'package:feeddeck/repositories/settings_repository.dart';
|
||||
|
||||
/// [FDImageType] is a enum value which defines the image type. An image can be
|
||||
/// related to an item or a source.
|
||||
enum FDImageType {
|
||||
item,
|
||||
source,
|
||||
}
|
||||
|
||||
/// [getImageUrl] returns the correct image url to use for the provided image
|
||||
/// url:
|
||||
/// - If the [imageType] is [FDImageType.source] the image is always requested
|
||||
/// from the Supabase storage.
|
||||
/// - If the [imageType] is [FDImageType.item] and the app runs on the web, the
|
||||
/// image is proxied through the Supabase functions.
|
||||
/// - If the [imageType] is [FDImageType.item] and the app runs on a mobile or
|
||||
/// desktop device, the image is requested directly from the provided url.
|
||||
String getImageUrl(FDImageType imageType, String imageUrl) {
|
||||
if (imageType == FDImageType.source) {
|
||||
return '${SettingsRepository().supabaseUrl}/storage/v1/object/public/sources/$imageUrl';
|
||||
}
|
||||
|
||||
if (kIsWeb) {
|
||||
return '${Supabase.instance.client.functionsUrl}/image-proxy-v1?media=${Uri.encodeQueryComponent(imageUrl)}';
|
||||
}
|
||||
|
||||
return imageUrl;
|
||||
}
|
||||
@@ -4,24 +4,29 @@ import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
/// [openUrl] can be used to open the given [url] in the platforms default
|
||||
/// browser.
|
||||
/// [openUrl] can be used to open the given [url] in the specified launch mode.
|
||||
/// For iOS and Android we are using the In-App-Browser to launch the url, for
|
||||
/// all other platforms we are using the external browser.
|
||||
///
|
||||
/// On Android we are not using the default launch mode
|
||||
/// (`LaunchMode.platformDefault`), because the opened In-App-Browser is very
|
||||
/// limited, so that we decided to use `LaunchMode.externalApplication` to open
|
||||
/// the url.
|
||||
/// We do not have to check if the launch mode is really supported, because
|
||||
/// `launchUrl` will fallback to a supported launch mode, when our preferred
|
||||
/// mode is not supported.
|
||||
Future<void> openUrl(String url) async {
|
||||
var launchMode = LaunchMode.platformDefault;
|
||||
|
||||
if (!kIsWeb) {
|
||||
if (Platform.isAndroid) {
|
||||
launchMode = LaunchMode.externalApplication;
|
||||
}
|
||||
if (kIsWeb) {
|
||||
launchMode = LaunchMode.externalApplication;
|
||||
} else if (Platform.isAndroid) {
|
||||
launchMode = LaunchMode.inAppBrowserView;
|
||||
} else if (Platform.isIOS) {
|
||||
launchMode = LaunchMode.inAppBrowserView;
|
||||
} else if (Platform.isMacOS) {
|
||||
launchMode = LaunchMode.externalApplication;
|
||||
} else if (Platform.isLinux) {
|
||||
launchMode = LaunchMode.externalApplication;
|
||||
} else if (Platform.isWindows) {
|
||||
launchMode = LaunchMode.externalApplication;
|
||||
}
|
||||
|
||||
await launchUrl(
|
||||
Uri.parse(url),
|
||||
mode: launchMode,
|
||||
);
|
||||
await launchUrl(Uri.parse(url), mode: launchMode);
|
||||
}
|
||||
|
||||
33
app/lib/utils/signin_with_apple.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
/// [signInWithApple] performs Apple sign in on iOS and macOS.
|
||||
/// See https://supabase.com/docs/guides/auth/social-login/auth-apple?platform=flutter#using-native-sign-in-with-apple-in-flutter
|
||||
Future<AuthResponse> signInWithApple() async {
|
||||
final rawNonce = Supabase.instance.client.auth.generateRawNonce();
|
||||
final hashedNonce = sha256.convert(utf8.encode(rawNonce)).toString();
|
||||
|
||||
final credential = await SignInWithApple.getAppleIDCredential(
|
||||
scopes: [
|
||||
AppleIDAuthorizationScopes.email,
|
||||
AppleIDAuthorizationScopes.fullName,
|
||||
],
|
||||
nonce: hashedNonce,
|
||||
);
|
||||
|
||||
final idToken = credential.identityToken;
|
||||
if (idToken == null) {
|
||||
throw const AuthException(
|
||||
'Could not find ID Token from generated credential.',
|
||||
);
|
||||
}
|
||||
|
||||
return Supabase.instance.client.auth.signInWithIdToken(
|
||||
provider: OAuthProvider.apple,
|
||||
idToken: idToken,
|
||||
nonce: rawNonce,
|
||||
);
|
||||
}
|
||||
@@ -164,6 +164,8 @@ class _CreateColumnState extends State<CreateColumn> {
|
||||
padding: const EdgeInsets.all(Constants.spacingMiddle),
|
||||
child: ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Constants.secondary,
|
||||
foregroundColor: Constants.onSecondary,
|
||||
maximumSize: const Size.fromHeight(
|
||||
Constants.elevatedButtonSize,
|
||||
),
|
||||
|
||||
@@ -101,7 +101,7 @@ class _ColumnLayoutHeaderState extends State<ColumnLayoutHeader> {
|
||||
),
|
||||
),
|
||||
centerTitle: false,
|
||||
backgroundColor: Constants.background,
|
||||
backgroundColor: Constants.surface,
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -121,7 +121,7 @@ class _ColumnLayoutHeaderState extends State<ColumnLayoutHeader> {
|
||||
),
|
||||
Text(
|
||||
Characters(
|
||||
'${items.column.sources.length} ${items.column.sources.length == 1 ? 'Source' : 'Sources'} / ${items.items.length} ${items.items.length == 1 ? 'Item' : 'Items'}',
|
||||
'${items.column.sources.length} ${items.column.sources.length == 1 ? 'Source' : 'Sources'} / ${items.items.length}${items.status == ItemsStatus.loaded ? '+' : ''} ${items.items.length == 1 ? 'Item' : 'Items'}',
|
||||
)
|
||||
.replaceAll(
|
||||
Characters(''),
|
||||
@@ -216,7 +216,7 @@ class _ColumnLayoutHeaderState extends State<ColumnLayoutHeader> {
|
||||
width: double.infinity,
|
||||
height: _showSettings,
|
||||
decoration: const BoxDecoration(
|
||||
color: Constants.backgroundContainerBackgroundColor,
|
||||
color: Constants.surfaceContainerBackgroundColor,
|
||||
),
|
||||
padding: const EdgeInsets.all(Constants.spacingMiddle),
|
||||
child: ColumnLayoutHeaderSettings(
|
||||
|
||||
@@ -31,23 +31,22 @@ class _ColumnLayoutHeaderSettingsDeleteColumnState
|
||||
/// [_showDeleteDialog] creates a new dialog, which is shown before the column
|
||||
/// can be deleted. This is done to raise the awareness that the column,
|
||||
/// sources and items which belongs to the column will also be deleted.
|
||||
_showDeleteDialog() {
|
||||
void _showDeleteDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
insetPadding: EdgeInsets.symmetric(
|
||||
horizontal: MediaQuery.of(context).size.width >=
|
||||
horizontal:
|
||||
MediaQuery.of(context).size.width >=
|
||||
(Constants.centeredFormMaxWidth +
|
||||
2 * Constants.spacingMiddle)
|
||||
? (MediaQuery.of(context).size.width -
|
||||
Constants.centeredFormMaxWidth) /
|
||||
2
|
||||
Constants.centeredFormMaxWidth) /
|
||||
2
|
||||
: Constants.spacingMiddle,
|
||||
),
|
||||
title: const Text(
|
||||
'Delete Column',
|
||||
),
|
||||
title: const Text('Delete Column'),
|
||||
content: const Text(
|
||||
'Do you really want to delete this column? This can not be undone and will also delete all sources, items and bookmarks related to this column.',
|
||||
),
|
||||
@@ -88,8 +87,10 @@ class _ColumnLayoutHeaderSettingsDeleteColumnState
|
||||
});
|
||||
|
||||
try {
|
||||
await Provider.of<AppRepository>(context, listen: false)
|
||||
.deleteColumn(widget.column.id);
|
||||
await Provider.of<AppRepository>(
|
||||
context,
|
||||
listen: false,
|
||||
).deleteColumn(widget.column.id);
|
||||
} catch (_) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
||||
@@ -32,28 +32,66 @@ class ColumnLayoutHeaderSettingsSources extends StatefulWidget {
|
||||
|
||||
class _ColumnLayoutHeaderSettingsSourcesState
|
||||
extends State<ColumnLayoutHeaderSettingsSources> {
|
||||
/// [_proxyDecorator] is used to highlight the source which is currently
|
||||
/// draged by the user.
|
||||
Widget _proxyDecorator(Widget child, int index, Animation<double> animation) {
|
||||
return Material(
|
||||
elevation: 0,
|
||||
color: Colors.transparent,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 16,
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
elevation: 24,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
),
|
||||
child,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// [_buildSourcesList] returns a list of all sources of the current column.
|
||||
/// If the list of sources is empty it will return a [Container].
|
||||
///
|
||||
/// Each source in the list also contains a delete item, which can be used to
|
||||
/// remove the source from the current column.
|
||||
List<Widget> _buildSourcesList() {
|
||||
Widget _buildSourcesList() {
|
||||
if (widget.column.sources.isEmpty) {
|
||||
return [Container()];
|
||||
return Container();
|
||||
}
|
||||
|
||||
List<Widget> columns = [];
|
||||
return ReorderableListView.builder(
|
||||
shrinkWrap: true,
|
||||
buildDefaultDragHandles: false,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
onReorder: (int start, int current) {
|
||||
final AppRepository appRepository = Provider.of<AppRepository>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
|
||||
for (var i = 0; i < widget.column.sources.length; i++) {
|
||||
columns.add(
|
||||
SourceListItem(
|
||||
appRepository.updateSourcePositions(widget.column.id, start, current);
|
||||
},
|
||||
proxyDecorator: (Widget child, int index, Animation<double> animation) {
|
||||
return _proxyDecorator(child, index, animation);
|
||||
},
|
||||
itemCount: widget.column.sources.length,
|
||||
itemBuilder: (context, index) {
|
||||
return SourceListItem(
|
||||
key: Key(widget.column.sources[index].id),
|
||||
columnId: widget.column.id,
|
||||
source: widget.column.sources[i],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return columns;
|
||||
sourceIndex: index,
|
||||
source: widget.column.sources[index],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// [_showAddSource] shows the [AddSource] widget within a modal bottom sheet
|
||||
@@ -92,16 +130,16 @@ class _ColumnLayoutHeaderSettingsSourcesState
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 275,
|
||||
),
|
||||
constraints: const BoxConstraints(maxHeight: 275),
|
||||
child: ListView(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
..._buildSourcesList(),
|
||||
_buildSourcesList(),
|
||||
ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Constants.secondary,
|
||||
foregroundColor: Constants.onSecondary,
|
||||
maximumSize: const Size.fromHeight(
|
||||
Constants.elevatedButtonSize,
|
||||
),
|
||||
@@ -114,9 +152,7 @@ class _ColumnLayoutHeaderSettingsSourcesState
|
||||
),
|
||||
label: const Text('Add Source'),
|
||||
onPressed: () => _showAddSource(),
|
||||
icon: const Icon(
|
||||
Icons.add,
|
||||
),
|
||||
icon: const Icon(Icons.add),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -101,7 +101,7 @@ class _ColumnLayoutSearchState extends State<ColumnLayoutSearch> {
|
||||
width: double.infinity,
|
||||
height: _showFilters,
|
||||
decoration: const BoxDecoration(
|
||||
color: Constants.backgroundContainerBackgroundColor,
|
||||
color: Constants.surfaceContainerBackgroundColor,
|
||||
),
|
||||
padding: const EdgeInsets.all(Constants.spacingMiddle),
|
||||
child: Column(
|
||||
|
||||
@@ -43,6 +43,8 @@ class _ConfirmationState extends State<Confirmation> {
|
||||
case 'change-email-address':
|
||||
return ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Constants.secondary,
|
||||
foregroundColor: Constants.onSecondary,
|
||||
maximumSize: const Size.fromHeight(
|
||||
Constants.elevatedButtonSize,
|
||||
),
|
||||
@@ -57,6 +59,8 @@ class _ConfirmationState extends State<Confirmation> {
|
||||
case 'confirm-signup':
|
||||
return ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Constants.secondary,
|
||||
foregroundColor: Constants.onSecondary,
|
||||
maximumSize: const Size.fromHeight(
|
||||
Constants.elevatedButtonSize,
|
||||
),
|
||||
@@ -71,6 +75,8 @@ class _ConfirmationState extends State<Confirmation> {
|
||||
case 'reset-password':
|
||||
return ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Constants.secondary,
|
||||
foregroundColor: Constants.onSecondary,
|
||||
maximumSize: const Size.fromHeight(
|
||||
Constants.elevatedButtonSize,
|
||||
),
|
||||
|
||||
@@ -179,6 +179,10 @@ class _CreateDeckState extends State<CreateDeck> {
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Name',
|
||||
hintText: 'e.g. News',
|
||||
hintStyle: TextStyle(
|
||||
color: Constants.secondaryTextColor,
|
||||
),
|
||||
),
|
||||
validator: (value) => _validateDeckName(value),
|
||||
onFieldSubmitted: (value) => _createDeck(),
|
||||
@@ -189,6 +193,8 @@ class _CreateDeckState extends State<CreateDeck> {
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Constants.secondary,
|
||||
foregroundColor: Constants.onSecondary,
|
||||
maximumSize: const Size.fromHeight(
|
||||
Constants.elevatedButtonSize,
|
||||
),
|
||||
|
||||
@@ -110,7 +110,7 @@ class _SelectDeckState extends State<SelectDeck> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: Constants.background,
|
||||
color: Constants.surface,
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
body: Center(
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:scroll_to_index/scroll_to_index.dart';
|
||||
|
||||
import 'package:feeddeck/models/source.dart';
|
||||
import 'package:feeddeck/repositories/app_repository.dart';
|
||||
import 'package:feeddeck/repositories/layout_repository.dart';
|
||||
import 'package:feeddeck/utils/constants.dart';
|
||||
import 'package:feeddeck/widgets/column/column_layout.dart';
|
||||
import 'package:feeddeck/widgets/column/create/create_column.dart';
|
||||
@@ -29,7 +30,7 @@ class _DeckLayoutLargeState extends State<DeckLayoutLarge> {
|
||||
|
||||
/// [_openDrawer] opens the provided [widget] in the drawer of the scaffold,
|
||||
/// by setting the [_drawer] state first and then opening the drawer.
|
||||
_openDrawer(Widget widget) {
|
||||
void _openDrawer(Widget widget) {
|
||||
setState(() {
|
||||
_drawer = widget;
|
||||
});
|
||||
@@ -62,20 +63,12 @@ class _DeckLayoutLargeState extends State<DeckLayoutLarge> {
|
||||
size: 32,
|
||||
),
|
||||
label: Container(
|
||||
padding: const EdgeInsets.only(
|
||||
top: Constants.spacingExtraSmall,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 54,
|
||||
maxWidth: 54,
|
||||
),
|
||||
padding: const EdgeInsets.only(top: Constants.spacingExtraSmall),
|
||||
constraints: const BoxConstraints(minWidth: 54, maxWidth: 54),
|
||||
child: Text(
|
||||
Characters(column.name)
|
||||
.replaceAll(
|
||||
Characters(''),
|
||||
Characters('\u{200B}'),
|
||||
)
|
||||
.toString(),
|
||||
Characters(
|
||||
column.name,
|
||||
).replaceAll(Characters(''), Characters('\u{200B}')).toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
@@ -90,10 +83,7 @@ class _DeckLayoutLargeState extends State<DeckLayoutLarge> {
|
||||
for (var i = widgets.length; i < 2; i++) {
|
||||
widgets.add(
|
||||
const NavigationRailDestination(
|
||||
icon: Icon(
|
||||
Icons.circle,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
icon: Icon(Icons.circle, color: Colors.transparent),
|
||||
label: Text(''),
|
||||
),
|
||||
);
|
||||
@@ -118,20 +108,13 @@ class _DeckLayoutLargeState extends State<DeckLayoutLarge> {
|
||||
child: RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: const TextSpan(
|
||||
style: TextStyle(
|
||||
color: Constants.onSurface,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
style: TextStyle(color: Constants.onSurface, fontSize: 14.0),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'Add you first column by clicking on the plus icon (',
|
||||
),
|
||||
WidgetSpan(
|
||||
child: Icon(Icons.add, size: 14.0),
|
||||
),
|
||||
TextSpan(
|
||||
text: ') in the sidebar on the left side.',
|
||||
),
|
||||
WidgetSpan(child: Icon(Icons.add, size: 14.0)),
|
||||
TextSpan(text: ') in the sidebar on the left side.'),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -195,14 +178,11 @@ class _DeckLayoutLargeState extends State<DeckLayoutLarge> {
|
||||
/// The drawer is used to display the [CreateColumn] widget, so that a
|
||||
/// user can add a new column without leaving the screen.
|
||||
drawer: ClipRRect(
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(Constants.spacingMiddle),
|
||||
bottom: Radius.circular(Constants.spacingMiddle),
|
||||
),
|
||||
child: Drawer(
|
||||
width: Constants.columnWidth,
|
||||
child: _drawer,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(Constants.spacingMiddle),
|
||||
bottomRight: Radius.circular(Constants.spacingMiddle),
|
||||
),
|
||||
child: Drawer(width: Constants.columnWidth, child: _drawer),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Row(
|
||||
@@ -210,7 +190,8 @@ class _DeckLayoutLargeState extends State<DeckLayoutLarge> {
|
||||
SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: MediaQuery.of(context).size.height -
|
||||
minHeight:
|
||||
MediaQuery.of(context).size.height -
|
||||
MediaQuery.of(context).padding.top -
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
@@ -221,13 +202,23 @@ class _DeckLayoutLargeState extends State<DeckLayoutLarge> {
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
),
|
||||
child: NavigationRail(
|
||||
backgroundColor: Constants.background,
|
||||
backgroundColor: Constants.surface,
|
||||
selectedIndex: null,
|
||||
|
||||
/// When a user selects a destination in the navigation rail
|
||||
/// we scroll to the corresponding column by using the
|
||||
/// `scroll_to_index` package.
|
||||
/// When a user selects a destination in the navigation
|
||||
/// rail we scroll to the corresponding column by using
|
||||
/// the `scroll_to_index` package.
|
||||
onDestinationSelected: (int index) {
|
||||
/// Before we scroll to the corresponding column, we
|
||||
/// also update the [deckLayoutSmallInitialTabIndex] in
|
||||
/// the [LayoutRepository] so that the correct tab is
|
||||
/// also selected when a user switches to the small
|
||||
/// layout.
|
||||
Provider.of<LayoutRepository>(
|
||||
context,
|
||||
listen: false,
|
||||
).deckLayoutSmallInitialTabIndex = index;
|
||||
|
||||
_scrollController.scrollToIndex(
|
||||
index,
|
||||
preferPosition: AutoScrollPosition.end,
|
||||
@@ -244,8 +235,8 @@ class _DeckLayoutLargeState extends State<DeckLayoutLarge> {
|
||||
|
||||
/// We add two additional items to the navigation rail via
|
||||
/// the trailing property. These items are used to allow a
|
||||
/// user to create a new column and to go to the settings of
|
||||
/// the app.
|
||||
/// user to create a new column and to go to the settings
|
||||
/// of the app.
|
||||
trailing: Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
@@ -270,9 +261,7 @@ class _DeckLayoutLargeState extends State<DeckLayoutLarge> {
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: Constants.spacingMiddle,
|
||||
),
|
||||
const SizedBox(height: Constants.spacingMiddle),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||