Compare commits
656 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
832b363da7 | ||
|
|
cf8f1ecd67 | ||
|
|
ee5129830c | ||
|
|
9facfaffa8 | ||
|
|
31b859ba1a | ||
|
|
b0f5a24c30 | ||
|
|
b60b594e44 | ||
|
|
a8cee1016b | ||
|
|
b551af01fa | ||
|
|
b178869a8e | ||
|
|
8e1c60d5f0 | ||
|
|
71ea49439e | ||
|
|
9d0a2f6535 | ||
|
|
c091fffe90 | ||
|
|
7e05d05f94 | ||
|
|
bd5b51c62f | ||
|
|
1d5eb2eced | ||
|
|
5073ead8fe | ||
|
|
f61bcb621b | ||
|
|
42b695854b | ||
|
|
496ae8b1b2 | ||
|
|
4215a2592f | ||
|
|
bca62033a1 | ||
|
|
18a91f9223 | ||
|
|
474d7c7a26 | ||
|
|
3f9a51ff46 | ||
|
|
df3b5c41f9 | ||
|
|
8800d11ab0 | ||
|
|
f021834ceb | ||
|
|
f54a39a549 | ||
|
|
c312ff3faf | ||
|
|
db833594f4 | ||
|
|
d36cf59829 | ||
|
|
caae751698 | ||
|
|
f7836eedce | ||
|
|
3ddc1a6229 | ||
|
|
cf9869b71e | ||
|
|
d31d8f574e | ||
|
|
c1336428dc | ||
|
|
f0e513bf0d | ||
|
|
ee9e41c5a7 | ||
|
|
30f1257983 | ||
|
|
f007cef208 | ||
|
|
19b5852098 | ||
|
|
2ec05ea7cc | ||
|
|
e8dd8bff44 | ||
|
|
ab3f65edfa | ||
|
|
fcaf62ecf7 | ||
|
|
7cfa5a57f5 | ||
|
|
56d6c22d53 | ||
|
|
10b0b1e96e | ||
|
|
6ef1e9c86c | ||
|
|
a0d0f2651b | ||
|
|
b6edbad744 | ||
|
|
05748e23b1 | ||
|
|
12d87be106 | ||
|
|
dfb83cc1f7 | ||
|
|
1840ae80c2 | ||
|
|
2e9d1d83a4 | ||
|
|
1ef83a3089 | ||
|
|
b95c06a5dc | ||
|
|
8f789841a6 | ||
|
|
0f57a4dfd7 | ||
|
|
125c277c88 | ||
|
|
a7674d436d | ||
|
|
48ba4cdf42 | ||
|
|
8a9265eb79 | ||
|
|
215dc6ea6d | ||
|
|
428dcb2348 | ||
|
|
1c79d14260 | ||
|
|
a82b487b92 | ||
|
|
068369fca2 | ||
|
|
d524143a53 | ||
|
|
05ebfa68c9 | ||
|
|
57d8fd7e10 | ||
|
|
64c556a337 | ||
|
|
de268d1168 | ||
|
|
8fd09bcad4 | ||
|
|
8cc7d46d7b | ||
|
|
bb56815210 | ||
|
|
332f2ab89f | ||
|
|
d3780244cd | ||
|
|
f7bfbd5080 | ||
|
|
eef2ee1baa | ||
|
|
8d8d7d0f8c | ||
|
|
979a3d8f1f | ||
|
|
95b8f54b27 | ||
|
|
5f1d411b28 | ||
|
|
6e3fcf363a | ||
|
|
8e129c561d | ||
|
|
fbc3ca4517 | ||
|
|
1668dfcce6 | ||
|
|
da2a58a88b | ||
|
|
8390a868ba | ||
|
|
296c498464 | ||
|
|
a92ff89221 | ||
|
|
e3b2f41a1c | ||
|
|
9b3424fa90 | ||
|
|
678d947f16 | ||
|
|
0cd1568c64 | ||
|
|
4e25dbf5e3 | ||
|
|
14e10c038c | ||
|
|
2bc5be2105 | ||
|
|
36b8de470a | ||
|
|
9abe3dd7b3 | ||
|
|
96c98a6987 | ||
|
|
e586145f50 | ||
|
|
611fbfa6df | ||
|
|
54eb4f78b2 | ||
|
|
1ce7bb8c02 | ||
|
|
ef17e86465 | ||
|
|
cd3e0492f5 | ||
|
|
c3f98e327c | ||
|
|
20fc34a6fd | ||
|
|
87af67c36c | ||
|
|
f33335a768 | ||
|
|
a9fffd44d2 | ||
|
|
e81b7e2e61 | ||
|
|
7fad15524f | ||
|
|
8367efa174 | ||
|
|
aa86b7efff | ||
|
|
b490450107 | ||
|
|
a4a7ff46c5 | ||
|
|
418eeb7317 | ||
|
|
5b0fcd77e5 | ||
|
|
757a73ca6b | ||
|
|
faab29cab7 | ||
|
|
d1d6f12f05 | ||
|
|
a979e8430f | ||
|
|
f5f045e8f1 | ||
|
|
90e291ac8a | ||
|
|
b49e1b3a97 | ||
|
|
5245feb739 | ||
|
|
550c8fb899 | ||
|
|
89ffc5d246 | ||
|
|
3330873ce0 | ||
|
|
ff998a8bd1 | ||
|
|
a1517f82bc | ||
|
|
d8885b089a | ||
|
|
beba06b8ba | ||
|
|
762be492bb | ||
|
|
c00efff554 | ||
|
|
5258ae4b34 | ||
|
|
a8b19e73fe | ||
|
|
92944c7adc | ||
|
|
0042173ddc | ||
|
|
a438b5ba17 | ||
|
|
b86643c052 | ||
|
|
e897182b23 | ||
|
|
56329f02b6 | ||
|
|
11ab579de9 | ||
|
|
9aa1de11af | ||
|
|
939b395dcd | ||
|
|
118e9bf50b | ||
|
|
67828365ca | ||
|
|
e0d99257fe | ||
|
|
e628a98cb8 | ||
|
|
a4f9284970 | ||
|
|
c403a716dd | ||
|
|
4278c8bacb | ||
|
|
56bfbeca58 | ||
|
|
b6f9eae7b5 | ||
|
|
cad9250cb7 | ||
|
|
4ac8095554 | ||
|
|
05d16983eb | ||
|
|
12f821fd6b | ||
|
|
13c8bf5f19 | ||
|
|
9a44e30e0f | ||
|
|
bf77572041 | ||
|
|
18ba4851d1 | ||
|
|
6620034d98 | ||
|
|
e93ecc7e4a | ||
|
|
f85085de41 | ||
|
|
310943590b | ||
|
|
c1dcd52a28 | ||
|
|
9bee5e449f | ||
|
|
02e99066ca | ||
|
|
7761cf6dbe | ||
|
|
7715d4bdda | ||
|
|
f3c4906f88 | ||
|
|
3dc16fa07b | ||
|
|
be58bdf24f | ||
|
|
ac170c67bf | ||
|
|
169cd9d743 | ||
|
|
a1931f08a8 | ||
|
|
5617206c6d | ||
|
|
cee00b8b2b | ||
|
|
323a8acbd5 | ||
|
|
d488b10848 | ||
|
|
24c99364ef | ||
|
|
f8b6975e70 | ||
|
|
c416e8c1fb | ||
|
|
51626dfd04 | ||
|
|
cb05e58f1e | ||
|
|
24c8cfd402 | ||
|
|
8257b0b582 | ||
|
|
e612f2c281 | ||
|
|
155df59b1d | ||
|
|
6efab9f386 | ||
|
|
422105a507 | ||
|
|
d58438ab1d | ||
|
|
209aa1a89f | ||
|
|
07ac81583e | ||
|
|
ee6249bb17 | ||
|
|
b8ca3f8604 | ||
|
|
85b5469d96 | ||
|
|
d3ddf7b191 | ||
|
|
ebf196ec73 | ||
|
|
85c3833452 | ||
|
|
8fb4735fc2 | ||
|
|
6bb09f10d2 | ||
|
|
a4ff67e8f4 | ||
|
|
b9a1756f90 | ||
|
|
72ff8e67ed | ||
|
|
d7006365eb | ||
|
|
86ef78b296 | ||
|
|
0d84e409c5 | ||
|
|
17c7b33167 | ||
|
|
856e1613a0 | ||
|
|
8db34289a7 | ||
|
|
3016f70349 | ||
|
|
bdf212fdb3 | ||
|
|
c6216f54f8 | ||
|
|
945c747e3e | ||
|
|
552fca8df7 | ||
|
|
4dac160619 | ||
|
|
fc0e746e1b | ||
|
|
8374b5fc0c | ||
|
|
52923ed35b | ||
|
|
f002ccfa3a | ||
|
|
b64cc44531 | ||
|
|
1b51982551 | ||
|
|
f4bbf6eb73 | ||
|
|
10c94efa57 | ||
|
|
c84316e974 | ||
|
|
7e4d1fbf49 | ||
|
|
31c7c8b46f | ||
|
|
8cdb6646c4 | ||
|
|
43a51e5f2f | ||
|
|
d0b54ea44b | ||
|
|
c8fce82dc1 | ||
|
|
3ffccaa7e5 | ||
|
|
3f7e7a1474 | ||
|
|
d30c15464b | ||
|
|
2a1f748469 | ||
|
|
74b429b1bf | ||
|
|
7afe1b517c | ||
|
|
fcdcf5692f | ||
|
|
735baa67ed | ||
|
|
ac00caf83e | ||
|
|
2ccda353e9 | ||
|
|
4257ca54b7 | ||
|
|
20dd2ef98d | ||
|
|
8be52c9ddd | ||
|
|
3bea6619fa | ||
|
|
0e5fb11c52 | ||
|
|
fc47f7701d | ||
|
|
3e4ccf8de4 | ||
|
|
8abb3c709b | ||
|
|
5263900620 | ||
|
|
54ee9e5072 | ||
|
|
20f4857405 | ||
|
|
55110dd39d | ||
|
|
07d8111df9 | ||
|
|
a701b97f1e | ||
|
|
b025537d62 | ||
|
|
e68bc2de65 | ||
|
|
d0b1c93382 | ||
|
|
a08fa9eabf | ||
|
|
65ef130bda | ||
|
|
32471a71e5 | ||
|
|
ca80f01907 | ||
|
|
b1fae57922 | ||
|
|
dea01dda2d | ||
|
|
9777619259 | ||
|
|
6db0f84f8f | ||
|
|
84e382fcc5 | ||
|
|
eceed1b934 | ||
|
|
e7b5cd74a4 | ||
|
|
a98930791c | ||
|
|
67b0389097 | ||
|
|
ff30595fb4 | ||
|
|
d272b62c45 | ||
|
|
c1973023d0 | ||
|
|
ef4689619e | ||
|
|
9ed297a49d | ||
|
|
9950729080 | ||
|
|
36eec9d061 | ||
|
|
fd3a977009 | ||
|
|
ff88832cca | ||
|
|
d69b72b77d | ||
|
|
d9bd9e996b | ||
|
|
f45580be70 | ||
|
|
0fbeb11304 | ||
|
|
6a70c13b57 | ||
|
|
f797520e56 | ||
|
|
d060db3c96 | ||
|
|
298344c2ab | ||
|
|
53dbeadabb | ||
|
|
93087d7aa7 | ||
|
|
c304517fc2 | ||
|
|
af13e034c3 | ||
|
|
e6b9d59160 | ||
|
|
bd9cbe30c5 | ||
|
|
f627226da9 | ||
|
|
bab3272970 | ||
|
|
003085db7e | ||
|
|
8f98bd37c7 | ||
|
|
6971ef1ede | ||
|
|
0baac3dc49 | ||
|
|
16feb8602d | ||
|
|
d623129d56 | ||
|
|
9cc4c33bb1 | ||
|
|
c087b40d4a | ||
|
|
a82e074bd4 | ||
|
|
3365c099bb | ||
|
|
d8705de26f | ||
|
|
3af181e736 | ||
|
|
ba6c01b7c6 | ||
|
|
7a7cdcb041 | ||
|
|
09e52bf199 | ||
|
|
a8c39a1587 | ||
|
|
68536d6c34 | ||
|
|
f57c27e755 | ||
|
|
9a97a27c8a | ||
|
|
07bb90dda9 | ||
|
|
3bb156f4da | ||
|
|
e13e0efcc6 | ||
|
|
3ae0a94159 | ||
|
|
eec67ec7dc | ||
|
|
cf51853eec | ||
|
|
67838b28a4 | ||
|
|
bf68e3b7d5 | ||
|
|
91ed0bb8bd | ||
|
|
55fe791c2a | ||
|
|
747efac2ec | ||
|
|
a87df2e9f6 | ||
|
|
2e4a664744 | ||
|
|
579a22ea45 | ||
|
|
abff850427 | ||
|
|
f1154058ba | ||
|
|
cf9f308b7f | ||
|
|
1791df236c | ||
|
|
7211f94f08 | ||
|
|
7b0343c87f | ||
|
|
b80f0276b4 | ||
|
|
8f66fcbb00 | ||
|
|
fe449ee1f3 | ||
|
|
34d6d95186 | ||
|
|
05ddc0660a | ||
|
|
c6047a8428 | ||
|
|
bc4838578e | ||
|
|
548996405a | ||
|
|
a9a5f0bd14 | ||
|
|
ec05ff6123 | ||
|
|
10c7ab421b | ||
|
|
a8a5cc53ea | ||
|
|
8fe48ca438 | ||
|
|
cbd5a515a9 | ||
|
|
5912adff93 | ||
|
|
983bc21448 | ||
|
|
6d08368462 | ||
|
|
cde7bb3524 | ||
|
|
133dc91561 | ||
|
|
f408f59beb | ||
|
|
8763bf11ab | ||
|
|
99592a52be | ||
|
|
25a8a52573 | ||
|
|
5901344459 | ||
|
|
b07ae4bc42 | ||
|
|
0d9e61480a | ||
|
|
baaea96b1d | ||
|
|
e156cb5c2e | ||
|
|
ff77b2b3dc | ||
|
|
4dc225184d | ||
|
|
49a0266224 | ||
|
|
7a62d96b91 | ||
|
|
641454fa14 | ||
|
|
cf2f0ec936 | ||
|
|
76375c9471 | ||
|
|
0894c56c19 | ||
|
|
7de89ffe57 | ||
|
|
e0c01343a8 | ||
|
|
48ed4f971c | ||
|
|
9e8c8a019e | ||
|
|
96f4ca2cc9 | ||
|
|
fad59604f9 | ||
|
|
ac0e8da0ba | ||
|
|
fc5c3fd73e | ||
|
|
536fbb23a1 | ||
|
|
ac01006398 | ||
|
|
ade8654cc4 | ||
|
|
8e03f26fb5 | ||
|
|
dab51bef7b | ||
|
|
bea7fb5fe5 | ||
|
|
d6b3240506 | ||
|
|
19e53d8bc8 | ||
|
|
5923d72803 | ||
|
|
8cde976358 | ||
|
|
256a5b50ac | ||
|
|
8b236f68f4 | ||
|
|
c18f6d3978 | ||
|
|
e65733754c | ||
|
|
4505630896 | ||
|
|
044804143f | ||
|
|
1e9d7c9b93 | ||
|
|
573fe3cad3 | ||
|
|
cd3880c85c | ||
|
|
02cd8ed7ae | ||
|
|
3d69f96b96 | ||
|
|
963a15abe7 | ||
|
|
40542f0461 | ||
|
|
bfc2af71a4 | ||
|
|
35142bb61d | ||
|
|
39626e0df9 | ||
|
|
ca40730600 | ||
|
|
4158e196d6 | ||
|
|
1516e800dd | ||
|
|
0b96f69a1d | ||
|
|
bb418e51d4 | ||
|
|
5fb23ab8bf | ||
|
|
117b01c48b | ||
|
|
18719815a3 | ||
|
|
f795595107 | ||
|
|
be3f9465eb | ||
|
|
c598daab9b | ||
|
|
438b25672f | ||
|
|
1e12ddd8e2 | ||
|
|
dccf55d57a | ||
|
|
3397845ccc | ||
|
|
82ae13ba3e | ||
|
|
8c830761f3 | ||
|
|
ad5c134887 | ||
|
|
150bf124a9 | ||
|
|
1c96c0ccbf | ||
|
|
cf8ad3d697 | ||
|
|
5b723ec954 | ||
|
|
633c708c33 | ||
|
|
e4784108f7 | ||
|
|
b4103a4edb | ||
|
|
cfdd32708a | ||
|
|
8e97c1e9e8 | ||
|
|
e009bebfaf | ||
|
|
d3d190989d | ||
|
|
1b36b52019 | ||
|
|
e526e96e2d | ||
|
|
1659d74938 | ||
|
|
f7161dc39b | ||
|
|
d8ea41e4dd | ||
|
|
fe0a03db2f | ||
|
|
49afed7751 | ||
|
|
a788b1706b | ||
|
|
d57fd20ca2 | ||
|
|
5ecc1b09c8 | ||
|
|
f1dfc9666b | ||
|
|
b503584431 | ||
|
|
0dfe1c8e3a | ||
|
|
c5630d90ec | ||
|
|
f8973a3cf7 | ||
|
|
228be8376f | ||
|
|
3fee3b0224 | ||
|
|
b4e705b347 | ||
|
|
51e59bebfc | ||
|
|
a56ac31f4b | ||
|
|
af05f081d2 | ||
|
|
b97d79d261 | ||
|
|
d227e6339f | ||
|
|
74c503fea0 | ||
|
|
ed8b0c4d3c | ||
|
|
cbb17e92f0 | ||
|
|
f9d64cf777 | ||
|
|
7fdd6d19cf | ||
|
|
60ca252b63 | ||
|
|
2027837c18 | ||
|
|
67904568b1 | ||
|
|
afbc559a47 | ||
|
|
67c312642c | ||
|
|
89eb007e43 | ||
|
|
88c3f3204b | ||
|
|
052ab98cd3 | ||
|
|
cd01c0e31b | ||
|
|
c3d3228f65 | ||
|
|
f04647192f | ||
|
|
c00f0c9f52 | ||
|
|
690e04c377 | ||
|
|
545880dcce | ||
|
|
fedf5d2203 | ||
|
|
5242e55826 | ||
|
|
1bf86e73a7 | ||
|
|
c3fe72f561 | ||
|
|
b54555a4f7 | ||
|
|
4c080360a7 | ||
|
|
fdea15eef4 | ||
|
|
5a4dd97abe | ||
|
|
e47e3d936b | ||
|
|
0e9fd4373e | ||
|
|
69a10c754a | ||
|
|
a8ba965c0d | ||
|
|
847aed3519 | ||
|
|
fed9345bea | ||
|
|
d71b6d0ab0 | ||
|
|
40e5521fbf | ||
|
|
c7781d2e75 | ||
|
|
0ccf67000c | ||
|
|
f4e81aca9d | ||
|
|
2d654cf759 | ||
|
|
0b728162ae | ||
|
|
f52b426652 | ||
|
|
ad0528d763 | ||
|
|
263adcf2b5 | ||
|
|
b201e274f1 | ||
|
|
c1a6a73b7d | ||
|
|
ed4764cc70 | ||
|
|
8d84b3adbc | ||
|
|
35db674c88 | ||
|
|
3e1850a713 | ||
|
|
8c625c0ecd | ||
|
|
bf1de8e7a4 | ||
|
|
8c2dca3770 | ||
|
|
23adbddfb7 | ||
|
|
b1b6b64e68 | ||
|
|
0c7909df09 | ||
|
|
641bfd8c06 | ||
|
|
52140c3208 | ||
|
|
1baa9173cb | ||
|
|
26749c2116 | ||
|
|
a1ecb9ad39 | ||
|
|
b398fde24e | ||
|
|
a5767b60fb | ||
|
|
ef196c384c | ||
|
|
920def7446 | ||
|
|
bec98dcaa2 | ||
|
|
747be07322 | ||
|
|
c1fc6f0bce | ||
|
|
851c42e630 | ||
|
|
4e5a4a9c17 | ||
|
|
df19d500de | ||
|
|
4fd4c9802e | ||
|
|
23d0eeab16 | ||
|
|
b66557f3be | ||
|
|
580586cab3 | ||
|
|
9a97ef1dad | ||
|
|
b4f7503d85 | ||
|
|
b89406a89a | ||
|
|
932cdae22e | ||
|
|
34ef86cfff | ||
|
|
023bace2a9 | ||
|
|
467b63cffb | ||
|
|
49ffb0fa68 | ||
|
|
04984dea98 | ||
|
|
78e9a768b4 | ||
|
|
401f877388 | ||
|
|
b3ba1aa3b9 | ||
|
|
ae76aebe24 | ||
|
|
b5e1bf2867 | ||
|
|
fb3a0da54f | ||
|
|
7d197f405d | ||
|
|
bc7af3a68b | ||
|
|
120e79229a | ||
|
|
f0e4bbcda9 | ||
|
|
57e31c1dfb | ||
|
|
8f2f414f5e | ||
|
|
879f91693e | ||
|
|
0c504b52e4 | ||
|
|
bbaa5f9fa8 | ||
|
|
2a9f7fde28 | ||
|
|
0ec7c87851 | ||
|
|
cf515abc4c | ||
|
|
99cab08ab7 | ||
|
|
e60d40ee9c | ||
|
|
c90fa68f16 | ||
|
|
61ae86e927 | ||
|
|
86c995906c | ||
|
|
a77b88aaf6 | ||
|
|
0d288f3206 | ||
|
|
d9d6244931 | ||
|
|
4d984f0524 | ||
|
|
b456b0143b | ||
|
|
3ebcfd5598 | ||
|
|
745ea3509e | ||
|
|
8f5d4e5bc2 | ||
|
|
acd7d3bbac | ||
|
|
f3bd6598e7 | ||
|
|
ce48fdc445 | ||
|
|
5aea223c14 | ||
|
|
1f59d3179c | ||
|
|
1f6e1db695 | ||
|
|
5b46c372ab | ||
|
|
f143d3e1c2 | ||
|
|
c7639309ef | ||
|
|
b788531e47 | ||
|
|
00fa2ca804 | ||
|
|
74cdf09ebc | ||
|
|
80ec12740a | ||
|
|
a0002ff97b | ||
|
|
2cb0db02e3 | ||
|
|
bed8d71f7e | ||
|
|
668b385a10 | ||
|
|
d96df469a4 | ||
|
|
8edbe1aa2c | ||
|
|
99ece9a61a | ||
|
|
4b5253dd6b | ||
|
|
5e10294a16 | ||
|
|
226d2069d9 | ||
|
|
8d1365b712 | ||
|
|
45624d905e | ||
|
|
a5d9c931ca | ||
|
|
f0db2b2f6e | ||
|
|
cde5913ace | ||
|
|
ceea9fc501 | ||
|
|
eb678b4533 | ||
|
|
dcbe8bb3dc | ||
|
|
555310a4ca | ||
|
|
3a57358f58 | ||
|
|
9982ba2fec | ||
|
|
11b652f851 | ||
|
|
380950a615 | ||
|
|
e8b07738a5 | ||
|
|
9d1163e73e | ||
|
|
e2f0c73bab | ||
|
|
183873afff | ||
|
|
b356475741 | ||
|
|
279bae115a | ||
|
|
82bb5a96ad | ||
|
|
a4e9ffcc99 | ||
|
|
1c25427c5c | ||
|
|
2ec22eb6cd | ||
|
|
2ff508607d | ||
|
|
69a34c65f7 | ||
|
|
f34c1520a4 | ||
|
|
f3fa8eec50 | ||
|
|
9baa9b1dd6 | ||
|
|
c1c1af1ded | ||
|
|
4a8c1b7de4 | ||
|
|
1f35165919 | ||
|
|
2e85508426 | ||
|
|
631b218057 | ||
|
|
e8cfc88d83 | ||
|
|
78502a0cd0 | ||
|
|
2bcb6a146b | ||
|
|
ae474b5e5b | ||
|
|
babed18af0 | ||
|
|
3fa7e3e36d | ||
|
|
45587d5f15 | ||
|
|
e98f6997ca | ||
|
|
21d1b71653 | ||
|
|
afd7b5650f | ||
|
|
a0687d555a | ||
|
|
f543953389 | ||
|
|
2c4c7586b7 | ||
|
|
e92921a6cc | ||
|
|
0322bcd047 | ||
|
|
26c914be96 | ||
|
|
75863faf58 | ||
|
|
99b3ec4ce3 | ||
|
|
d97ffd863b |
@@ -149,7 +149,7 @@ csharp_preferred_modifier_order = public,private,protected,internal,static,exter
|
||||
# Code-block preferences
|
||||
csharp_prefer_braces = true:silent
|
||||
csharp_prefer_simple_using_statement = true:suggestion
|
||||
csharp_style_namespace_declarations = block_scoped:silent
|
||||
csharp_style_namespace_declarations = file_scoped:error
|
||||
|
||||
# Expression-level preferences
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
@@ -287,4 +287,6 @@ csharp_style_prefer_top_level_statements = true:silent
|
||||
csharp_style_prefer_utf8_string_literals = true:suggestion
|
||||
csharp_style_prefer_readonly_struct = true:suggestion
|
||||
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent
|
||||
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent
|
||||
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent
|
||||
csharp_style_prefer_primary_constructors = true:silent
|
||||
csharp_prefer_system_threading_lock = true:suggestion
|
||||
3
.gitignore
vendored
@@ -206,9 +206,6 @@ PublishScripts/
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Nuget personal access tokens and Credentials
|
||||
nuget.config
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
@@ -15,7 +15,12 @@ Wino Mail is [Universal Windows Platform](https://learn.microsoft.com/en-us/wind
|
||||
**Min Version:** Windows 10 1809
|
||||
**Target Version:** Windows 11 22H2
|
||||
|
||||
It's pretty straightforward after cloning the repo. There are no prerequisites needed. Just open **Wino.sln** solution in your IDE and launch.
|
||||
## Prerequisites
|
||||
|
||||
* ".NET desktop development" workload in Visual Studio 2022+
|
||||
* .NET SDK 8.0+
|
||||
|
||||
With those installed, it's pretty straightforward after cloning the repo. Just open **Wino.sln** solution in your IDE and launch.
|
||||
|
||||
## Project Architecture
|
||||
|
||||
|
||||
7
Directory.Build.Props
Normal file
@@ -0,0 +1,7 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
65
Directory.Packages.props
Normal file
@@ -0,0 +1,65 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="ColorHashSharp" Version="1.0.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Common" Version="8.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Diagnostics" Version="8.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Labs.Uwp.Controls.MarkdownTextBlock" Version="0.1.250206-build.2040" />
|
||||
<PackageVersion Include="CommunityToolkit.Labs.Uwp.DependencyPropertyGenerator" Version="0.1.250206-build.2040" />
|
||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Animations" Version="8.2.250129-preview2" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Behaviors" Version="8.2.250129-preview2" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Controls.Segmented" Version="8.2.250129-preview2" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Controls.SettingsControls" Version="8.2.250129-preview2" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Controls.Sizers" Version="8.2.250129-preview2" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Controls.TabbedCommandBar" Version="8.2.250129-preview2" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Controls.TokenizingTextBox" Version="8.2.250129-preview2" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Extensions" Version="8.2.250129-preview2" />
|
||||
<PackageVersion Include="EmailValidation" Version="1.2.0" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.11.72" />
|
||||
<PackageVersion Include="Ical.Net" Version="4.3.1" />
|
||||
<PackageVersion Include="IsExternalInit" Version="1.0.3" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Graph" Version="5.69.0" />
|
||||
<PackageVersion Include="Microsoft.Identity.Client" Version="4.68.0" />
|
||||
<PackageVersion Include="Microsoft.Identity.Client.Broker" Version="4.68.0" />
|
||||
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.68.0" />
|
||||
<PackageVersion Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.2.14" />
|
||||
<PackageVersion Include="Microsoft.UI.Xaml" Version="2.8.7" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.Uwp.Managed" Version="3.0.0" />
|
||||
<PackageVersion Include="MimeKit" Version="4.10.0" />
|
||||
<PackageVersion Include="morelinq" Version="4.4.0" />
|
||||
<PackageVersion Include="Nito.AsyncEx" Version="5.1.2" />
|
||||
<PackageVersion Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
|
||||
<PackageVersion Include="NodaTime" Version="3.2.1" />
|
||||
<PackageVersion Include="Serilog" Version="4.2.0" />
|
||||
<PackageVersion Include="Serilog.Exceptions" Version="8.4.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Debug" Version="3.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.ApplicationInsights" Version="4.0.0" />
|
||||
<PackageVersion Include="SkiaSharp" Version="3.116.1" />
|
||||
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
||||
<PackageVersion Include="SqlKata" Version="4.0.1" />
|
||||
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.2" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.2" />
|
||||
<PackageVersion Include="Win2D.uwp" Version="1.28.2" />
|
||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.2.0" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
|
||||
<PackageVersion Include="Google.Apis.Auth" Version="1.69.0" />
|
||||
<PackageVersion Include="Google.Apis.Calendar.v3" Version="1.69.0.3667" />
|
||||
<PackageVersion Include="Google.Apis.Gmail.v1" Version="1.68.0.3427" />
|
||||
<PackageVersion Include="Google.Apis.PeopleService.v1" Version="1.68.0.3359" />
|
||||
<PackageVersion Include="HtmlKit" Version="1.2.0" />
|
||||
<PackageVersion Include="MailKit" Version="4.10.0" />
|
||||
<PackageVersion Include="TimePeriodLibrary.NET" Version="2.1.5" />
|
||||
<PackageVersion Include="System.Reactive" Version="6.0.1" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.2" />
|
||||
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -25,6 +25,7 @@ I'm a big fan of Windows Mail & Calendars due to its simplicity. Personally, I f
|
||||
- API integration for Outlook and Gmail
|
||||
- IMAP/SMTP support for custom mail servers
|
||||
- Send, receive, mark as (read,important,spam etc), move mails.
|
||||
- Linked/Merged Accounts
|
||||
- Toast notifications with background sync.
|
||||
- Instant startup performance
|
||||
- Offline use / search.
|
||||
@@ -43,7 +44,7 @@ Download latest version of Wino Mail from Microsoft Store for free.
|
||||
|
||||
## Beta Releases
|
||||
|
||||
Stable releases will always be distributed on Microsoft Store. However, beta releases will be distributed in [GitHub Releases](https://github.com/bkaankose/Wino-Mail/releases). Please keep in mind that beta releases might not be for daily use, only for testing purposes and recommended for experienced users or developers.
|
||||
Stable releases will always be distributed on Microsoft Store. However, beta releases will be distributed in [GitHub Releases](https://github.com/bkaankose/Wino-Mail/releases). Please keep in mind that beta releases might not be for daily use, only for testing purposes and recommended for experienced users or developers. Beta releases are also managed manually. Therefore, code in the repository might be ahead of the released Beta version at the moment. Make sure to compare versions before tryout out the Beta version.
|
||||
|
||||
These releases are distributed as side-loaded packages. To install them, download the **.msixbundle** file in GitHub releases and [follow the steps explained here.](https://learn.microsoft.com/en-us/windows/application-management/sideload-apps-in-windows)
|
||||
|
||||
@@ -52,10 +53,6 @@ These releases are distributed as side-loaded packages. To install them, downloa
|
||||
|
||||
Check out the [contribution guidelines](/CONTRIBUTING.md) before diving into the source code or opening an issue. There are multiple ways to contribute and all of them are explained in detail there.
|
||||
|
||||
#### Attention
|
||||
|
||||
Sources here **does not belong to the Store version of Wino Mail. It belongs to beta release as of April 17 2024.** I've been working on a big patch for couple months already and the code here includes those changes, but these changes are not yet released to Microsoft Store. Therefore, if you'd like to contribute, please validate the bug before in beta version and start working on it. I will delete this text from here once this big patch goes alive in the Store, so everything will be aligned then.
|
||||
|
||||
## Donate
|
||||
|
||||
Your donations will motivate me more to work on Wino in my spare time and cover the expenses to keep [project's website](https://www.winomail.app/) alive.
|
||||
|
||||
42
Settings.XamlStyler
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"AttributesTolerance": 2,
|
||||
"KeepFirstAttributeOnSameLine": false,
|
||||
"MaxAttributeCharactersPerLine": 0,
|
||||
"MaxAttributesPerLine": 1,
|
||||
"NewlineExemptionElements": "RadialGradientBrush, GradientStop, LinearGradientBrush, ScaleTransform, SkewTransform, RotateTransform, TranslateTransform, Trigger, Condition, Setter",
|
||||
"SeparateByGroups": false,
|
||||
"AttributeIndentation": 0,
|
||||
"AttributeIndentationStyle": 1,
|
||||
"RemoveDesignTimeReferences": false,
|
||||
"IgnoreDesignTimeReferencePrefix": false,
|
||||
"EnableAttributeReordering": true,
|
||||
"AttributeOrderingRuleGroups": [
|
||||
"x:Class",
|
||||
"xmlns, xmlns:x",
|
||||
"xmlns:*",
|
||||
"x:Key, Key, x:Name, Name, x:Uid, Uid, Title",
|
||||
"Grid.Row, Grid.RowSpan, Grid.Column, Grid.ColumnSpan, Canvas.Left, Canvas.Top, Canvas.Right, Canvas.Bottom",
|
||||
"Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight",
|
||||
"Margin, Padding, HorizontalAlignment, VerticalAlignment, HorizontalContentAlignment, VerticalContentAlignment, Panel.ZIndex",
|
||||
"*:*, *",
|
||||
"PageSource, PageIndex, Offset, Color, TargetName, Property, Value, StartPoint, EndPoint",
|
||||
"mc:Ignorable, d:IsDataSource, d:LayoutOverrides, d:IsStaticText",
|
||||
"Storyboard.*, From, To, Duration"
|
||||
],
|
||||
"FirstLineAttributes": "",
|
||||
"OrderAttributesByName": true,
|
||||
"PutEndingBracketOnNewLine": false,
|
||||
"RemoveEndingTagOfEmptyElement": true,
|
||||
"SpaceBeforeClosingSlash": true,
|
||||
"RootElementLineBreakRule": 0,
|
||||
"ReorderVSM": 2,
|
||||
"ReorderGridChildren": false,
|
||||
"ReorderCanvasChildren": false,
|
||||
"ReorderSetters": 0,
|
||||
"FormatMarkupExtension": true,
|
||||
"NoNewLineMarkupExtensions": "x:Bind, Binding",
|
||||
"ThicknessSeparator": 2,
|
||||
"ThicknessAttributes": "Margin, Padding, BorderThickness, ThumbnailClipMargin",
|
||||
"FormatOnSave": true,
|
||||
"CommentPadding": 2,
|
||||
}
|
||||
16
Wino.Authentication/BaseAuthenticator.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Authentication;
|
||||
|
||||
public abstract class BaseAuthenticator
|
||||
{
|
||||
public abstract MailProviderType ProviderType { get; }
|
||||
protected IAuthenticatorConfig AuthenticatorConfig { get; }
|
||||
|
||||
protected BaseAuthenticator(IAuthenticatorConfig authenticatorConfig)
|
||||
{
|
||||
|
||||
AuthenticatorConfig = authenticatorConfig;
|
||||
}
|
||||
}
|
||||
50
Wino.Authentication/GmailAuthenticator.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Google.Apis.Auth.OAuth2;
|
||||
using Google.Apis.Util.Store;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Authentication;
|
||||
|
||||
namespace Wino.Authentication;
|
||||
|
||||
public class GmailAuthenticator : BaseAuthenticator, IGmailAuthenticator
|
||||
{
|
||||
public GmailAuthenticator(IAuthenticatorConfig authConfig) : base(authConfig)
|
||||
{
|
||||
}
|
||||
|
||||
public string ClientId => AuthenticatorConfig.GmailAuthenticatorClientId;
|
||||
public bool ProposeCopyAuthURL { get; set; }
|
||||
|
||||
public override MailProviderType ProviderType => MailProviderType.Gmail;
|
||||
|
||||
/// <summary>
|
||||
/// Generates the token information for the given account.
|
||||
/// For gmail, interactivity is automatically handled when you get the token.
|
||||
/// </summary>
|
||||
/// <param name="account">Account to get token for.</param>
|
||||
public Task<TokenInformationEx> GenerateTokenInformationAsync(MailAccount account)
|
||||
=> GetTokenInformationAsync(account);
|
||||
|
||||
public async Task<TokenInformationEx> GetTokenInformationAsync(MailAccount account)
|
||||
{
|
||||
var userCredential = await GetGoogleUserCredentialAsync(account);
|
||||
|
||||
if (userCredential.Token.IsStale)
|
||||
{
|
||||
await userCredential.RefreshTokenAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
return new TokenInformationEx(userCredential.Token.AccessToken, account.Address);
|
||||
}
|
||||
|
||||
private Task<UserCredential> GetGoogleUserCredentialAsync(MailAccount account)
|
||||
{
|
||||
return GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets()
|
||||
{
|
||||
ClientId = ClientId
|
||||
}, AuthenticatorConfig.GmailScope, account.Id.ToString(), CancellationToken.None, new FileDataStore(AuthenticatorConfig.GmailTokenStoreIdentifier));
|
||||
}
|
||||
}
|
||||
125
Wino.Authentication/OutlookAuthenticator.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Identity.Client;
|
||||
using Microsoft.Identity.Client.Broker;
|
||||
using Microsoft.Identity.Client.Extensions.Msal;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Authentication;
|
||||
|
||||
namespace Wino.Authentication;
|
||||
|
||||
public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator
|
||||
{
|
||||
private const string TokenCacheFileName = "OutlookCache.bin";
|
||||
private bool isTokenCacheAttached = false;
|
||||
|
||||
// Outlook
|
||||
private const string Authority = "https://login.microsoftonline.com/common";
|
||||
|
||||
public override MailProviderType ProviderType => MailProviderType.Outlook;
|
||||
|
||||
private readonly IPublicClientApplication _publicClientApplication;
|
||||
private readonly IApplicationConfiguration _applicationConfiguration;
|
||||
|
||||
public OutlookAuthenticator(INativeAppService nativeAppService,
|
||||
IApplicationConfiguration applicationConfiguration,
|
||||
IAuthenticatorConfig authenticatorConfig) : base(authenticatorConfig)
|
||||
{
|
||||
_applicationConfiguration = applicationConfiguration;
|
||||
|
||||
var authenticationRedirectUri = nativeAppService.GetWebAuthenticationBrokerUri();
|
||||
|
||||
var options = new BrokerOptions(BrokerOptions.OperatingSystems.Windows)
|
||||
{
|
||||
Title = "Wino Mail",
|
||||
ListOperatingSystemAccounts = true,
|
||||
};
|
||||
|
||||
var outlookAppBuilder = PublicClientApplicationBuilder.Create(AuthenticatorConfig.OutlookAuthenticatorClientId)
|
||||
.WithParentActivityOrWindow(nativeAppService.GetCoreWindowHwnd)
|
||||
.WithBroker(options)
|
||||
.WithDefaultRedirectUri()
|
||||
.WithAuthority(Authority);
|
||||
|
||||
_publicClientApplication = outlookAppBuilder.Build();
|
||||
}
|
||||
|
||||
public string[] Scope => AuthenticatorConfig.OutlookScope;
|
||||
|
||||
private async Task EnsureTokenCacheAttachedAsync()
|
||||
{
|
||||
if (!isTokenCacheAttached)
|
||||
{
|
||||
var storageProperties = new StorageCreationPropertiesBuilder(TokenCacheFileName, _applicationConfiguration.PublisherSharedFolderPath).Build();
|
||||
var msalcachehelper = await MsalCacheHelper.CreateAsync(storageProperties);
|
||||
msalcachehelper.RegisterCache(_publicClientApplication.UserTokenCache);
|
||||
|
||||
isTokenCacheAttached = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<TokenInformationEx> GetTokenInformationAsync(MailAccount account)
|
||||
{
|
||||
await EnsureTokenCacheAttachedAsync();
|
||||
|
||||
var storedAccount = (await _publicClientApplication.GetAccountsAsync()).FirstOrDefault(a => a.Username == account.Address);
|
||||
|
||||
if (storedAccount == null)
|
||||
return await GenerateTokenInformationAsync(account);
|
||||
|
||||
try
|
||||
{
|
||||
var authResult = await _publicClientApplication.AcquireTokenSilent(Scope, storedAccount).ExecuteAsync();
|
||||
|
||||
return new TokenInformationEx(authResult.AccessToken, authResult.Account.Username);
|
||||
}
|
||||
catch (MsalUiRequiredException)
|
||||
{
|
||||
// Somehow MSAL is not able to refresh the token silently.
|
||||
// Force interactive login.
|
||||
|
||||
return await GenerateTokenInformationAsync(account);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<TokenInformationEx> GenerateTokenInformationAsync(MailAccount account)
|
||||
{
|
||||
try
|
||||
{
|
||||
await EnsureTokenCacheAttachedAsync();
|
||||
|
||||
var authResult = await _publicClientApplication
|
||||
.AcquireTokenInteractive(Scope)
|
||||
.ExecuteAsync();
|
||||
|
||||
// If the account is null, it means it's the initial creation of it.
|
||||
// If not, make sure the authenticated user address matches the username.
|
||||
// When people refresh their token, accounts must match.
|
||||
|
||||
if (account?.Address != null && !account.Address.Equals(authResult.Account.Username, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new AuthenticationException("Authenticated address does not match with your account address.");
|
||||
}
|
||||
|
||||
return new TokenInformationEx(authResult.AccessToken, authResult.Account.Username);
|
||||
}
|
||||
catch (MsalClientException msalClientException)
|
||||
{
|
||||
if (msalClientException.ErrorCode == "authentication_canceled" || msalClientException.ErrorCode == "access_denied")
|
||||
throw new AccountSetupCanceledException();
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
throw new AuthenticationException(Translator.Exception_UnknowErrorDuringAuthentication, new Exception(Translator.Exception_TokenGenerationFailed));
|
||||
}
|
||||
}
|
||||
22
Wino.Authentication/Wino.Authentication.csproj
Normal file
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<RootNamespace>Wino.Authentication</RootNamespace>
|
||||
<Platforms>x86;x64;arm64</Platforms>
|
||||
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Diagnostics" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||
<PackageReference Include="Google.Apis.Auth" />
|
||||
<PackageReference Include="Microsoft.Identity.Client" />
|
||||
<PackageReference Include="Microsoft.Identity.Client.Broker" />
|
||||
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj" />
|
||||
<ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,7 +1,6 @@
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.ApplicationModel.Background;
|
||||
using Wino.Core.Domain;
|
||||
|
||||
namespace Wino.BackgroundTasks
|
||||
{
|
||||
@@ -23,8 +22,9 @@ namespace Wino.BackgroundTasks
|
||||
|
||||
var versionText = string.Format("{0}.{1}.{2}.{3}", version.Major, version.Minor, version.Build, version.Revision);
|
||||
|
||||
builder.AddText(Translator.Notifications_WinoUpdatedTitle);
|
||||
builder.AddText(string.Format(Translator.Notifications_WinoUpdatedMessage, versionText));
|
||||
// TODO: Handle with Translator, but it's not initialized here yet.
|
||||
builder.AddText("Wino Mail is updated!");
|
||||
builder.AddText(string.Format("New version {0} is ready.", versionText));
|
||||
|
||||
builder.Show();
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serilog;
|
||||
using Windows.ApplicationModel.Background;
|
||||
using Windows.Storage;
|
||||
using Wino.Core;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Services;
|
||||
using Wino.Core.UWP;
|
||||
using Wino.Services;
|
||||
|
||||
namespace Wino.BackgroundTasks
|
||||
{
|
||||
public sealed class SessionConnectedTask : IBackgroundTask
|
||||
{
|
||||
public async void Run(IBackgroundTaskInstance taskInstance)
|
||||
{
|
||||
var def = taskInstance.GetDeferral();
|
||||
|
||||
try
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
services.RegisterCoreServices();
|
||||
services.RegisterCoreUWPServices();
|
||||
|
||||
var providere = services.BuildServiceProvider();
|
||||
|
||||
var backgroundTaskService = providere.GetService<IBackgroundSynchronizer>();
|
||||
var dbService = providere.GetService<IDatabaseService>();
|
||||
var logInitializer = providere.GetService<ILogInitializer>();
|
||||
|
||||
logInitializer.SetupLogger(ApplicationData.Current.LocalFolder.Path);
|
||||
|
||||
await dbService.InitializeAsync();
|
||||
await backgroundTaskService.RunBackgroundSynchronizationAsync(Core.Domain.Enums.BackgroundSynchronizationReason.SessionConnected);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Background synchronization failed from background task.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
def.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,25 +18,6 @@
|
||||
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<AllowCrossPlatformRetargeting>false</AllowCrossPlatformRetargeting>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@@ -57,26 +38,6 @@
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
|
||||
<PlatformTarget>ARM</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\ARM\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM'">
|
||||
<PlatformTarget>ARM</PlatformTarget>
|
||||
<OutputPath>bin\ARM\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
|
||||
<PlatformTarget>ARM64</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@@ -123,9 +84,11 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="AppUpdatedTask.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SessionConnectedTask.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Identity.Client">
|
||||
<Version>4.66.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
|
||||
<Version>6.2.14</Version>
|
||||
</PackageReference>
|
||||
|
||||
BIN
Wino.Calendar.Packaging/Images/LockScreenLogo.scale-200.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Wino.Calendar.Packaging/Images/SplashScreen.scale-200.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
Wino.Calendar.Packaging/Images/Square150x150Logo.scale-200.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
Wino.Calendar.Packaging/Images/Square44x44Logo.scale-200.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
Wino.Calendar.Packaging/Images/Wide310x150Logo.scale-200.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
90
Wino.Calendar.Packaging/Package.appxmanifest
Normal file
@@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
|
||||
<Identity
|
||||
Name="58272BurakKSE.WinoCalendar"
|
||||
Publisher="CN=51FBDAF3-E212-4149-89A2-A2636B3BC911"
|
||||
Version="1.0.0.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Wino Calendar</DisplayName>
|
||||
<PublisherDisplayName>Burak KÖSE</PublisherDisplayName>
|
||||
<Logo>Images\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14393.0" MaxVersionTested="10.0.14393.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="Wino Calendar"
|
||||
Description="Wino.Calendar.Packaging"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Images\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
|
||||
<Extensions>
|
||||
<!-- Registration of full trust backend application. -->
|
||||
<uap:Extension Category="windows.appService">
|
||||
<uap:AppService Name="WinoInteropService" />
|
||||
</uap:Extension>
|
||||
|
||||
<!-- Protocol activation: Google OAuth -->
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="google.pw.oauth2">
|
||||
<uap:DisplayName>Wino Google Authentication Protocol</uap:DisplayName>
|
||||
</uap:Protocol>
|
||||
</uap:Extension>
|
||||
|
||||
<!-- Protocol activation: Launch UWP app from Full Trust Process -->
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="wino.calendar.launch">
|
||||
<uap:DisplayName>Wino Calendara Launcher Protocol</uap:DisplayName>
|
||||
</uap:Protocol>
|
||||
</uap:Extension>
|
||||
|
||||
<!-- Startup Task -->
|
||||
<uap5:Extension
|
||||
Category="windows.startupTask"
|
||||
Executable="Wino.Server\Wino.Server.exe"
|
||||
EntryPoint="Windows.FullTrustApplication">
|
||||
<uap5:StartupTask
|
||||
TaskId="WinoServer"
|
||||
Enabled="false"
|
||||
DisplayName="Wino Mail" />
|
||||
</uap5:Extension>
|
||||
|
||||
<desktop:Extension Category="windows.fullTrustProcess" Executable="Wino.Server\Wino.Server.exe">
|
||||
<desktop:FullTrustProcess>
|
||||
<desktop:ParameterGroup GroupId="WinoServer" Parameters="Calendar" />
|
||||
</desktop:FullTrustProcess>
|
||||
</desktop:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
<rescap:Capability Name="confirmAppClose" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
77
Wino.Calendar.Packaging/Wino.Calendar.Packaging.wapproj
Normal file
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '15.0'">
|
||||
<VisualStudioVersion>15.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x86">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x86</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x86">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x86</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|AnyCPU">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>AnyCPU</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|AnyCPU">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>AnyCPU</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<WapProjPath Condition="'$(WapProjPath)'==''">$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\</WapProjPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>7485b18c-f5ab-4abe-ba7f-05b6623c67c8</ProjectGuid>
|
||||
<TargetPlatformVersion>10.0.22621.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<AppxPackageSigningEnabled>false</AppxPackageSigningEnabled>
|
||||
<NoWarn>$(NoWarn);NU1702</NoWarn>
|
||||
<EntryPointProjectUniqueName>..\Wino.Calendar\Wino.Calendar.csproj</EntryPointProjectUniqueName>
|
||||
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AppxManifest Include="Package.appxmanifest">
|
||||
<SubType>Designer</SubType>
|
||||
</AppxManifest>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Images\SplashScreen.scale-200.png" />
|
||||
<Content Include="Images\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-200.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||
<Content Include="Images\StoreLogo.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-200.png" />
|
||||
<None Include="Package.StoreAssociation.xml" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.1742" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Wino.Calendar\Wino.Calendar.csproj" />
|
||||
<ProjectReference Include="..\Wino.Server\Wino.Server.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
48
Wino.Calendar.ViewModels/AccountDetailsPageViewModel.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Wino.Calendar.ViewModels.Interfaces;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.ViewModels;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Calendar.ViewModels
|
||||
{
|
||||
public partial class AccountDetailsPageViewModel : CalendarBaseViewModel
|
||||
{
|
||||
private readonly IAccountService _accountService;
|
||||
|
||||
public AccountProviderDetailViewModel Account { get; private set; }
|
||||
public ICalendarDialogService CalendarDialogService { get; }
|
||||
public IAccountCalendarStateService AccountCalendarStateService { get; }
|
||||
|
||||
public AccountDetailsPageViewModel(ICalendarDialogService calendarDialogService, IAccountService accountService, IAccountCalendarStateService accountCalendarStateService)
|
||||
{
|
||||
CalendarDialogService = calendarDialogService;
|
||||
_accountService = accountService;
|
||||
AccountCalendarStateService = accountCalendarStateService;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task RenameAccount()
|
||||
{
|
||||
if (Account == null)
|
||||
return;
|
||||
|
||||
var updatedAccount = await CalendarDialogService.ShowEditAccountDialogAsync(Account.Account);
|
||||
|
||||
if (updatedAccount != null)
|
||||
{
|
||||
await _accountService.UpdateAccountAsync(updatedAccount);
|
||||
|
||||
ReportUIChange(new AccountUpdatedMessage(updatedAccount));
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedTo(mode, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
147
Wino.Calendar.ViewModels/AccountManagementViewModel.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Authentication;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.ViewModels;
|
||||
using Wino.Messaging.Server;
|
||||
|
||||
namespace Wino.Calendar.ViewModels
|
||||
{
|
||||
public partial class AccountManagementViewModel : AccountManagementPageViewModelBase
|
||||
{
|
||||
private readonly IProviderService _providerService;
|
||||
|
||||
public AccountManagementViewModel(ICalendarDialogService dialogService,
|
||||
IWinoServerConnectionManager winoServerConnectionManager,
|
||||
INavigationService navigationService,
|
||||
IAccountService accountService,
|
||||
IProviderService providerService,
|
||||
IStoreManagementService storeManagementService,
|
||||
IAuthenticationProvider authenticationProvider,
|
||||
IPreferencesService preferencesService) : base(dialogService, winoServerConnectionManager, navigationService, accountService, providerService, storeManagementService, authenticationProvider, preferencesService)
|
||||
{
|
||||
CalendarDialogService = dialogService;
|
||||
_providerService = providerService;
|
||||
}
|
||||
|
||||
public ICalendarDialogService CalendarDialogService { get; }
|
||||
|
||||
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedTo(mode, parameters);
|
||||
|
||||
await InitializeAccountsAsync();
|
||||
}
|
||||
|
||||
public override async Task InitializeAccountsAsync()
|
||||
{
|
||||
Accounts.Clear();
|
||||
|
||||
var accounts = await AccountService.GetAccountsAsync().ConfigureAwait(false);
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
var accountDetails = GetAccountProviderDetails(account);
|
||||
|
||||
Accounts.Add(accountDetails);
|
||||
}
|
||||
});
|
||||
|
||||
await ManageStorePurchasesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task AddNewAccountAsync()
|
||||
{
|
||||
if (IsAccountCreationBlocked)
|
||||
{
|
||||
var isPurchaseClicked = await DialogService.ShowConfirmationDialogAsync(Translator.DialogMessage_AccountLimitMessage, Translator.DialogMessage_AccountLimitTitle, Translator.Buttons_Purchase);
|
||||
|
||||
if (!isPurchaseClicked) return;
|
||||
|
||||
await PurchaseUnlimitedAccountAsync();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var availableProviders = _providerService.GetAvailableProviders();
|
||||
|
||||
var accountCreationDialogResult = await DialogService.ShowAccountProviderSelectionDialogAsync(availableProviders);
|
||||
|
||||
if (accountCreationDialogResult == null) return;
|
||||
|
||||
var accountCreationCancellationTokenSource = new CancellationTokenSource();
|
||||
var accountCreationDialog = CalendarDialogService.GetAccountCreationDialog(accountCreationDialogResult);
|
||||
|
||||
accountCreationDialog.ShowDialog(accountCreationCancellationTokenSource);
|
||||
accountCreationDialog.State = AccountCreationDialogState.SigningIn;
|
||||
|
||||
// For OAuth authentications, we just generate token and assign it to the MailAccount.
|
||||
|
||||
var createdAccount = new MailAccount()
|
||||
{
|
||||
ProviderType = accountCreationDialogResult.ProviderType,
|
||||
Name = accountCreationDialogResult.AccountName,
|
||||
Id = Guid.NewGuid()
|
||||
};
|
||||
|
||||
var tokenInformationResponse = await WinoServerConnectionManager
|
||||
.GetResponseAsync<TokenInformationEx, AuthorizationRequested>(new AuthorizationRequested(accountCreationDialogResult.ProviderType,
|
||||
createdAccount,
|
||||
createdAccount.ProviderType == MailProviderType.Gmail), accountCreationCancellationTokenSource.Token);
|
||||
|
||||
if (accountCreationDialog.State == AccountCreationDialogState.Canceled)
|
||||
throw new AccountSetupCanceledException();
|
||||
|
||||
tokenInformationResponse.ThrowIfFailed();
|
||||
|
||||
await AccountService.CreateAccountAsync(createdAccount, null);
|
||||
|
||||
// Sync profile information if supported.
|
||||
if (createdAccount.IsProfileInfoSyncSupported)
|
||||
{
|
||||
// Start profile information synchronization.
|
||||
// It's only available for Outlook and Gmail synchronizers.
|
||||
|
||||
var profileSyncOptions = new MailSynchronizationOptions()
|
||||
{
|
||||
AccountId = createdAccount.Id,
|
||||
Type = MailSynchronizationType.UpdateProfile
|
||||
};
|
||||
|
||||
var profileSynchronizationResponse = await WinoServerConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(new NewMailSynchronizationRequested(profileSyncOptions, SynchronizationSource.Client));
|
||||
|
||||
var profileSynchronizationResult = profileSynchronizationResponse.Data;
|
||||
|
||||
if (profileSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeProfileInformation);
|
||||
|
||||
createdAccount.SenderName = profileSynchronizationResult.ProfileInformation.SenderName;
|
||||
createdAccount.Base64ProfilePictureData = profileSynchronizationResult.ProfileInformation.Base64ProfilePictureData;
|
||||
|
||||
await AccountService.UpdateProfileInformationAsync(createdAccount.Id, profileSynchronizationResult.ProfileInformation);
|
||||
}
|
||||
|
||||
accountCreationDialog.State = AccountCreationDialogState.FetchingEvents;
|
||||
|
||||
// Start synchronizing events.
|
||||
var synchronizationOptions = new CalendarSynchronizationOptions()
|
||||
{
|
||||
AccountId = createdAccount.Id,
|
||||
Type = CalendarSynchronizationType.CalendarMetadata
|
||||
};
|
||||
|
||||
var synchronizationResponse = await WinoServerConnectionManager.GetResponseAsync<CalendarSynchronizationResult, NewCalendarSynchronizationRequested>(new NewCalendarSynchronizationRequested(synchronizationOptions, SynchronizationSource.Client));
|
||||
}
|
||||
}
|
||||
}
|
||||
367
Wino.Calendar.ViewModels/AppShellViewModel.cs
Normal file
@@ -0,0 +1,367 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Serilog;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Calendar.ViewModels.Interfaces;
|
||||
using Wino.Core.Domain.Collections;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Extensions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.ViewModels;
|
||||
using Wino.Messaging.Client.Calendar;
|
||||
using Wino.Messaging.Client.Navigation;
|
||||
using Wino.Messaging.Server;
|
||||
|
||||
namespace Wino.Calendar.ViewModels
|
||||
{
|
||||
public partial class AppShellViewModel : CalendarBaseViewModel,
|
||||
IRecipient<VisibleDateRangeChangedMessage>,
|
||||
IRecipient<CalendarEnableStatusChangedMessage>,
|
||||
IRecipient<NavigateManageAccountsRequested>,
|
||||
IRecipient<CalendarDisplayTypeChangedMessage>,
|
||||
IRecipient<DetailsPageStateChangedMessage>
|
||||
{
|
||||
public IPreferencesService PreferencesService { get; }
|
||||
public IStatePersistanceService StatePersistenceService { get; }
|
||||
public IAccountCalendarStateService AccountCalendarStateService { get; }
|
||||
public INavigationService NavigationService { get; }
|
||||
public IWinoServerConnectionManager ServerConnectionManager { get; }
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isEventDetailsPageActive;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _selectedMenuItemIndex = -1;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool isCalendarEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the active connection status of the Wino server.
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private WinoServerConnectionStatus activeConnectionStatus;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the display date of the calendar.
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private DateTimeOffset _displayDate;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the highlighted range in the CalendarView and displayed date range in FlipView.
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private DateRange highlightedDateRange;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableRangeCollection<string> dateNavigationHeaderItems = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private int _selectedDateNavigationHeaderIndex;
|
||||
|
||||
public bool IsVerticalCalendar => StatePersistenceService.CalendarDisplayType == CalendarDisplayType.Month;
|
||||
|
||||
// For updating account calendars asynchronously.
|
||||
private SemaphoreSlim _accountCalendarUpdateSemaphoreSlim = new(1);
|
||||
|
||||
public AppShellViewModel(IPreferencesService preferencesService,
|
||||
IStatePersistanceService statePersistanceService,
|
||||
IAccountService accountService,
|
||||
ICalendarService calendarService,
|
||||
IAccountCalendarStateService accountCalendarStateService,
|
||||
INavigationService navigationService,
|
||||
IWinoServerConnectionManager serverConnectionManager)
|
||||
{
|
||||
_accountService = accountService;
|
||||
_calendarService = calendarService;
|
||||
|
||||
AccountCalendarStateService = accountCalendarStateService;
|
||||
AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested;
|
||||
AccountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged;
|
||||
|
||||
NavigationService = navigationService;
|
||||
ServerConnectionManager = serverConnectionManager;
|
||||
PreferencesService = preferencesService;
|
||||
|
||||
StatePersistenceService = statePersistanceService;
|
||||
StatePersistenceService.StatePropertyChanged += PrefefencesChanged;
|
||||
}
|
||||
|
||||
private void SelectedCalendarItemsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private void PrefefencesChanged(object sender, string e)
|
||||
{
|
||||
if (e == nameof(StatePersistenceService.CalendarDisplayType))
|
||||
{
|
||||
Messenger.Send(new CalendarDisplayTypeChangedMessage(StatePersistenceService.CalendarDisplayType));
|
||||
|
||||
|
||||
// Change the calendar.
|
||||
DateClicked(new CalendarViewDayClickedEventArgs(GetDisplayTypeSwitchDate()));
|
||||
}
|
||||
}
|
||||
|
||||
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedTo(mode, parameters);
|
||||
|
||||
UpdateDateNavigationHeaderItems();
|
||||
|
||||
await InitializeAccountCalendarsAsync();
|
||||
|
||||
TodayClicked();
|
||||
}
|
||||
|
||||
private async void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e)
|
||||
{
|
||||
// When using three-state checkbox, multiple accounts will be selected/unselected at the same time.
|
||||
// Reporting all these changes one by one to the UI is not efficient and may cause problems in the future.
|
||||
|
||||
// Update all calendar states at once.
|
||||
try
|
||||
{
|
||||
await _accountCalendarUpdateSemaphoreSlim.WaitAsync();
|
||||
|
||||
foreach (var calendar in e.AccountCalendars)
|
||||
{
|
||||
await _calendarService.UpdateAccountCalendarAsync(calendar.AccountCalendar).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error while waiting for account calendar update semaphore.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_accountCalendarUpdateSemaphoreSlim.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async void UpdateAccountCalendarRequested(object sender, AccountCalendarViewModel e)
|
||||
=> await _calendarService.UpdateAccountCalendarAsync(e.AccountCalendar).ConfigureAwait(false);
|
||||
|
||||
private async Task InitializeAccountCalendarsAsync()
|
||||
{
|
||||
await Dispatcher.ExecuteOnUIThread(() => AccountCalendarStateService.ClearGroupedAccountCalendar());
|
||||
|
||||
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
var accountCalendars = await _calendarService.GetAccountCalendarsAsync(account.Id).ConfigureAwait(false);
|
||||
var calendarViewModels = new List<AccountCalendarViewModel>();
|
||||
|
||||
foreach (var calendar in accountCalendars)
|
||||
{
|
||||
var calendarViewModel = new AccountCalendarViewModel(account, calendar);
|
||||
|
||||
calendarViewModels.Add(calendarViewModel);
|
||||
}
|
||||
|
||||
var groupedAccountCalendarViewModel = new GroupedAccountCalendarViewModel(account, calendarViewModels);
|
||||
|
||||
await Dispatcher.ExecuteOnUIThread(() =>
|
||||
{
|
||||
AccountCalendarStateService.AddGroupedAccountCalendar(groupedAccountCalendarViewModel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void ForceNavigateCalendarDate()
|
||||
{
|
||||
if (SelectedMenuItemIndex == -1)
|
||||
{
|
||||
var args = new CalendarPageNavigationArgs()
|
||||
{
|
||||
NavigationDate = _navigationDate ?? DateTime.Now.Date
|
||||
};
|
||||
|
||||
// Already on calendar. Just navigate.
|
||||
NavigationService.Navigate(WinoPage.CalendarPage, args);
|
||||
|
||||
_navigationDate = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedMenuItemIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedMenuItemIndexChanged(int oldValue, int newValue)
|
||||
{
|
||||
switch (newValue)
|
||||
{
|
||||
case -1:
|
||||
ForceNavigateCalendarDate();
|
||||
break;
|
||||
case 0:
|
||||
NavigationService.Navigate(WinoPage.ManageAccountsPage);
|
||||
break;
|
||||
case 1:
|
||||
NavigationService.Navigate(WinoPage.SettingsPage);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task Sync()
|
||||
{
|
||||
// Sync all calendars.
|
||||
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
var t = new NewCalendarSynchronizationRequested(new CalendarSynchronizationOptions()
|
||||
{
|
||||
AccountId = account.Id,
|
||||
Type = CalendarSynchronizationType.CalendarMetadata
|
||||
}, SynchronizationSource.Client);
|
||||
|
||||
Messenger.Send(t);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When calendar type switches, we need to navigate to the most ideal date.
|
||||
/// This method returns that date.
|
||||
/// </summary>
|
||||
private DateTime GetDisplayTypeSwitchDate()
|
||||
{
|
||||
var settings = PreferencesService.GetCurrentCalendarSettings();
|
||||
switch (StatePersistenceService.CalendarDisplayType)
|
||||
{
|
||||
case CalendarDisplayType.Day:
|
||||
if (HighlightedDateRange.IsInRange(DateTime.Now)) return DateTime.Now.Date;
|
||||
|
||||
return HighlightedDateRange.StartDate;
|
||||
case CalendarDisplayType.Week:
|
||||
if (HighlightedDateRange == null || HighlightedDateRange.IsInRange(DateTime.Now))
|
||||
{
|
||||
return DateTime.Now.Date.GetWeekStartDateForDate(settings.FirstDayOfWeek);
|
||||
}
|
||||
|
||||
return HighlightedDateRange.StartDate.GetWeekStartDateForDate(settings.FirstDayOfWeek);
|
||||
case CalendarDisplayType.WorkWeek:
|
||||
break;
|
||||
case CalendarDisplayType.Month:
|
||||
break;
|
||||
case CalendarDisplayType.Year:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return DateTime.Today.Date;
|
||||
}
|
||||
|
||||
private DateTime? _navigationDate;
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly ICalendarService _calendarService;
|
||||
|
||||
#region Commands
|
||||
|
||||
[RelayCommand]
|
||||
private void TodayClicked()
|
||||
{
|
||||
_navigationDate = DateTime.Now.Date;
|
||||
|
||||
ForceNavigateCalendarDate();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void ManageAccounts() => NavigationService.Navigate(WinoPage.AccountManagementPage);
|
||||
|
||||
[RelayCommand]
|
||||
private Task ReconnectServerAsync() => ServerConnectionManager.ConnectAsync();
|
||||
|
||||
[RelayCommand]
|
||||
private void DateClicked(CalendarViewDayClickedEventArgs clickedDateArgs)
|
||||
{
|
||||
_navigationDate = clickedDateArgs.ClickedDate;
|
||||
|
||||
ForceNavigateCalendarDate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Receive(VisibleDateRangeChangedMessage message) => HighlightedDateRange = message.DateRange;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the header navigation items based on visible date range and calendar type.
|
||||
/// </summary>
|
||||
private void UpdateDateNavigationHeaderItems()
|
||||
{
|
||||
DateNavigationHeaderItems.Clear();
|
||||
|
||||
// TODO: From settings
|
||||
var testInfo = new CultureInfo("en-US");
|
||||
|
||||
switch (StatePersistenceService.CalendarDisplayType)
|
||||
{
|
||||
case CalendarDisplayType.Day:
|
||||
case CalendarDisplayType.Week:
|
||||
case CalendarDisplayType.WorkWeek:
|
||||
case CalendarDisplayType.Month:
|
||||
DateNavigationHeaderItems.ReplaceRange(testInfo.DateTimeFormat.MonthNames);
|
||||
break;
|
||||
case CalendarDisplayType.Year:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
SetDateNavigationHeaderItems();
|
||||
}
|
||||
|
||||
partial void OnHighlightedDateRangeChanged(DateRange value) => SetDateNavigationHeaderItems();
|
||||
|
||||
private void SetDateNavigationHeaderItems()
|
||||
{
|
||||
if (HighlightedDateRange == null) return;
|
||||
|
||||
if (DateNavigationHeaderItems.Count == 0)
|
||||
{
|
||||
UpdateDateNavigationHeaderItems();
|
||||
}
|
||||
|
||||
// TODO: Year view
|
||||
var monthIndex = HighlightedDateRange.GetMostVisibleMonthIndex();
|
||||
|
||||
SelectedDateNavigationHeaderIndex = Math.Max(monthIndex - 1, -1);
|
||||
}
|
||||
|
||||
public async void Receive(CalendarEnableStatusChangedMessage message)
|
||||
=> await ExecuteUIThread(() => IsCalendarEnabled = message.IsEnabled);
|
||||
|
||||
public void Receive(NavigateManageAccountsRequested message) => SelectedMenuItemIndex = 1;
|
||||
|
||||
public void Receive(CalendarDisplayTypeChangedMessage message) => OnPropertyChanged(nameof(IsVerticalCalendar));
|
||||
|
||||
public async void Receive(DetailsPageStateChangedMessage message)
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
IsEventDetailsPageActive = message.IsActivated;
|
||||
|
||||
// TODO: This is for Wino Mail. Generalize this later on.
|
||||
StatePersistenceService.IsReaderNarrowed = message.IsActivated;
|
||||
StatePersistenceService.IsReadingMail = message.IsActivated;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
871
Wino.Calendar.ViewModels/CalendarPageViewModel.cs
Normal file
@@ -0,0 +1,871 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Diagnostics;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using MoreLinq;
|
||||
using Serilog;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Calendar.ViewModels.Interfaces;
|
||||
using Wino.Calendar.ViewModels.Messages;
|
||||
using Wino.Core.Domain.Collections;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Core.Domain.Models.Calendar.CalendarTypeStrategies;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.ViewModels;
|
||||
using Wino.Messaging.Client.Calendar;
|
||||
|
||||
namespace Wino.Calendar.ViewModels
|
||||
{
|
||||
public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
||||
IRecipient<LoadCalendarMessage>,
|
||||
IRecipient<CalendarItemDeleted>,
|
||||
IRecipient<CalendarSettingsUpdatedMessage>,
|
||||
IRecipient<CalendarItemTappedMessage>,
|
||||
IRecipient<CalendarItemDoubleTappedMessage>,
|
||||
IRecipient<CalendarItemRightTappedMessage>
|
||||
{
|
||||
#region Quick Event Creation
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isQuickEventDialogOpen;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(SelectedQuickEventAccountCalendarName))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SaveQuickEventCommand))]
|
||||
private AccountCalendarViewModel _selectedQuickEventAccountCalendar;
|
||||
|
||||
public string SelectedQuickEventAccountCalendarName
|
||||
{
|
||||
get
|
||||
{
|
||||
return SelectedQuickEventAccountCalendar == null ? "Pick a calendar" : SelectedQuickEventAccountCalendar.Name;
|
||||
}
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private List<string> _hourSelectionStrings;
|
||||
|
||||
// To be able to revert the values when the user enters an invalid time.
|
||||
private string _previousSelectedStartTimeString;
|
||||
private string _previousSelectedEndTimeString;
|
||||
|
||||
[ObservableProperty]
|
||||
private DateTime? _selectedQuickEventDate;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isAllDay;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(SaveQuickEventCommand))]
|
||||
private string _selectedStartTimeString;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(SaveQuickEventCommand))]
|
||||
private string _selectedEndTimeString;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _location;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(SaveQuickEventCommand))]
|
||||
private string _eventName;
|
||||
|
||||
public DateTime QuickEventStartTime => SelectedQuickEventDate.Value.Date.Add(CurrentSettings.GetTimeSpan(SelectedStartTimeString).Value);
|
||||
public DateTime QuickEventEndTime => SelectedQuickEventDate.Value.Date.Add(CurrentSettings.GetTimeSpan(SelectedEndTimeString).Value);
|
||||
|
||||
public bool CanSaveQuickEvent => SelectedQuickEventAccountCalendar != null &&
|
||||
!string.IsNullOrWhiteSpace(EventName) &&
|
||||
!string.IsNullOrWhiteSpace(SelectedStartTimeString) &&
|
||||
!string.IsNullOrWhiteSpace(SelectedEndTimeString) &&
|
||||
QuickEventEndTime > QuickEventStartTime;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Data Initialization
|
||||
|
||||
[ObservableProperty]
|
||||
private CalendarOrientation _calendarOrientation = CalendarOrientation.Horizontal;
|
||||
|
||||
[ObservableProperty]
|
||||
private DayRangeCollection _dayRanges = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private int _selectedDateRangeIndex;
|
||||
|
||||
[ObservableProperty]
|
||||
private DayRangeRenderModel _selectedDayRange;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isCalendarEnabled = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Details
|
||||
|
||||
public event EventHandler DetailsShowCalendarItemChanged;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsEventDetailsVisible))]
|
||||
private CalendarItemViewModel _displayDetailsCalendarItemViewModel;
|
||||
|
||||
public bool IsEventDetailsVisible => DisplayDetailsCalendarItemViewModel != null;
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: Get rid of some of the items if we have too many.
|
||||
private const int maxDayRangeSize = 10;
|
||||
|
||||
private readonly ICalendarService _calendarService;
|
||||
private readonly INavigationService _navigationService;
|
||||
private readonly IKeyPressService _keyPressService;
|
||||
private readonly IPreferencesService _preferencesService;
|
||||
|
||||
// Store latest rendered options.
|
||||
private CalendarDisplayType _currentDisplayType;
|
||||
private int _displayDayCount;
|
||||
|
||||
private SemaphoreSlim _calendarLoadingSemaphore = new(1);
|
||||
private bool isLoadMoreBlocked = false;
|
||||
|
||||
[ObservableProperty]
|
||||
private CalendarSettings _currentSettings;
|
||||
|
||||
public IStatePersistanceService StatePersistanceService { get; }
|
||||
public IAccountCalendarStateService AccountCalendarStateService { get; }
|
||||
|
||||
public CalendarPageViewModel(IStatePersistanceService statePersistanceService,
|
||||
ICalendarService calendarService,
|
||||
INavigationService navigationService,
|
||||
IKeyPressService keyPressService,
|
||||
IAccountCalendarStateService accountCalendarStateService,
|
||||
IPreferencesService preferencesService)
|
||||
{
|
||||
StatePersistanceService = statePersistanceService;
|
||||
AccountCalendarStateService = accountCalendarStateService;
|
||||
|
||||
_calendarService = calendarService;
|
||||
_navigationService = navigationService;
|
||||
_keyPressService = keyPressService;
|
||||
_preferencesService = preferencesService;
|
||||
|
||||
AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested;
|
||||
AccountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged;
|
||||
}
|
||||
|
||||
private void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e)
|
||||
=> FilterActiveCalendars(DayRanges);
|
||||
|
||||
private void UpdateAccountCalendarRequested(object sender, AccountCalendarViewModel e)
|
||||
=> FilterActiveCalendars(DayRanges);
|
||||
|
||||
private async void FilterActiveCalendars(IEnumerable<DayRangeRenderModel> dayRangeRenderModels)
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
var days = dayRangeRenderModels.SelectMany(a => a.CalendarDays);
|
||||
|
||||
days.ForEach(a => a.EventsCollection.FilterByCalendars(AccountCalendarStateService.ActiveCalendars.Select(a => a.Id)));
|
||||
|
||||
DisplayDetailsCalendarItemViewModel = null;
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Replace when calendar settings are updated.
|
||||
// Should be a field ideally.
|
||||
private BaseCalendarTypeDrawingStrategy GetDrawingStrategy(CalendarDisplayType displayType)
|
||||
{
|
||||
return displayType switch
|
||||
{
|
||||
CalendarDisplayType.Day => new DayCalendarDrawingStrategy(CurrentSettings),
|
||||
CalendarDisplayType.Week => new WeekCalendarDrawingStrategy(CurrentSettings),
|
||||
CalendarDisplayType.Month => new MonthCalendarDrawingStrategy(CurrentSettings),
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
|
||||
{
|
||||
// Do not call base method because that will unregister messenger recipient.
|
||||
// This is a singleton view model and should not be unregistered.
|
||||
}
|
||||
|
||||
public override void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedTo(mode, parameters);
|
||||
|
||||
if (mode == NavigationMode.Back) return;
|
||||
|
||||
RefreshSettings();
|
||||
|
||||
// Automatically select the first primary calendar for quick event dialog.
|
||||
SelectedQuickEventAccountCalendar = AccountCalendarStateService.ActiveCalendars.FirstOrDefault(a => a.IsPrimary);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void NavigateSeries()
|
||||
{
|
||||
if (DisplayDetailsCalendarItemViewModel == null) return;
|
||||
|
||||
NavigateEvent(DisplayDetailsCalendarItemViewModel, CalendarEventTargetType.Series);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void NavigateEventDetails()
|
||||
{
|
||||
if (DisplayDetailsCalendarItemViewModel == null) return;
|
||||
|
||||
NavigateEvent(DisplayDetailsCalendarItemViewModel, CalendarEventTargetType.Single);
|
||||
}
|
||||
|
||||
private void NavigateEvent(CalendarItemViewModel calendarItemViewModel, CalendarEventTargetType calendarEventTargetType)
|
||||
{
|
||||
var target = new CalendarItemTarget(calendarItemViewModel.CalendarItem, calendarEventTargetType);
|
||||
_navigationService.Navigate(WinoPage.EventDetailsPage, target);
|
||||
}
|
||||
|
||||
[RelayCommand(AllowConcurrentExecutions = false, CanExecute = nameof(CanSaveQuickEvent))]
|
||||
private async Task SaveQuickEventAsync()
|
||||
{
|
||||
var durationSeconds = (QuickEventEndTime - QuickEventStartTime).TotalSeconds;
|
||||
|
||||
var testCalendarItem = new CalendarItem
|
||||
{
|
||||
CalendarId = SelectedQuickEventAccountCalendar.Id,
|
||||
StartDate = QuickEventStartTime,
|
||||
DurationInSeconds = durationSeconds,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
Description = string.Empty,
|
||||
Location = Location,
|
||||
Title = EventName,
|
||||
Id = Guid.NewGuid()
|
||||
};
|
||||
|
||||
IsQuickEventDialogOpen = false;
|
||||
await _calendarService.CreateNewCalendarItemAsync(testCalendarItem, null);
|
||||
|
||||
// TODO: Create the request with the synchronizer.
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void MoreDetails()
|
||||
{
|
||||
// TODO: Navigate to advanced event creation page with existing parameters.
|
||||
}
|
||||
|
||||
public void SelectQuickEventTimeRange(TimeSpan startTime, TimeSpan endTime)
|
||||
{
|
||||
IsAllDay = false;
|
||||
|
||||
SelectedStartTimeString = CurrentSettings.GetTimeString(startTime);
|
||||
SelectedEndTimeString = CurrentSettings.GetTimeString(endTime);
|
||||
}
|
||||
|
||||
// Manage event detail popup context and select-unselect the proper items.
|
||||
// Item selection rules are defined in the selection method.
|
||||
partial void OnDisplayDetailsCalendarItemViewModelChanging(CalendarItemViewModel oldValue, CalendarItemViewModel newValue)
|
||||
{
|
||||
if (oldValue != null)
|
||||
{
|
||||
UnselectCalendarItem(oldValue);
|
||||
}
|
||||
|
||||
if (newValue != null)
|
||||
{
|
||||
SelectCalendarItem(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify view that the detail context changed.
|
||||
// This will align the event detail popup to the selected event.
|
||||
partial void OnDisplayDetailsCalendarItemViewModelChanged(CalendarItemViewModel value)
|
||||
=> DetailsShowCalendarItemChanged?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
private void RefreshSettings()
|
||||
{
|
||||
CurrentSettings = _preferencesService.GetCurrentCalendarSettings();
|
||||
|
||||
// Populate the hour selection strings.
|
||||
var timeStrings = new List<string>();
|
||||
|
||||
for (int hour = 0; hour < 24; hour++)
|
||||
{
|
||||
for (int minute = 0; minute < 60; minute += 30)
|
||||
{
|
||||
var time = new DateTime(1, 1, 1, hour, minute, 0);
|
||||
|
||||
if (CurrentSettings.DayHeaderDisplayType == DayHeaderDisplayType.TwentyFourHour)
|
||||
{
|
||||
timeStrings.Add(time.ToString("HH:mm"));
|
||||
}
|
||||
else
|
||||
{
|
||||
timeStrings.Add(time.ToString("h:mm tt"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HourSelectionStrings = timeStrings;
|
||||
}
|
||||
|
||||
partial void OnIsCalendarEnabledChanging(bool oldValue, bool newValue) => Messenger.Send(new CalendarEnableStatusChangedMessage(newValue));
|
||||
|
||||
private bool ShouldResetDayRanges(LoadCalendarMessage message)
|
||||
{
|
||||
if (message.ForceRedraw) return true;
|
||||
|
||||
// Never reset if the initiative is from the app.
|
||||
if (message.CalendarInitInitiative == CalendarInitInitiative.App) return false;
|
||||
|
||||
// 1. Display type is different.
|
||||
// 2. Day display count is different.
|
||||
// 3. Display date is not in the visible range.
|
||||
|
||||
if (DayRanges.DisplayRange == null) return false;
|
||||
|
||||
return
|
||||
(_currentDisplayType != StatePersistanceService.CalendarDisplayType ||
|
||||
_displayDayCount != StatePersistanceService.DayDisplayCount ||
|
||||
!(message.DisplayDate >= DayRanges.DisplayRange.StartDate && message.DisplayDate <= DayRanges.DisplayRange.EndDate));
|
||||
}
|
||||
|
||||
private void AdjustCalendarOrientation()
|
||||
{
|
||||
// Orientation only changes when we should reset.
|
||||
// Handle the FlipView orientation here.
|
||||
// We don't want to change the orientation while the item manipulation is going on.
|
||||
// That causes a glitch in the UI.
|
||||
|
||||
bool isRequestedVerticalCalendar = StatePersistanceService.CalendarDisplayType == CalendarDisplayType.Month;
|
||||
bool isLastRenderedVerticalCalendar = _currentDisplayType == CalendarDisplayType.Month;
|
||||
|
||||
if (isRequestedVerticalCalendar && !isLastRenderedVerticalCalendar)
|
||||
{
|
||||
CalendarOrientation = CalendarOrientation.Vertical;
|
||||
}
|
||||
else
|
||||
{
|
||||
CalendarOrientation = CalendarOrientation.Horizontal;
|
||||
}
|
||||
}
|
||||
|
||||
public async void Receive(LoadCalendarMessage message)
|
||||
{
|
||||
await _calendarLoadingSemaphore.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
await ExecuteUIThread(() => IsCalendarEnabled = false);
|
||||
|
||||
if (ShouldResetDayRanges(message))
|
||||
{
|
||||
Debug.WriteLine("Will reset day ranges.");
|
||||
await ClearDayRangeModelsAsync();
|
||||
}
|
||||
else if (ShouldScrollToItem(message))
|
||||
{
|
||||
// Scroll to the selected date.
|
||||
Messenger.Send(new ScrollToDateMessage(message.DisplayDate));
|
||||
Debug.WriteLine("Scrolling to selected date.");
|
||||
return;
|
||||
}
|
||||
|
||||
AdjustCalendarOrientation();
|
||||
|
||||
// This will replace the whole collection because the user initiated a new render.
|
||||
await RenderDatesAsync(message.CalendarInitInitiative,
|
||||
message.DisplayDate,
|
||||
CalendarLoadDirection.Replace);
|
||||
|
||||
// Scroll to the current hour.
|
||||
Messenger.Send(new ScrollToHourMessage(TimeSpan.FromHours(DateTime.Now.Hour)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error while loading calendar.");
|
||||
Debugger.Break();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_calendarLoadingSemaphore.Release();
|
||||
|
||||
await ExecuteUIThread(() => IsCalendarEnabled = true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task AddDayRangeModelAsync(DayRangeRenderModel dayRangeRenderModel)
|
||||
{
|
||||
if (dayRangeRenderModel == null) return;
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
DayRanges.Add(dayRangeRenderModel);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task InsertDayRangeModelAsync(DayRangeRenderModel dayRangeRenderModel, int index)
|
||||
{
|
||||
if (dayRangeRenderModel == null) return;
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
DayRanges.Insert(index, dayRangeRenderModel);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task RemoveDayRangeModelAsync(DayRangeRenderModel dayRangeRenderModel)
|
||||
{
|
||||
if (dayRangeRenderModel == null) return;
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
DayRanges.Remove(dayRangeRenderModel);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task ClearDayRangeModelsAsync()
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
DayRanges.Clear();
|
||||
});
|
||||
}
|
||||
|
||||
private async Task RenderDatesAsync(CalendarInitInitiative calendarInitInitiative,
|
||||
DateTime? loadingDisplayDate = null,
|
||||
CalendarLoadDirection calendarLoadDirection = CalendarLoadDirection.Replace)
|
||||
{
|
||||
isLoadMoreBlocked = calendarLoadDirection == CalendarLoadDirection.Replace;
|
||||
|
||||
// This is the part we arrange the flip view calendar logic.
|
||||
|
||||
/* Loading for a month of the selected date is fine.
|
||||
* If the selected date is in the loaded range, we'll just change the selected flip index to scroll.
|
||||
* If the selected date is not in the loaded range:
|
||||
* 1. Detect the direction of the scroll.
|
||||
* 2. Load the next month.
|
||||
* 3. Replace existing month with the new month.
|
||||
*/
|
||||
|
||||
// 2 things are important: How many items should 1 flip have, and, where we should start loading?
|
||||
|
||||
// User initiated renders must always have a date to start with.
|
||||
if (calendarInitInitiative == CalendarInitInitiative.User) Guard.IsNotNull(loadingDisplayDate, nameof(loadingDisplayDate));
|
||||
|
||||
var strategy = GetDrawingStrategy(StatePersistanceService.CalendarDisplayType);
|
||||
var displayDate = loadingDisplayDate.GetValueOrDefault();
|
||||
|
||||
// How many days should be placed in 1 flip view item?
|
||||
int eachFlipItemCount = strategy.GetRenderDayCount(displayDate, StatePersistanceService.DayDisplayCount);
|
||||
|
||||
DateRange flipLoadRange = null;
|
||||
|
||||
|
||||
if (calendarInitInitiative == CalendarInitInitiative.User || DayRanges.DisplayRange == null)
|
||||
{
|
||||
flipLoadRange = strategy.GetRenderDateRange(displayDate, StatePersistanceService.DayDisplayCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
// App is trying to load.
|
||||
// This should be based on direction. We'll load the next or previous range.
|
||||
// DisplayDate is either the start or end date of the current visible range.
|
||||
|
||||
if (calendarLoadDirection == CalendarLoadDirection.Previous)
|
||||
{
|
||||
flipLoadRange = strategy.GetPreviousDateRange(DayRanges.DisplayRange, StatePersistanceService.DayDisplayCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
flipLoadRange = strategy.GetNextDateRange(DayRanges.DisplayRange, StatePersistanceService.DayDisplayCount);
|
||||
}
|
||||
}
|
||||
|
||||
// Create day ranges for each flip item until we reach the total days to load.
|
||||
int totalFlipItemCount = (int)Math.Ceiling((double)flipLoadRange.TotalDays / eachFlipItemCount);
|
||||
|
||||
List<DayRangeRenderModel> renderModels = new();
|
||||
|
||||
for (int i = 0; i < totalFlipItemCount; i++)
|
||||
{
|
||||
var startDate = flipLoadRange.StartDate.AddDays(i * eachFlipItemCount);
|
||||
var endDate = startDate.AddDays(eachFlipItemCount);
|
||||
|
||||
var range = new DateRange(startDate, endDate);
|
||||
var renderOptions = new CalendarRenderOptions(range, CurrentSettings);
|
||||
|
||||
var dayRangeHeaderModel = new DayRangeRenderModel(renderOptions);
|
||||
renderModels.Add(dayRangeHeaderModel);
|
||||
}
|
||||
|
||||
// Dates are loaded. Now load the events for them.
|
||||
foreach (var renderModel in renderModels)
|
||||
{
|
||||
await InitializeCalendarEventsForDayRangeAsync(renderModel).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Filter by active calendars. This is a quick operation, and things are not on the UI yet.
|
||||
FilterActiveCalendars(renderModels);
|
||||
|
||||
CalendarLoadDirection animationDirection = calendarLoadDirection;
|
||||
|
||||
//bool removeCurrent = calendarLoadDirection == CalendarLoadDirection.Replace;
|
||||
|
||||
if (calendarLoadDirection == CalendarLoadDirection.Replace)
|
||||
{
|
||||
// New date ranges are being replaced.
|
||||
// We must preserve existing selection if any, add the items before/after the current one, remove the current one.
|
||||
// This will make sure the new dates are animated in the correct direction.
|
||||
|
||||
isLoadMoreBlocked = true;
|
||||
|
||||
// Remove all other dates except this one.
|
||||
var rangesToRemove = DayRanges.Where(a => a != SelectedDayRange).ToList();
|
||||
|
||||
foreach (var range in rangesToRemove)
|
||||
{
|
||||
await RemoveDayRangeModelAsync(range);
|
||||
}
|
||||
|
||||
animationDirection = displayDate <= SelectedDayRange?.CalendarRenderOptions.DateRange.StartDate ?
|
||||
CalendarLoadDirection.Previous : CalendarLoadDirection.Next;
|
||||
}
|
||||
|
||||
if (animationDirection == CalendarLoadDirection.Next)
|
||||
{
|
||||
foreach (var item in renderModels)
|
||||
{
|
||||
await AddDayRangeModelAsync(item);
|
||||
}
|
||||
}
|
||||
else if (animationDirection == CalendarLoadDirection.Previous)
|
||||
{
|
||||
// Wait for the animation to finish.
|
||||
// Otherwise it somehow shutters a little, which is annoying.
|
||||
|
||||
// if (!removeCurrent) await Task.Delay(350);
|
||||
|
||||
// Insert each render model in reverse order.
|
||||
for (int i = renderModels.Count - 1; i >= 0; i--)
|
||||
{
|
||||
await InsertDayRangeModelAsync(renderModels[i], 0);
|
||||
}
|
||||
}
|
||||
|
||||
Debug.WriteLine($"Flip count: ({DayRanges.Count})");
|
||||
|
||||
foreach (var item in DayRanges)
|
||||
{
|
||||
Debug.WriteLine($"- {item.CalendarRenderOptions.DateRange.ToString()}");
|
||||
}
|
||||
|
||||
//if (removeCurrent)
|
||||
//{
|
||||
// await RemoveDayRangeModelAsync(SelectedDayRange);
|
||||
//}
|
||||
|
||||
// TODO...
|
||||
// await TryConsolidateItemsAsync();
|
||||
|
||||
isLoadMoreBlocked = false;
|
||||
|
||||
// Only scroll if the render is initiated by user.
|
||||
// Otherwise we'll scroll to the app rendered invisible date range.
|
||||
if (calendarInitInitiative == CalendarInitInitiative.User)
|
||||
{
|
||||
// Save the current settings for the page for later comparison.
|
||||
_currentDisplayType = StatePersistanceService.CalendarDisplayType;
|
||||
_displayDayCount = StatePersistanceService.DayDisplayCount;
|
||||
|
||||
Messenger.Send(new ScrollToDateMessage(displayDate));
|
||||
}
|
||||
}
|
||||
|
||||
protected override async void OnCalendarItemAdded(CalendarItem calendarItem)
|
||||
{
|
||||
base.OnCalendarItemAdded(calendarItem);
|
||||
|
||||
// Check if event falls into the current date range.
|
||||
|
||||
|
||||
if (DayRanges.DisplayRange == null) return;
|
||||
|
||||
// Check whether this event falls into any of the loaded date ranges.
|
||||
var allDaysForEvent = DayRanges.SelectMany(a => a.CalendarDays).Where(a => a.Period.OverlapsWith(calendarItem.Period));
|
||||
|
||||
foreach (var calendarDay in allDaysForEvent)
|
||||
{
|
||||
var calendarItemViewModel = new CalendarItemViewModel(calendarItem);
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
calendarDay.EventsCollection.AddCalendarItem(calendarItemViewModel);
|
||||
});
|
||||
}
|
||||
|
||||
FilterActiveCalendars(DayRanges);
|
||||
}
|
||||
|
||||
private async Task InitializeCalendarEventsForDayRangeAsync(DayRangeRenderModel dayRangeRenderModel)
|
||||
{
|
||||
// Clear all events first for all days.
|
||||
foreach (var day in dayRangeRenderModel.CalendarDays)
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
day.EventsCollection.Clear();
|
||||
});
|
||||
}
|
||||
|
||||
// Initialization is done for all calendars, regardless whether they are actively selected or not.
|
||||
// This is because the filtering is cached internally of the calendar items in CalendarEventCollection.
|
||||
var allCalendars = AccountCalendarStateService.GroupedAccountCalendars.SelectMany(a => a.AccountCalendars);
|
||||
|
||||
foreach (var calendarViewModel in allCalendars)
|
||||
{
|
||||
// Check all the events for the given date range and calendar.
|
||||
// Then find the day representation for all the events returned, and add to the collection.
|
||||
|
||||
var events = await _calendarService.GetCalendarEventsAsync(calendarViewModel, dayRangeRenderModel).ConfigureAwait(false);
|
||||
|
||||
foreach (var @event in events)
|
||||
{
|
||||
// Find the days that the event falls into.
|
||||
var allDaysForEvent = dayRangeRenderModel.CalendarDays.Where(a => a.Period.OverlapsWith(@event.Period));
|
||||
|
||||
foreach (var calendarDay in allDaysForEvent)
|
||||
{
|
||||
var calendarItemViewModel = new CalendarItemViewModel(@event);
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
calendarDay.EventsCollection.AddCalendarItem(calendarItemViewModel);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TryConsolidateItemsAsync()
|
||||
{
|
||||
// Check if trimming is necessary
|
||||
if (DayRanges.Count > maxDayRangeSize)
|
||||
{
|
||||
Debug.WriteLine("Trimming items.");
|
||||
|
||||
isLoadMoreBlocked = true;
|
||||
|
||||
var removeCount = DayRanges.Count - maxDayRangeSize;
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
// Right shifted, remove from the start.
|
||||
if (SelectedDateRangeIndex > DayRanges.Count / 2)
|
||||
{
|
||||
DayRanges.RemoveRange(DayRanges.Take(removeCount).ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Left shifted, remove from the end.
|
||||
DayRanges.RemoveRange(DayRanges.Skip(DayRanges.Count - removeCount).Take(removeCount));
|
||||
}
|
||||
|
||||
SelectedDateRangeIndex = DayRanges.IndexOf(SelectedDayRange);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldScrollToItem(LoadCalendarMessage message)
|
||||
{
|
||||
// Never scroll if the initiative is from the app.
|
||||
if (message.CalendarInitInitiative == CalendarInitInitiative.App) return false;
|
||||
|
||||
// Nothing to scroll.
|
||||
if (DayRanges.Count == 0) return false;
|
||||
|
||||
if (DayRanges.DisplayRange == null) return false;
|
||||
|
||||
var selectedDate = message.DisplayDate;
|
||||
|
||||
return selectedDate >= DayRanges.DisplayRange.StartDate && selectedDate <= DayRanges.DisplayRange.EndDate;
|
||||
}
|
||||
|
||||
partial void OnIsAllDayChanged(bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
SelectedStartTimeString = HourSelectionStrings.FirstOrDefault();
|
||||
SelectedEndTimeString = HourSelectionStrings.FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedStartTimeString = _previousSelectedStartTimeString;
|
||||
SelectedEndTimeString = _previousSelectedEndTimeString;
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedStartTimeStringChanged(string newValue)
|
||||
{
|
||||
var parsedTime = CurrentSettings.GetTimeSpan(newValue);
|
||||
|
||||
if (parsedTime == null)
|
||||
{
|
||||
SelectedStartTimeString = _previousSelectedStartTimeString;
|
||||
}
|
||||
else if (IsAllDay)
|
||||
{
|
||||
_previousSelectedStartTimeString = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedEndTimeStringChanged(string newValue)
|
||||
{
|
||||
var parsedTime = CurrentSettings.GetTimeSpan(newValue);
|
||||
|
||||
if (parsedTime == null)
|
||||
{
|
||||
SelectedEndTimeString = _previousSelectedStartTimeString;
|
||||
}
|
||||
else if (IsAllDay)
|
||||
{
|
||||
_previousSelectedEndTimeString = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedDayRangeChanged(DayRangeRenderModel value)
|
||||
{
|
||||
DisplayDetailsCalendarItemViewModel = null;
|
||||
|
||||
if (DayRanges.Count == 0 || SelectedDateRangeIndex < 0) return;
|
||||
|
||||
var selectedRange = DayRanges[SelectedDateRangeIndex];
|
||||
|
||||
Messenger.Send(new VisibleDateRangeChangedMessage(new DateRange(selectedRange.Period.Start, selectedRange.Period.End)));
|
||||
|
||||
if (isLoadMoreBlocked) return;
|
||||
|
||||
_ = LoadMoreAsync();
|
||||
}
|
||||
|
||||
private async Task LoadMoreAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _calendarLoadingSemaphore.WaitAsync();
|
||||
|
||||
// Depending on the selected index, we'll load more dates.
|
||||
// Day ranges may change while the async update is in progress.
|
||||
// Therefore we wait for semaphore to be released before we continue.
|
||||
// There is no need to load more if the current index is not in ideal position.
|
||||
|
||||
if (SelectedDateRangeIndex == 0)
|
||||
{
|
||||
await RenderDatesAsync(CalendarInitInitiative.App, calendarLoadDirection: CalendarLoadDirection.Previous);
|
||||
}
|
||||
else if (SelectedDateRangeIndex == DayRanges.Count - 1)
|
||||
{
|
||||
await RenderDatesAsync(CalendarInitInitiative.App, calendarLoadDirection: CalendarLoadDirection.Next);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Debugger.Break();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_calendarLoadingSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(CalendarSettingsUpdatedMessage message)
|
||||
{
|
||||
RefreshSettings();
|
||||
|
||||
// TODO: This might need throttling due to slider in the settings page for hour height.
|
||||
// or make sure the slider does not update on each tick but on focus lost.
|
||||
|
||||
// Messenger.Send(new LoadCalendarMessage(DateTime.UtcNow.Date, CalendarInitInitiative.App, true));
|
||||
}
|
||||
|
||||
private IEnumerable<CalendarItemViewModel> GetCalendarItems(CalendarItemViewModel calendarItemViewModel, CalendarDayModel selectedDay)
|
||||
{
|
||||
// All-day and multi-day events are selected collectively.
|
||||
// Recurring events must be selected as a single instance.
|
||||
// We need to find the day that the event is in, and then select the event.
|
||||
|
||||
if (!calendarItemViewModel.IsRecurringEvent)
|
||||
{
|
||||
return [calendarItemViewModel];
|
||||
}
|
||||
else
|
||||
{
|
||||
return DayRanges
|
||||
.SelectMany(a => a.CalendarDays)
|
||||
.Select(b => b.EventsCollection.GetCalendarItem(calendarItemViewModel.Id))
|
||||
.Where(c => c != null)
|
||||
.Cast<CalendarItemViewModel>()
|
||||
.Distinct();
|
||||
}
|
||||
}
|
||||
|
||||
private void UnselectCalendarItem(CalendarItemViewModel calendarItemViewModel, CalendarDayModel calendarDay = null)
|
||||
{
|
||||
if (calendarItemViewModel == null) return;
|
||||
|
||||
var itemsToUnselect = GetCalendarItems(calendarItemViewModel, calendarDay);
|
||||
|
||||
foreach (var item in itemsToUnselect)
|
||||
{
|
||||
item.IsSelected = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectCalendarItem(CalendarItemViewModel calendarItemViewModel, CalendarDayModel calendarDay = null)
|
||||
{
|
||||
if (calendarItemViewModel == null) return;
|
||||
|
||||
var itemsToSelect = GetCalendarItems(calendarItemViewModel, calendarDay);
|
||||
|
||||
foreach (var item in itemsToSelect)
|
||||
{
|
||||
item.IsSelected = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(CalendarItemTappedMessage message)
|
||||
{
|
||||
if (message.CalendarItemViewModel == null) return;
|
||||
|
||||
DisplayDetailsCalendarItemViewModel = message.CalendarItemViewModel;
|
||||
}
|
||||
|
||||
public void Receive(CalendarItemDoubleTappedMessage message) => NavigateEvent(message.CalendarItemViewModel, CalendarEventTargetType.Single);
|
||||
|
||||
public void Receive(CalendarItemRightTappedMessage message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public async void Receive(CalendarItemDeleted message)
|
||||
{
|
||||
// Each deleted recurrence will report for it's own.
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
var deletedItem = message.CalendarItem;
|
||||
|
||||
// Event might be spreaded into multiple days.
|
||||
// Remove from all.
|
||||
|
||||
// var calendarItems = GetCalendarItems(deletedItem.Id);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
127
Wino.Calendar.ViewModels/CalendarSettingsPageViewModel.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Translations;
|
||||
using Wino.Core.ViewModels;
|
||||
using Wino.Messaging.Client.Calendar;
|
||||
|
||||
namespace Wino.Calendar.ViewModels
|
||||
{
|
||||
public partial class CalendarSettingsPageViewModel : CalendarBaseViewModel
|
||||
{
|
||||
[ObservableProperty]
|
||||
private double _cellHourHeight;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _selectedFirstDayOfWeekIndex;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _is24HourHeaders;
|
||||
|
||||
[ObservableProperty]
|
||||
private TimeSpan _workingHourStart;
|
||||
|
||||
[ObservableProperty]
|
||||
private TimeSpan _workingHourEnd;
|
||||
|
||||
[ObservableProperty]
|
||||
private List<string> _dayNames = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private int _workingDayStartIndex;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _workingDayEndIndex;
|
||||
|
||||
public IPreferencesService PreferencesService { get; }
|
||||
|
||||
private readonly bool _isLoaded = false;
|
||||
|
||||
public CalendarSettingsPageViewModel(IPreferencesService preferencesService)
|
||||
{
|
||||
PreferencesService = preferencesService;
|
||||
|
||||
var currentLanguageLanguageCode = WinoTranslationDictionary.GetLanguageFileNameRelativePath(preferencesService.CurrentLanguage);
|
||||
|
||||
var cultureInfo = new CultureInfo(currentLanguageLanguageCode);
|
||||
|
||||
// Populate the day names list
|
||||
for (var i = 0; i < 7; i++)
|
||||
{
|
||||
_dayNames.Add(cultureInfo.DateTimeFormat.DayNames[i]);
|
||||
}
|
||||
|
||||
var cultureFirstDayName = cultureInfo.DateTimeFormat.GetDayName(preferencesService.FirstDayOfWeek);
|
||||
|
||||
_selectedFirstDayOfWeekIndex = _dayNames.IndexOf(cultureFirstDayName);
|
||||
_is24HourHeaders = preferencesService.Prefer24HourTimeFormat;
|
||||
_workingHourStart = preferencesService.WorkingHourStart;
|
||||
_workingHourEnd = preferencesService.WorkingHourEnd;
|
||||
_cellHourHeight = preferencesService.HourHeight;
|
||||
|
||||
_workingDayStartIndex = _dayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayStart));
|
||||
_workingDayEndIndex = _dayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayEnd));
|
||||
|
||||
_isLoaded = true;
|
||||
}
|
||||
|
||||
partial void OnCellHourHeightChanged(double oldValue, double newValue) => SaveSettings();
|
||||
partial void OnIs24HourHeadersChanged(bool value) => SaveSettings();
|
||||
partial void OnSelectedFirstDayOfWeekIndexChanged(int value) => SaveSettings();
|
||||
partial void OnWorkingHourStartChanged(TimeSpan value) => SaveSettings();
|
||||
partial void OnWorkingHourEndChanged(TimeSpan value) => SaveSettings();
|
||||
partial void OnWorkingDayStartIndexChanged(int value) => SaveSettings();
|
||||
partial void OnWorkingDayEndIndexChanged(int value) => SaveSettings();
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
if (!_isLoaded) return;
|
||||
|
||||
PreferencesService.FirstDayOfWeek = SelectedFirstDayOfWeekIndex switch
|
||||
{
|
||||
0 => DayOfWeek.Sunday,
|
||||
1 => DayOfWeek.Monday,
|
||||
2 => DayOfWeek.Tuesday,
|
||||
3 => DayOfWeek.Wednesday,
|
||||
4 => DayOfWeek.Thursday,
|
||||
5 => DayOfWeek.Friday,
|
||||
6 => DayOfWeek.Saturday,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
PreferencesService.WorkingDayStart = WorkingDayStartIndex switch
|
||||
{
|
||||
0 => DayOfWeek.Sunday,
|
||||
1 => DayOfWeek.Monday,
|
||||
2 => DayOfWeek.Tuesday,
|
||||
3 => DayOfWeek.Wednesday,
|
||||
4 => DayOfWeek.Thursday,
|
||||
5 => DayOfWeek.Friday,
|
||||
6 => DayOfWeek.Saturday,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
PreferencesService.WorkingDayEnd = WorkingDayEndIndex switch
|
||||
{
|
||||
0 => DayOfWeek.Sunday,
|
||||
1 => DayOfWeek.Monday,
|
||||
2 => DayOfWeek.Tuesday,
|
||||
3 => DayOfWeek.Wednesday,
|
||||
4 => DayOfWeek.Thursday,
|
||||
5 => DayOfWeek.Friday,
|
||||
6 => DayOfWeek.Saturday,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
PreferencesService.Prefer24HourTimeFormat = Is24HourHeaders;
|
||||
PreferencesService.WorkingHourStart = WorkingHourStart;
|
||||
PreferencesService.WorkingHourEnd = WorkingHourEnd;
|
||||
PreferencesService.HourHeight = CellHourHeight;
|
||||
|
||||
Messenger.Send(new CalendarSettingsUpdatedMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Wino.Calendar.ViewModels/CalendarViewModelContainerSetup.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Wino.Core;
|
||||
|
||||
namespace Wino.Calendar.ViewModels
|
||||
{
|
||||
public static class CalendarViewModelContainerSetup
|
||||
{
|
||||
public static void RegisterCalendarViewModelServices(this IServiceCollection services)
|
||||
{
|
||||
services.RegisterCoreServices();
|
||||
}
|
||||
}
|
||||
}
|
||||
70
Wino.Calendar.ViewModels/Data/AccountCalendarViewModel.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Calendar.ViewModels.Data
|
||||
{
|
||||
public partial class AccountCalendarViewModel : ObservableObject, IAccountCalendar
|
||||
{
|
||||
public MailAccount Account { get; }
|
||||
public AccountCalendar AccountCalendar { get; }
|
||||
|
||||
public AccountCalendarViewModel(MailAccount account, AccountCalendar accountCalendar)
|
||||
{
|
||||
Account = account;
|
||||
AccountCalendar = accountCalendar;
|
||||
|
||||
IsChecked = accountCalendar.IsExtended;
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isChecked;
|
||||
|
||||
partial void OnIsCheckedChanged(bool value) => IsExtended = value;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => AccountCalendar.Name;
|
||||
set => SetProperty(AccountCalendar.Name, value, AccountCalendar, (u, n) => u.Name = n);
|
||||
}
|
||||
|
||||
public string TextColorHex
|
||||
{
|
||||
get => AccountCalendar.TextColorHex;
|
||||
set => SetProperty(AccountCalendar.TextColorHex, value, AccountCalendar, (u, t) => u.TextColorHex = t);
|
||||
}
|
||||
|
||||
public string BackgroundColorHex
|
||||
{
|
||||
get => AccountCalendar.BackgroundColorHex;
|
||||
set => SetProperty(AccountCalendar.BackgroundColorHex, value, AccountCalendar, (u, b) => u.BackgroundColorHex = b);
|
||||
}
|
||||
|
||||
public bool IsExtended
|
||||
{
|
||||
get => AccountCalendar.IsExtended;
|
||||
set => SetProperty(AccountCalendar.IsExtended, value, AccountCalendar, (u, i) => u.IsExtended = i);
|
||||
}
|
||||
|
||||
public bool IsPrimary
|
||||
{
|
||||
get => AccountCalendar.IsPrimary;
|
||||
set => SetProperty(AccountCalendar.IsPrimary, value, AccountCalendar, (u, i) => u.IsPrimary = i);
|
||||
}
|
||||
|
||||
public Guid AccountId
|
||||
{
|
||||
get => AccountCalendar.AccountId;
|
||||
set => SetProperty(AccountCalendar.AccountId, value, AccountCalendar, (u, a) => u.AccountId = a);
|
||||
}
|
||||
|
||||
public string RemoteCalendarId
|
||||
{
|
||||
get => AccountCalendar.RemoteCalendarId;
|
||||
set => SetProperty(AccountCalendar.RemoteCalendarId, value, AccountCalendar, (u, r) => u.RemoteCalendarId = r);
|
||||
}
|
||||
public Guid Id { get => ((IAccountCalendar)AccountCalendar).Id; set => ((IAccountCalendar)AccountCalendar).Id = value; }
|
||||
}
|
||||
}
|
||||
46
Wino.Calendar.ViewModels/Data/CalendarItemViewModel.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Itenso.TimePeriod;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Calendar.ViewModels.Data
|
||||
{
|
||||
public partial class CalendarItemViewModel : ObservableObject, ICalendarItem, ICalendarItemViewModel
|
||||
{
|
||||
public CalendarItem CalendarItem { get; }
|
||||
|
||||
public string Title => CalendarItem.Title;
|
||||
|
||||
public Guid Id => CalendarItem.Id;
|
||||
|
||||
public IAccountCalendar AssignedCalendar => CalendarItem.AssignedCalendar;
|
||||
|
||||
public DateTime StartDate { get => CalendarItem.StartDate; set => CalendarItem.StartDate = value; }
|
||||
|
||||
public DateTime EndDate => CalendarItem.EndDate;
|
||||
|
||||
public double DurationInSeconds { get => CalendarItem.DurationInSeconds; set => CalendarItem.DurationInSeconds = value; }
|
||||
|
||||
public ITimePeriod Period => CalendarItem.Period;
|
||||
|
||||
public bool IsAllDayEvent => CalendarItem.IsAllDayEvent;
|
||||
public bool IsMultiDayEvent => CalendarItem.IsMultiDayEvent;
|
||||
public bool IsRecurringEvent => CalendarItem.IsRecurringEvent;
|
||||
public bool IsRecurringChild => CalendarItem.IsRecurringChild;
|
||||
public bool IsRecurringParent => CalendarItem.IsRecurringParent;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isSelected;
|
||||
|
||||
public ObservableCollection<CalendarEventAttendee> Attendees { get; } = new ObservableCollection<CalendarEventAttendee>();
|
||||
|
||||
public CalendarItemViewModel(CalendarItem calendarItem)
|
||||
{
|
||||
CalendarItem = calendarItem;
|
||||
}
|
||||
|
||||
public override string ToString() => CalendarItem.Title;
|
||||
}
|
||||
}
|
||||
146
Wino.Calendar.ViewModels/Data/GroupedAccountCalendarViewModel.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
namespace Wino.Calendar.ViewModels.Data
|
||||
{
|
||||
public partial class GroupedAccountCalendarViewModel : ObservableObject
|
||||
{
|
||||
public event EventHandler CollectiveSelectionStateChanged;
|
||||
public event EventHandler<AccountCalendarViewModel> CalendarSelectionStateChanged;
|
||||
|
||||
public MailAccount Account { get; }
|
||||
public ObservableCollection<AccountCalendarViewModel> AccountCalendars { get; }
|
||||
|
||||
public GroupedAccountCalendarViewModel(MailAccount account, IEnumerable<AccountCalendarViewModel> calendarViewModels)
|
||||
{
|
||||
Account = account;
|
||||
AccountCalendars = new ObservableCollection<AccountCalendarViewModel>(calendarViewModels);
|
||||
|
||||
ManageIsCheckedState();
|
||||
|
||||
foreach (var calendarViewModel in calendarViewModels)
|
||||
{
|
||||
calendarViewModel.PropertyChanged += CalendarPropertyChanged;
|
||||
}
|
||||
|
||||
AccountCalendars.CollectionChanged += CalendarListUpdated;
|
||||
}
|
||||
|
||||
private void CalendarListUpdated(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.Action == NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
foreach (AccountCalendarViewModel calendar in e.NewItems)
|
||||
{
|
||||
calendar.PropertyChanged += CalendarPropertyChanged;
|
||||
}
|
||||
}
|
||||
else if (e.Action == NotifyCollectionChangedAction.Remove)
|
||||
{
|
||||
foreach (AccountCalendarViewModel calendar in e.OldItems)
|
||||
{
|
||||
calendar.PropertyChanged -= CalendarPropertyChanged;
|
||||
}
|
||||
}
|
||||
else if (e.Action == NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
foreach (AccountCalendarViewModel calendar in e.OldItems)
|
||||
{
|
||||
calendar.PropertyChanged -= CalendarPropertyChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CalendarPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (sender is AccountCalendarViewModel viewModel)
|
||||
{
|
||||
if (e.PropertyName == nameof(AccountCalendarViewModel.IsChecked))
|
||||
{
|
||||
ManageIsCheckedState();
|
||||
UpdateCalendarCheckedState(viewModel, viewModel.IsChecked, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isExpanded = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool? isCheckedState = true;
|
||||
|
||||
private bool _isExternalPropChangeBlocked = false;
|
||||
|
||||
private void ManageIsCheckedState()
|
||||
{
|
||||
if (_isExternalPropChangeBlocked) return;
|
||||
|
||||
_isExternalPropChangeBlocked = true;
|
||||
|
||||
if (AccountCalendars.All(c => c.IsChecked))
|
||||
{
|
||||
IsCheckedState = true;
|
||||
}
|
||||
else if (AccountCalendars.All(c => !c.IsChecked))
|
||||
{
|
||||
IsCheckedState = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsCheckedState = null;
|
||||
}
|
||||
|
||||
_isExternalPropChangeBlocked = false;
|
||||
}
|
||||
|
||||
partial void OnIsCheckedStateChanged(bool? newValue)
|
||||
{
|
||||
if (_isExternalPropChangeBlocked) return;
|
||||
|
||||
// Update is triggered by user on the three-state checkbox.
|
||||
// We should not report all changes one by one.
|
||||
|
||||
_isExternalPropChangeBlocked = true;
|
||||
|
||||
if (newValue == null)
|
||||
{
|
||||
// Only primary calendars must be checked.
|
||||
|
||||
foreach (var calendar in AccountCalendars)
|
||||
{
|
||||
UpdateCalendarCheckedState(calendar, calendar.IsPrimary);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var calendar in AccountCalendars)
|
||||
{
|
||||
UpdateCalendarCheckedState(calendar, newValue.GetValueOrDefault());
|
||||
}
|
||||
}
|
||||
|
||||
_isExternalPropChangeBlocked = false;
|
||||
|
||||
CollectiveSelectionStateChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void UpdateCalendarCheckedState(AccountCalendarViewModel accountCalendarViewModel, bool newValue, bool ignoreValueCheck = false)
|
||||
{
|
||||
var currentValue = accountCalendarViewModel.IsChecked;
|
||||
|
||||
if (currentValue == newValue && !ignoreValueCheck) return;
|
||||
|
||||
accountCalendarViewModel.IsChecked = newValue;
|
||||
|
||||
// No need to report.
|
||||
if (_isExternalPropChangeBlocked == true) return;
|
||||
|
||||
CalendarSelectionStateChanged?.Invoke(this, accountCalendarViewModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
116
Wino.Calendar.ViewModels/EventDetailsPageViewModel.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.ViewModels;
|
||||
using Wino.Messaging.Client.Calendar;
|
||||
|
||||
namespace Wino.Calendar.ViewModels
|
||||
{
|
||||
public partial class EventDetailsPageViewModel : CalendarBaseViewModel
|
||||
{
|
||||
private readonly ICalendarService _calendarService;
|
||||
private readonly INativeAppService _nativeAppService;
|
||||
private readonly IPreferencesService _preferencesService;
|
||||
|
||||
public CalendarSettings CurrentSettings { get; }
|
||||
|
||||
#region Details
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(CanViewSeries))]
|
||||
private CalendarItemViewModel _currentEvent;
|
||||
|
||||
[ObservableProperty]
|
||||
private CalendarItemViewModel _seriesParent;
|
||||
|
||||
public bool CanViewSeries => CurrentEvent?.IsRecurringChild ?? false;
|
||||
|
||||
#endregion
|
||||
|
||||
public EventDetailsPageViewModel(ICalendarService calendarService, INativeAppService nativeAppService, IPreferencesService preferencesService)
|
||||
{
|
||||
_calendarService = calendarService;
|
||||
_nativeAppService = nativeAppService;
|
||||
_preferencesService = preferencesService;
|
||||
|
||||
CurrentSettings = _preferencesService.GetCurrentCalendarSettings();
|
||||
}
|
||||
|
||||
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedTo(mode, parameters);
|
||||
|
||||
Messenger.Send(new DetailsPageStateChangedMessage(true));
|
||||
|
||||
if (parameters == null || parameters is not CalendarItemTarget args)
|
||||
return;
|
||||
|
||||
await LoadCalendarItemTargetAsync(args);
|
||||
}
|
||||
|
||||
private async Task LoadCalendarItemTargetAsync(CalendarItemTarget target)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentEventItem = await _calendarService.GetCalendarItemTargetAsync(target);
|
||||
|
||||
if (currentEventItem == null)
|
||||
return;
|
||||
|
||||
CurrentEvent = new CalendarItemViewModel(currentEventItem);
|
||||
|
||||
var attendees = await _calendarService.GetAttendeesAsync(currentEventItem.EventTrackingId);
|
||||
|
||||
foreach (var item in attendees)
|
||||
{
|
||||
CurrentEvent.Attendees.Add(item);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedFrom(mode, parameters);
|
||||
|
||||
Messenger.Send(new DetailsPageStateChangedMessage(false));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task DeleteAsync()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private Task JoinOnline()
|
||||
{
|
||||
if (CurrentEvent == null || string.IsNullOrEmpty(CurrentEvent.CalendarItem.HtmlLink)) return Task.CompletedTask;
|
||||
|
||||
return _nativeAppService.LaunchUriAsync(new Uri(CurrentEvent.CalendarItem.HtmlLink));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task Respond(CalendarItemStatus status)
|
||||
{
|
||||
if (CurrentEvent == null) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
namespace Wino.Calendar.ViewModels.Interfaces
|
||||
{
|
||||
public interface IAccountCalendarStateService : INotifyPropertyChanged
|
||||
{
|
||||
ReadOnlyObservableCollection<GroupedAccountCalendarViewModel> GroupedAccountCalendars { get; }
|
||||
|
||||
event EventHandler<GroupedAccountCalendarViewModel> CollectiveAccountGroupSelectionStateChanged;
|
||||
event EventHandler<AccountCalendarViewModel> AccountCalendarSelectionStateChanged;
|
||||
|
||||
public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar);
|
||||
public void RemoveGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar);
|
||||
public void ClearGroupedAccountCalendar();
|
||||
|
||||
public void AddAccountCalendar(AccountCalendarViewModel accountCalendar);
|
||||
public void RemoveAccountCalendar(AccountCalendarViewModel accountCalendar);
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration of currently selected calendars.
|
||||
/// </summary>
|
||||
IEnumerable<AccountCalendarViewModel> ActiveCalendars { get; }
|
||||
IEnumerable<IGrouping<MailAccount, AccountCalendarViewModel>> GroupedAccountCalendarsEnumerable { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
|
||||
namespace Wino.Calendar.ViewModels.Messages
|
||||
{
|
||||
public class CalendarItemDoubleTappedMessage
|
||||
{
|
||||
public CalendarItemDoubleTappedMessage(CalendarItemViewModel calendarItemViewModel)
|
||||
{
|
||||
CalendarItemViewModel = calendarItemViewModel;
|
||||
}
|
||||
|
||||
public CalendarItemViewModel CalendarItemViewModel { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
|
||||
namespace Wino.Calendar.ViewModels.Messages
|
||||
{
|
||||
public class CalendarItemRightTappedMessage
|
||||
{
|
||||
public CalendarItemRightTappedMessage(CalendarItemViewModel calendarItemViewModel)
|
||||
{
|
||||
CalendarItemViewModel = calendarItemViewModel;
|
||||
}
|
||||
|
||||
public CalendarItemViewModel CalendarItemViewModel { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Calendar.ViewModels.Messages
|
||||
{
|
||||
public class CalendarItemTappedMessage
|
||||
{
|
||||
public CalendarItemTappedMessage(CalendarItemViewModel calendarItemViewModel, CalendarDayModel clickedPeriod)
|
||||
{
|
||||
CalendarItemViewModel = calendarItemViewModel;
|
||||
ClickedPeriod = clickedPeriod;
|
||||
}
|
||||
|
||||
public CalendarItemViewModel CalendarItemViewModel { get; }
|
||||
public CalendarDayModel ClickedPeriod { get; }
|
||||
}
|
||||
}
|
||||
23
Wino.Calendar.ViewModels/Wino.Calendar.ViewModels.csproj
Normal file
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Platforms>AnyCPU;x64;x86</Platforms>
|
||||
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="TimePeriodLibrary.NET" Version="2.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj" />
|
||||
<ProjectReference Include="..\Wino.Core.ViewModels\Wino.Core.ViewModels.csproj" />
|
||||
<ProjectReference Include="..\Wino.Core\Wino.Core.csproj" />
|
||||
<ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj" />
|
||||
<ProjectReference Include="..\Wino.Services\Wino.Services.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
308
Wino.Calendar.sln
Normal file
@@ -0,0 +1,308 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.12.35424.110
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Core.Domain", "Wino.Core.Domain\Wino.Core.Domain.csproj", "{814400B6-5A05-4596-B451-3A116A147DC1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Core.UWP", "Wino.Core.UWP\Wino.Core.UWP.csproj", "{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Core.ViewModels", "Wino.Core.ViewModels\Wino.Core.ViewModels.csproj", "{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Messaging", "Wino.Messages\Wino.Messaging.csproj", "{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Server", "Wino.Server\Wino.Server.csproj", "{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Core", "Wino.Core\Wino.Core.csproj", "{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Calendar", "Wino.Calendar\Wino.Calendar.csproj", "{600F4979-DB7E-409D-B7DA-B60BE4C55C35}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.SourceGenerators", "Wino.SourceGenerators\Wino.SourceGenerators.csproj", "{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}"
|
||||
EndProject
|
||||
Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "Wino.Calendar.Packaging", "Wino.Calendar.Packaging\Wino.Calendar.Packaging.wapproj", "{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Calendar.ViewModels", "Wino.Calendar.ViewModels\Wino.Calendar.ViewModels.csproj", "{CF850F8C-5042-4376-9CBA-C8F2BB554083}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Services", "Wino.Services\Wino.Services.csproj", "{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Authentication", "Wino.Authentication\Wino.Authentication.csproj", "{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|ARM = Debug|ARM
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|ARM = Release|ARM
|
||||
Release|ARM64 = Release|ARM64
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|x64.Build.0 = Debug|x64
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|x86.Build.0 = Debug|x86
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|x64.ActiveCfg = Release|x64
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|x64.Build.0 = Release|x64
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|x86.ActiveCfg = Release|x86
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|x86.Build.0 = Release|x86
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|x64.Build.0 = Debug|x64
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|x86.Build.0 = Debug|x86
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|x64.ActiveCfg = Release|x64
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|x64.Build.0 = Release|x64
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|x86.ActiveCfg = Release|x86
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|x86.Build.0 = Release|x86
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|x64.Build.0 = Debug|x64
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|x86.Build.0 = Debug|x86
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|x64.ActiveCfg = Release|x64
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|x64.Build.0 = Release|x64
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|x86.ActiveCfg = Release|x86
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|x86.Build.0 = Release|x86
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|x64.Build.0 = Debug|x64
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|x86.Build.0 = Debug|x86
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|x64.ActiveCfg = Release|x64
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|x64.Build.0 = Release|x64
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|x86.ActiveCfg = Release|x86
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|x86.Build.0 = Release|x86
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|ARM.ActiveCfg = Debug|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|ARM.Build.0 = Debug|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|x64.Build.0 = Debug|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|x86.Build.0 = Debug|x86
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|Any CPU.Build.0 = Release|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|ARM.ActiveCfg = Release|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|ARM.Build.0 = Release|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|x64.ActiveCfg = Release|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|x64.Build.0 = Release|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|x86.ActiveCfg = Release|x86
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|x86.Build.0 = Release|x86
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|x64.Build.0 = Debug|x64
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|x86.Build.0 = Debug|x86
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|x64.ActiveCfg = Release|x64
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|x64.Build.0 = Release|x64
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|x86.ActiveCfg = Release|x86
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|x86.Build.0 = Release|x86
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|Any CPU.Deploy.0 = Debug|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM.ActiveCfg = Debug|ARM
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM.Build.0 = Debug|ARM
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM.Deploy.0 = Debug|ARM
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x64.Build.0 = Debug|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x86.Build.0 = Debug|x86
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x86.Deploy.0 = Debug|x86
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|Any CPU.Build.0 = Release|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|Any CPU.Deploy.0 = Release|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM.ActiveCfg = Release|ARM
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM.Build.0 = Release|ARM
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM.Deploy.0 = Release|ARM
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x64.ActiveCfg = Release|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x64.Build.0 = Release|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x64.Deploy.0 = Release|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x86.ActiveCfg = Release|x86
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x86.Build.0 = Release|x86
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x86.Deploy.0 = Release|x86
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|x64.Build.0 = Release|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|x86.Build.0 = Release|Any CPU
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM.ActiveCfg = Debug|ARM
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM.Build.0 = Debug|ARM
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM.Deploy.0 = Debug|ARM
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x64.Build.0 = Debug|x64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x86.Build.0 = Debug|x86
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x86.Deploy.0 = Debug|x86
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM.ActiveCfg = Release|ARM
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM.Build.0 = Release|ARM
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM.Deploy.0 = Release|ARM
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x64.ActiveCfg = Release|x64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x64.Build.0 = Release|x64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x64.Deploy.0 = Release|x64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x86.ActiveCfg = Release|x86
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x86.Build.0 = Release|x86
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x86.Deploy.0 = Release|x86
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|x64.Build.0 = Debug|x64
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|x86.Build.0 = Debug|x86
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|x64.ActiveCfg = Release|x64
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|x64.Build.0 = Release|x64
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|x86.ActiveCfg = Release|x86
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|x86.Build.0 = Release|x86
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|x64.Build.0 = Release|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|x86.Build.0 = Release|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|x64.Build.0 = Debug|x64
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|x86.Build.0 = Debug|x86
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|x64.ActiveCfg = Release|x64
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|x64.Build.0 = Release|x64
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|x86.ActiveCfg = Release|x86
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
24
Wino.Calendar/Activation/DefaultActivationHandler.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Media.Animation;
|
||||
using Wino.Activation;
|
||||
using Wino.Calendar.Views;
|
||||
|
||||
namespace Wino.Calendar.Activation
|
||||
{
|
||||
public class DefaultActivationHandler : ActivationHandler<IActivatedEventArgs>
|
||||
{
|
||||
protected override Task HandleInternalAsync(IActivatedEventArgs args)
|
||||
{
|
||||
(Window.Current.Content as Frame).Navigate(typeof(AppShell), null, new DrillInNavigationTransitionInfo());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Only navigate if Frame content doesn't exist.
|
||||
protected override bool CanHandleInternal(IActivatedEventArgs args)
|
||||
=> (Window.Current?.Content as Frame)?.Content == null;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,31 @@
|
||||
<Application
|
||||
<core:WinoApplication
|
||||
x:Class="Wino.Calendar.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Wino.Calendar">
|
||||
xmlns:controls="using:Microsoft.UI.Xaml.Controls"
|
||||
xmlns:core="using:Wino.Core.UWP"
|
||||
xmlns:coreStyles="using:Wino.Core.UWP.Styles"
|
||||
xmlns:local="using:Wino.Calendar"
|
||||
xmlns:styles="using:Wino.Calendar.Styles">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
|
||||
<core:CoreGeneric />
|
||||
|
||||
<styles:WinoCalendarResources />
|
||||
|
||||
<ResourceDictionary Source="Styles/CalendarThemeResources.xaml" />
|
||||
|
||||
<ResourceDictionary Source="Styles/WinoDayTimelineCanvas.xaml" />
|
||||
<ResourceDictionary Source="Styles/WinoCalendarView.xaml" />
|
||||
<ResourceDictionary Source="Styles/WinoCalendarTypeSelectorControl.xaml" />
|
||||
|
||||
<!-- Last item must always be the default theme. -->
|
||||
<ResourceDictionary Source="ms-appx:///Wino.Core.UWP/AppThemes/Mica.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</core:WinoApplication>
|
||||
|
||||
</Application>
|
||||
|
||||
@@ -1,100 +1,164 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serilog;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Media;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using Windows.ApplicationModel.AppService;
|
||||
using Windows.ApplicationModel.Background;
|
||||
using Windows.UI.Core.Preview;
|
||||
using Wino.Activation;
|
||||
using Wino.Calendar.Activation;
|
||||
using Wino.Calendar.Services;
|
||||
using Wino.Calendar.ViewModels;
|
||||
using Wino.Calendar.ViewModels.Interfaces;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.UWP;
|
||||
using Wino.Core.ViewModels;
|
||||
using Wino.Messaging.Client.Connection;
|
||||
using Wino.Messaging.Server;
|
||||
using Wino.Services;
|
||||
|
||||
namespace Wino.Calendar
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
sealed partial class App : Application
|
||||
public sealed partial class App : WinoApplication, IRecipient<NewCalendarSynchronizationRequested>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public override string AppCenterKey => "dfdad6ab-95f9-44cc-9112-45ec6730c49e";
|
||||
|
||||
private BackgroundTaskDeferral connectionBackgroundTaskDeferral;
|
||||
private BackgroundTaskDeferral toastActionBackgroundTaskDeferral;
|
||||
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.Suspending += OnSuspending;
|
||||
InitializeComponent();
|
||||
|
||||
WeakReferenceMessenger.Default.Register(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the application is launched normally by the end user. Other entry points
|
||||
/// will be used such as when the application is launched to open a specific file.
|
||||
/// </summary>
|
||||
/// <param name="e">Details about the launch request and process.</param>
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs e)
|
||||
public override IServiceProvider ConfigureServices()
|
||||
{
|
||||
Frame rootFrame = Window.Current.Content as Frame;
|
||||
var services = new ServiceCollection();
|
||||
|
||||
// Do not repeat app initialization when the Window already has content,
|
||||
// just ensure that the window is active
|
||||
if (rootFrame == null)
|
||||
services.RegisterSharedServices();
|
||||
services.RegisterCalendarViewModelServices();
|
||||
services.RegisterCoreUWPServices();
|
||||
services.RegisterCoreViewModels();
|
||||
|
||||
RegisterUWPServices(services);
|
||||
RegisterViewModels(services);
|
||||
RegisterActivationHandlers(services);
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
#region Dependency Injection
|
||||
|
||||
private void RegisterActivationHandlers(IServiceCollection services)
|
||||
{
|
||||
//services.AddTransient<ProtocolActivationHandler>();
|
||||
//services.AddTransient<ToastNotificationActivationHandler>();
|
||||
//services.AddTransient<FileActivationHandler>();
|
||||
}
|
||||
|
||||
private void RegisterUWPServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<INavigationService, NavigationService>();
|
||||
services.AddSingleton<ICalendarDialogService, DialogService>();
|
||||
services.AddTransient<ISettingsBuilderService, SettingsBuilderService>();
|
||||
services.AddTransient<IProviderService, ProviderService>();
|
||||
services.AddSingleton<IAuthenticatorConfig, CalendarAuthenticatorConfig>();
|
||||
services.AddSingleton<IAccountCalendarStateService, AccountCalendarStateService>();
|
||||
}
|
||||
|
||||
private void RegisterViewModels(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton(typeof(AppShellViewModel));
|
||||
services.AddSingleton(typeof(CalendarPageViewModel));
|
||||
services.AddTransient(typeof(CalendarSettingsPageViewModel));
|
||||
services.AddTransient(typeof(AccountManagementViewModel));
|
||||
services.AddTransient(typeof(PersonalizationPageViewModel));
|
||||
services.AddTransient(typeof(AccountDetailsPageViewModel));
|
||||
services.AddTransient(typeof(EventDetailsPageViewModel));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void OnApplicationCloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e)
|
||||
{
|
||||
// TODO: Check server running.
|
||||
}
|
||||
|
||||
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
LogActivation($"OnLaunched -> {args.GetType().Name}, Kind -> {args.Kind}, PreviousExecutionState -> {args.PreviousExecutionState}, IsPrelaunch -> {args.PrelaunchActivated}");
|
||||
|
||||
if (!args.PrelaunchActivated)
|
||||
{
|
||||
// Create a Frame to act as the navigation context and navigate to the first page
|
||||
rootFrame = new Frame();
|
||||
|
||||
rootFrame.NavigationFailed += OnNavigationFailed;
|
||||
|
||||
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
|
||||
{
|
||||
//TODO: Load state from previously suspended application
|
||||
}
|
||||
|
||||
// Place the frame in the current Window
|
||||
Window.Current.Content = rootFrame;
|
||||
}
|
||||
|
||||
if (e.PrelaunchActivated == false)
|
||||
{
|
||||
if (rootFrame.Content == null)
|
||||
{
|
||||
// When the navigation stack isn't restored navigate to the first page,
|
||||
// configuring the new page by passing required information as a navigation
|
||||
// parameter
|
||||
rootFrame.Navigate(typeof(MainPage), e.Arguments);
|
||||
}
|
||||
// Ensure the current window is active
|
||||
Window.Current.Activate();
|
||||
await ActivateWinoAsync(args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when Navigation to a certain page fails
|
||||
/// </summary>
|
||||
/// <param name="sender">The Frame which failed navigation</param>
|
||||
/// <param name="e">Details about the navigation failure</param>
|
||||
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
|
||||
protected override IEnumerable<ActivationHandler> GetActivationHandlers()
|
||||
{
|
||||
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when application execution is being suspended. Application state is saved
|
||||
/// without knowing whether the application will be terminated or resumed with the contents
|
||||
/// of memory still intact.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the suspend request.</param>
|
||||
/// <param name="e">Details about the suspend request.</param>
|
||||
private void OnSuspending(object sender, SuspendingEventArgs e)
|
||||
protected override ActivationHandler<IActivatedEventArgs> GetDefaultActivationHandler()
|
||||
=> new DefaultActivationHandler();
|
||||
|
||||
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
|
||||
{
|
||||
var deferral = e.SuspendingOperation.GetDeferral();
|
||||
//TODO: Save application state and stop any background activity
|
||||
deferral.Complete();
|
||||
base.OnBackgroundActivated(args);
|
||||
|
||||
if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails appServiceTriggerDetails)
|
||||
{
|
||||
LogActivation("OnBackgroundActivated -> AppServiceTriggerDetails received.");
|
||||
|
||||
// Only accept connections from callers in the same package
|
||||
if (appServiceTriggerDetails.CallerPackageFamilyName == Package.Current.Id.FamilyName)
|
||||
{
|
||||
// Connection established from the fulltrust process
|
||||
|
||||
connectionBackgroundTaskDeferral = args.TaskInstance.GetDeferral();
|
||||
args.TaskInstance.Canceled += OnConnectionBackgroundTaskCanceled;
|
||||
|
||||
AppServiceConnectionManager.Connection = appServiceTriggerDetails.AppServiceConnection;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new WinoServerConnectionEstablished());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnConnectionBackgroundTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
|
||||
{
|
||||
sender.Canceled -= OnConnectionBackgroundTaskCanceled;
|
||||
|
||||
Log.Information($"Server connection background task was canceled. Reason: {reason}");
|
||||
|
||||
connectionBackgroundTaskDeferral?.Complete();
|
||||
connectionBackgroundTaskDeferral = null;
|
||||
|
||||
AppServiceConnectionManager.Connection = null;
|
||||
}
|
||||
|
||||
public async void Receive(NewCalendarSynchronizationRequested message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var synchronizationResultResponse = await AppServiceConnectionManager.GetResponseAsync<CalendarSynchronizationResult, NewCalendarSynchronizationRequested>(message);
|
||||
synchronizationResultResponse.ThrowIfFailed();
|
||||
}
|
||||
catch (WinoServerException serverException)
|
||||
{
|
||||
var dialogService = Services.GetService<ICalendarDialogService>();
|
||||
|
||||
dialogService.InfoBarMessage(Translator.Info_SyncFailedTitle, serverException.Message, InfoBarMessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
41
Wino.Calendar/Args/TimelineCellSelectedArgs.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Wino.Calendar.Args
|
||||
{
|
||||
/// <summary>
|
||||
/// When a new timeline cell is selected.
|
||||
/// </summary>
|
||||
public class TimelineCellSelectedArgs : EventArgs
|
||||
{
|
||||
public TimelineCellSelectedArgs(DateTime clickedDate, Point canvasPoint, Point positionerPoint, Size cellSize)
|
||||
{
|
||||
ClickedDate = clickedDate;
|
||||
CanvasPoint = canvasPoint;
|
||||
PositionerPoint = positionerPoint;
|
||||
CellSize = cellSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clicked date and time information for the cell.
|
||||
/// </summary>
|
||||
public DateTime ClickedDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Position relative to the cell drawing part of the canvas.
|
||||
/// Used to detect clicked cell from the position.
|
||||
/// </summary>
|
||||
public Point CanvasPoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Position relative to the main root positioner element of the drawing canvas.
|
||||
/// Used to show the create event dialog teaching tip in correct position.
|
||||
/// </summary>
|
||||
public Point PositionerPoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the cell.
|
||||
/// </summary>
|
||||
public Size CellSize { get; }
|
||||
}
|
||||
}
|
||||
9
Wino.Calendar/Args/TimelineCellUnselectedArgs.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Wino.Calendar.Args
|
||||
{
|
||||
/// <summary>
|
||||
/// When selected timeline cell is unselected.
|
||||
/// </summary>
|
||||
public class TimelineCellUnselectedArgs : EventArgs { }
|
||||
}
|
||||
BIN
Wino.Calendar/Assets/LargeTile.scale-100.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
Wino.Calendar/Assets/LargeTile.scale-125.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
Wino.Calendar/Assets/LargeTile.scale-150.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
Wino.Calendar/Assets/LargeTile.scale-200.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
Wino.Calendar/Assets/LargeTile.scale-400.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
Wino.Calendar/Assets/SmallTile.scale-100.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
Wino.Calendar/Assets/SmallTile.scale-125.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
Wino.Calendar/Assets/SmallTile.scale-150.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
Wino.Calendar/Assets/SmallTile.scale-200.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
Wino.Calendar/Assets/SmallTile.scale-400.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
Wino.Calendar/Assets/SplashScreen.scale-100.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
Wino.Calendar/Assets/SplashScreen.scale-125.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
Wino.Calendar/Assets/SplashScreen.scale-150.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 17 KiB |
BIN
Wino.Calendar/Assets/SplashScreen.scale-400.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
Wino.Calendar/Assets/Square150x150Logo.scale-100.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
Wino.Calendar/Assets/Square150x150Logo.scale-125.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
Wino.Calendar/Assets/Square150x150Logo.scale-150.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 6.0 KiB |
BIN
Wino.Calendar/Assets/Square150x150Logo.scale-400.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 809 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 809 B |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
BIN
Wino.Calendar/Assets/Square44x44Logo.scale-100.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Wino.Calendar/Assets/Square44x44Logo.scale-125.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
Wino.Calendar/Assets/Square44x44Logo.scale-150.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
Wino.Calendar/Assets/Square44x44Logo.scale-400.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
Wino.Calendar/Assets/Square44x44Logo.targetsize-16.png
Normal file
|
After Width: | Height: | Size: 596 B |
BIN
Wino.Calendar/Assets/Square44x44Logo.targetsize-24.png
Normal file
|
After Width: | Height: | Size: 920 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
Wino.Calendar/Assets/Square44x44Logo.targetsize-256.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Wino.Calendar/Assets/Square44x44Logo.targetsize-32.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Wino.Calendar/Assets/Square44x44Logo.targetsize-48.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
Wino.Calendar/Assets/StoreLogo.backup.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Wino.Calendar/Assets/StoreLogo.scale-100.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
Wino.Calendar/Assets/StoreLogo.scale-125.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
Wino.Calendar/Assets/StoreLogo.scale-150.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
Wino.Calendar/Assets/StoreLogo.scale-200.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
Wino.Calendar/Assets/StoreLogo.scale-400.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Wino.Calendar/Assets/Wide310x150Logo.scale-100.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
Wino.Calendar/Assets/Wide310x150Logo.scale-125.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
Wino.Calendar/Assets/Wide310x150Logo.scale-150.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 6.6 KiB |
BIN
Wino.Calendar/Assets/Wide310x150Logo.scale-400.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
31
Wino.Calendar/Controls/CalendarItemCommandBarFlyout.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
|
||||
namespace Wino.Calendar.Controls
|
||||
{
|
||||
public class CalendarItemCommandBarFlyout : CommandBarFlyout
|
||||
{
|
||||
public static readonly DependencyProperty ItemProperty = DependencyProperty.Register(nameof(Item), typeof(CalendarItemViewModel), typeof(CalendarItemCommandBarFlyout), new PropertyMetadata(null, new PropertyChangedCallback(OnItemChanged)));
|
||||
|
||||
public CalendarItemViewModel Item
|
||||
{
|
||||
get { return (CalendarItemViewModel)GetValue(ItemProperty); }
|
||||
set { SetValue(ItemProperty, value); }
|
||||
}
|
||||
|
||||
|
||||
private static void OnItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is CalendarItemCommandBarFlyout flyout)
|
||||
{
|
||||
flyout.UpdateMenuItems();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMenuItems()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
160
Wino.Calendar/Controls/CalendarItemControl.xaml
Normal file
@@ -0,0 +1,160 @@
|
||||
<UserControl
|
||||
x:Class="Wino.Calendar.Controls.CalendarItemControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Wino.Core.UWP.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:helpers="using:Wino.Helpers"
|
||||
xmlns:local="using:Wino.Calendar.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
d:DesignHeight="300"
|
||||
d:DesignWidth="400"
|
||||
CanDrag="True"
|
||||
DragStarting="ControlDragStarting"
|
||||
DropCompleted="ControlDropped"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid
|
||||
x:Name="MainGrid"
|
||||
CornerRadius="4"
|
||||
DoubleTapped="ControlDoubleTapped"
|
||||
RightTapped="ControlRightTapped"
|
||||
Tapped="ControlTapped"
|
||||
ToolTipService.ToolTip="{x:Bind CalendarItemTitle, Mode=OneWay}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.ContextFlyout>
|
||||
<local:CalendarItemCommandBarFlyout Placement="Top">
|
||||
<local:CalendarItemCommandBarFlyout.PrimaryCommands>
|
||||
<AppBarButton Icon="Save" Label="save" />
|
||||
<AppBarButton Icon="Delete" Label="Delet" />
|
||||
</local:CalendarItemCommandBarFlyout.PrimaryCommands>
|
||||
</local:CalendarItemCommandBarFlyout>
|
||||
</Grid.ContextFlyout>
|
||||
|
||||
<Grid
|
||||
x:Name="MainBackground"
|
||||
Grid.ColumnSpan="2"
|
||||
Background="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(CalendarItem.AssignedCalendar.BackgroundColorHex), Mode=OneWay}" />
|
||||
|
||||
<Rectangle
|
||||
x:Name="MainBorder"
|
||||
Grid.ColumnSpan="2"
|
||||
Canvas.ZIndex="2"
|
||||
Stroke="{ThemeResource CalendarItemBorderBrush}"
|
||||
StrokeThickness="0" />
|
||||
|
||||
<TextBlock
|
||||
x:Name="EventTitleTextblock"
|
||||
Margin="2,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="8"
|
||||
FontSize="13"
|
||||
Foreground="{x:Bind helpers:XamlHelpers.GetReadableTextColor(CalendarItem.AssignedCalendar.BackgroundColorHex), Mode=OneWay}"
|
||||
HorizontalTextAlignment="Center"
|
||||
Text="{x:Bind CalendarItemTitle, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
|
||||
<!-- TODO: Event attributes -->
|
||||
<StackPanel
|
||||
x:Name="AttributeStack"
|
||||
Grid.Column="1"
|
||||
Margin="0,4,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6">
|
||||
<controls:WinoFontIcon
|
||||
FontSize="12"
|
||||
Foreground="{x:Bind helpers:XamlHelpers.GetReadableTextColor(CalendarItem.AssignedCalendar.BackgroundColorHex), Mode=OneWay}"
|
||||
Icon="CalendarEventRepeat"
|
||||
Visibility="{x:Bind CalendarItem.IsRecurringEvent, Mode=OneWay}" />
|
||||
|
||||
<controls:WinoFontIcon
|
||||
FontSize="12"
|
||||
Foreground="{x:Bind helpers:XamlHelpers.GetReadableTextColor(CalendarItem.AssignedCalendar.BackgroundColorHex), Mode=OneWay}"
|
||||
Icon="CalendarEventMuiltiDay"
|
||||
Visibility="{x:Bind CalendarItem.IsMultiDayEvent, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="SelectionStates">
|
||||
<VisualState x:Name="NonSelected" />
|
||||
<VisualState x:Name="Selected">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="MainBorder.StrokeThickness" Value="1" />
|
||||
<Setter Target="MainBorder.Margin" Value="1" />
|
||||
<Setter Target="MainBorder.Stroke" Value="{ThemeResource CalendarItemSelectedBorderBrush}" />
|
||||
</VisualState.Setters>
|
||||
<VisualState.StateTriggers>
|
||||
<StateTrigger IsActive="{x:Bind CalendarItem.IsSelected, Mode=OneWay, FallbackValue=False}" />
|
||||
</VisualState.StateTriggers>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="DraggingStates">
|
||||
<VisualState x:Name="NotDragging" />
|
||||
<VisualState x:Name="Dragging">
|
||||
<VisualState.StateTriggers>
|
||||
<StateTrigger IsActive="{x:Bind IsDragging, Mode=OneWay}" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="MainBorder.StrokeThickness" Value="2" />
|
||||
<Setter Target="MainBorder.Stroke" Value="{ThemeResource CalendarItemDraggingBorderBrush}" />
|
||||
<Setter Target="MainBorder.StrokeDashArray" Value="2.5" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="EventDurationStates">
|
||||
<!-- Regular event template in the panel. -->
|
||||
<VisualState x:Name="RegularEvent" />
|
||||
|
||||
<!-- All-Day template for top area. -->
|
||||
<VisualState x:Name="AllDayEvent">
|
||||
<VisualState.Setters>
|
||||
|
||||
<Setter Target="AttributeStack.VerticalAlignment" Value="Center" />
|
||||
<Setter Target="MainGrid.MinHeight" Value="30" />
|
||||
<Setter Target="MainBorder.StrokeThickness" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
<!-- Multi-Day template for top area. -->
|
||||
<VisualState x:Name="CustomAreaMultiDayEvent">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="MainBackground.Opacity" Value="1" />
|
||||
<Setter Target="MainBorder.StrokeThickness" Value="0" />
|
||||
<Setter Target="AttributeStack.Visibility" Value="Visible" />
|
||||
<Setter Target="AttributeStack.Margin" Value="0,0,4,0" />
|
||||
<Setter Target="AttributeStack.VerticalAlignment" Value="Center" />
|
||||
<Setter Target="MainGrid.MinHeight" Value="30" />
|
||||
<Setter Target="EventTitleTextblock.HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Target="EventTitleTextblock.HorizontalTextAlignment" Value="Left" />
|
||||
<Setter Target="EventTitleTextblock.Margin" Value="6,0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
<!--
|
||||
Ghost rendering for multi-day events in the panel.
|
||||
All-Multi day area template is CustomAreaMultiDayEvent
|
||||
-->
|
||||
|
||||
<VisualState x:Name="MultiDayEvent">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="MainGrid.CornerRadius" Value="0" />
|
||||
<Setter Target="MainBackground.Opacity" Value="0.2" />
|
||||
<Setter Target="MainGrid.IsHitTestVisible" Value="False" />
|
||||
<Setter Target="MainBorder.StrokeThickness" Value="0" />
|
||||
<Setter Target="AttributeStack.Visibility" Value="Collapsed" />
|
||||
<Setter Target="EventTitleTextblock.Visibility" Value="Collapsed" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
198
Wino.Calendar/Controls/CalendarItemControl.xaml.cs
Normal file
@@ -0,0 +1,198 @@
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Itenso.TimePeriod;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Calendar.ViewModels.Messages;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Calendar.Controls
|
||||
{
|
||||
public sealed partial class CalendarItemControl : UserControl
|
||||
{
|
||||
// Single tap has a delay to report double taps properly.
|
||||
private bool isSingleTap = false;
|
||||
|
||||
public static readonly DependencyProperty CalendarItemProperty = DependencyProperty.Register(nameof(CalendarItem), typeof(CalendarItemViewModel), typeof(CalendarItemControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarItemChanged)));
|
||||
public static readonly DependencyProperty IsDraggingProperty = DependencyProperty.Register(nameof(IsDragging), typeof(bool), typeof(CalendarItemControl), new PropertyMetadata(false));
|
||||
public static readonly DependencyProperty IsCustomEventAreaProperty = DependencyProperty.Register(nameof(IsCustomEventArea), typeof(bool), typeof(CalendarItemControl), new PropertyMetadata(false));
|
||||
public static readonly DependencyProperty CalendarItemTitleProperty = DependencyProperty.Register(nameof(CalendarItemTitle), typeof(string), typeof(CalendarItemControl), new PropertyMetadata(string.Empty));
|
||||
public static readonly DependencyProperty DisplayingDateProperty = DependencyProperty.Register(nameof(DisplayingDate), typeof(CalendarDayModel), typeof(CalendarItemControl), new PropertyMetadata(null, new PropertyChangedCallback(OnDisplayDateChanged)));
|
||||
|
||||
/// <summary>
|
||||
/// Whether the control is displaying as regular event or all-multi day area in the day control.
|
||||
/// </summary>
|
||||
public bool IsCustomEventArea
|
||||
{
|
||||
get { return (bool)GetValue(IsCustomEventAreaProperty); }
|
||||
set { SetValue(IsCustomEventAreaProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Day that the calendar item is rendered at.
|
||||
/// It's needed for title manipulation and some other adjustments later on.
|
||||
/// </summary>
|
||||
public CalendarDayModel DisplayingDate
|
||||
{
|
||||
get { return (CalendarDayModel)GetValue(DisplayingDateProperty); }
|
||||
set { SetValue(DisplayingDateProperty, value); }
|
||||
}
|
||||
|
||||
public string CalendarItemTitle
|
||||
{
|
||||
get { return (string)GetValue(CalendarItemTitleProperty); }
|
||||
set { SetValue(CalendarItemTitleProperty, value); }
|
||||
}
|
||||
|
||||
public CalendarItemViewModel CalendarItem
|
||||
{
|
||||
get { return (CalendarItemViewModel)GetValue(CalendarItemProperty); }
|
||||
set { SetValue(CalendarItemProperty, value); }
|
||||
}
|
||||
|
||||
public bool IsDragging
|
||||
{
|
||||
get { return (bool)GetValue(IsDraggingProperty); }
|
||||
set { SetValue(IsDraggingProperty, value); }
|
||||
}
|
||||
|
||||
public CalendarItemControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private static void OnDisplayDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is CalendarItemControl control)
|
||||
{
|
||||
control.UpdateControlVisuals();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnCalendarItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is CalendarItemControl control)
|
||||
{
|
||||
control.UpdateControlVisuals();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateControlVisuals()
|
||||
{
|
||||
// Depending on the calendar item's duration and attributes, we might need to change the display title.
|
||||
// 1. Multi-Day events should display the start date and end date.
|
||||
// 2. Multi-Day events that occupy the whole day just shows 'all day'.
|
||||
// 3. Other events should display the title.
|
||||
|
||||
if (CalendarItem == null) return;
|
||||
if (DisplayingDate == null) return;
|
||||
|
||||
if (CalendarItem.IsMultiDayEvent)
|
||||
{
|
||||
// Multi day events are divided into 3 categories:
|
||||
// 1. All day events
|
||||
// 2. Events that started after the period.
|
||||
// 3. Events that started before the period and finishes within the period.
|
||||
|
||||
var periodRelation = CalendarItem.Period.GetRelation(DisplayingDate.Period);
|
||||
|
||||
if (periodRelation == Itenso.TimePeriod.PeriodRelation.StartInside ||
|
||||
periodRelation == PeriodRelation.EnclosingStartTouching)
|
||||
{
|
||||
// hour -> title
|
||||
CalendarItemTitle = $"{DisplayingDate.CalendarRenderOptions.CalendarSettings.GetTimeString(CalendarItem.StartDate.TimeOfDay)} -> {CalendarItem.Title}";
|
||||
}
|
||||
else if (
|
||||
periodRelation == PeriodRelation.EndInside ||
|
||||
periodRelation == PeriodRelation.EnclosingEndTouching)
|
||||
{
|
||||
// title <- hour
|
||||
CalendarItemTitle = $"{CalendarItem.Title} <- {DisplayingDate.CalendarRenderOptions.CalendarSettings.GetTimeString(CalendarItem.EndDate.TimeOfDay)}";
|
||||
}
|
||||
else if (periodRelation == PeriodRelation.Enclosing)
|
||||
{
|
||||
// This event goes all day and it's multi-day.
|
||||
// Item must be hidden in the calendar but displayed on the custom area at the top.
|
||||
|
||||
CalendarItemTitle = $"{Translator.CalendarItemAllDay} {CalendarItem.Title}";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not expected, but there it is.
|
||||
CalendarItemTitle = CalendarItem.Title;
|
||||
}
|
||||
|
||||
// Debug.WriteLine($"{CalendarItem.Title} Period relation with {DisplayingDate.Period.ToString()}: {periodRelation}");
|
||||
}
|
||||
else
|
||||
{
|
||||
CalendarItemTitle = CalendarItem.Title;
|
||||
}
|
||||
|
||||
UpdateVisualStates();
|
||||
}
|
||||
|
||||
private void UpdateVisualStates()
|
||||
{
|
||||
if (CalendarItem == null) return;
|
||||
|
||||
if (CalendarItem.IsAllDayEvent)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "AllDayEvent", true);
|
||||
}
|
||||
else if (CalendarItem.IsMultiDayEvent)
|
||||
{
|
||||
if (IsCustomEventArea)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "CustomAreaMultiDayEvent", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hide it.
|
||||
VisualStateManager.GoToState(this, "MultiDayEvent", true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
VisualStateManager.GoToState(this, "RegularEvent", true);
|
||||
}
|
||||
}
|
||||
|
||||
private void ControlDragStarting(UIElement sender, DragStartingEventArgs args) => IsDragging = true;
|
||||
|
||||
private void ControlDropped(UIElement sender, DropCompletedEventArgs args) => IsDragging = false;
|
||||
|
||||
private async void ControlTapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
if (CalendarItem == null) return;
|
||||
|
||||
isSingleTap = true;
|
||||
|
||||
await Task.Delay(100);
|
||||
|
||||
if (isSingleTap)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new CalendarItemTappedMessage(CalendarItem, DisplayingDate));
|
||||
}
|
||||
}
|
||||
|
||||
private void ControlDoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
|
||||
{
|
||||
if (CalendarItem == null) return;
|
||||
|
||||
isSingleTap = false;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new CalendarItemDoubleTappedMessage(CalendarItem));
|
||||
}
|
||||
|
||||
private void ControlRightTapped(object sender, RightTappedRoutedEventArgs e)
|
||||
{
|
||||
if (CalendarItem == null) return;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new CalendarItemRightTappedMessage(CalendarItem));
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Wino.Calendar/Controls/CustomCalendarFlipView.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Windows.UI.Xaml.Automation.Peers;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace Wino.Calendar.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// FlipView that hides the navigation buttons and exposes methods to navigate to the next and previous items with animations.
|
||||
/// </summary>
|
||||
public class CustomCalendarFlipView : FlipView
|
||||
{
|
||||
private const string PART_PreviousButton = "PreviousButtonHorizontal";
|
||||
private const string PART_NextButton = "NextButtonHorizontal";
|
||||
|
||||
private Button PreviousButton;
|
||||
private Button NextButton;
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
PreviousButton = GetTemplateChild(PART_PreviousButton) as Button;
|
||||
NextButton = GetTemplateChild(PART_NextButton) as Button;
|
||||
|
||||
// Hide navigation buttons
|
||||
PreviousButton.Opacity = NextButton.Opacity = 0;
|
||||
PreviousButton.IsHitTestVisible = NextButton.IsHitTestVisible = false;
|
||||
|
||||
var t = FindName("ScrollingHost");
|
||||
}
|
||||
|
||||
public void GoPreviousFlip()
|
||||
{
|
||||
var backPeer = new ButtonAutomationPeer(PreviousButton);
|
||||
backPeer.Invoke();
|
||||
}
|
||||
|
||||
public void GoNextFlip()
|
||||
{
|
||||
var nextPeer = new ButtonAutomationPeer(NextButton);
|
||||
nextPeer.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
78
Wino.Calendar/Controls/DayColumnControl.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Calendar.Controls
|
||||
{
|
||||
public class DayColumnControl : Control
|
||||
{
|
||||
private const string PART_HeaderDateDayText = nameof(PART_HeaderDateDayText);
|
||||
private const string PART_IsTodayBorder = nameof(PART_IsTodayBorder);
|
||||
private const string PART_ColumnHeaderText = nameof(PART_ColumnHeaderText);
|
||||
|
||||
private const string PART_AllDayItemsControl = nameof(PART_AllDayItemsControl);
|
||||
|
||||
private const string TodayState = nameof(TodayState);
|
||||
private const string NotTodayState = nameof(NotTodayState);
|
||||
|
||||
private TextBlock HeaderDateDayText;
|
||||
private TextBlock ColumnHeaderText;
|
||||
private Border IsTodayBorder;
|
||||
private ItemsControl AllDayItemsControl;
|
||||
|
||||
public CalendarDayModel DayModel
|
||||
{
|
||||
get { return (CalendarDayModel)GetValue(DayModelProperty); }
|
||||
set { SetValue(DayModelProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty DayModelProperty = DependencyProperty.Register(nameof(DayModel), typeof(CalendarDayModel), typeof(DayColumnControl), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged)));
|
||||
|
||||
public DayColumnControl()
|
||||
{
|
||||
DefaultStyleKey = typeof(DayColumnControl);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
HeaderDateDayText = GetTemplateChild(PART_HeaderDateDayText) as TextBlock;
|
||||
ColumnHeaderText = GetTemplateChild(PART_ColumnHeaderText) as TextBlock;
|
||||
IsTodayBorder = GetTemplateChild(PART_IsTodayBorder) as Border;
|
||||
AllDayItemsControl = GetTemplateChild(PART_AllDayItemsControl) as ItemsControl;
|
||||
|
||||
UpdateValues();
|
||||
}
|
||||
|
||||
private static void OnRenderingPropertiesChanged(DependencyObject control, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (control is DayColumnControl columnControl)
|
||||
{
|
||||
columnControl.UpdateValues();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateValues()
|
||||
{
|
||||
if (HeaderDateDayText == null || IsTodayBorder == null || DayModel == null) return;
|
||||
|
||||
HeaderDateDayText.Text = DayModel.RepresentingDate.Day.ToString();
|
||||
|
||||
// Monthly template does not use it.
|
||||
if (ColumnHeaderText != null)
|
||||
{
|
||||
ColumnHeaderText.Text = DayModel.RepresentingDate.ToString("dddd", DayModel.CalendarRenderOptions.CalendarSettings.CultureInfo);
|
||||
}
|
||||
|
||||
AllDayItemsControl.ItemsSource = DayModel.EventsCollection.AllDayEvents;
|
||||
|
||||
bool isToday = DayModel.RepresentingDate.Date == DateTime.Now.Date;
|
||||
|
||||
VisualStateManager.GoToState(this, isToday ? TodayState : NotTodayState, false);
|
||||
|
||||
UpdateLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||