Compare commits
743 Commits
v.0.0.1-al
...
feature/16
| Author | SHA1 | Date | |
|---|---|---|---|
| 99c3c3c257 | |||
| 6d17539af2 | |||
| 3c9d115d08 | |||
| a1b2a1d722 | |||
| 25afe998f5 | |||
| 4b30673125 | |||
| 047c4e88d3 | |||
| 3048e5c75d | |||
| 5cb89f6334 | |||
| 4eb485e40a | |||
| 773056b162 | |||
| 4dd4a2f35a | |||
| 7d0f9b5c33 | |||
| dffc6b9151 | |||
| dec61fd88f | |||
| 66a72f1c3c | |||
| ef32aeae52 | |||
| ec9ed1f19e | |||
| cf7dbe60f4 | |||
| 811079705f | |||
| 5211007629 | |||
| e27b0cc941 | |||
| a6649b4258 | |||
| d2cb1b511a | |||
| 6cb5cc401b | |||
| e35997dd6c | |||
| f6a5ab8943 | |||
| b74a21b02a | |||
| 87c597f29a | |||
| c10bc9dbdc | |||
| 47829a6955 | |||
| 96037e6062 | |||
| a0897d4966 | |||
| 6fb4a8996c | |||
| df64ef4b93 | |||
| a6deba4238 | |||
| a7d36787ce | |||
| 14eb77e241 | |||
| 46134a4f5c | |||
| 12b7bcdc6c | |||
| a803dc36d7 | |||
| a497ae872b | |||
| df8e060707 | |||
| 021a546479 | |||
| 32a8a6090a | |||
| f3f7d44994 | |||
| 60d746ede2 | |||
| 32d6eb1d18 | |||
| 042f44e8ef | |||
| e761fb1474 | |||
| 1f9ba96401 | |||
| 63f050b34f | |||
| aaeb4bf18c | |||
| 8b1a447bd9 | |||
| 0c40357847 | |||
| e61af14827 | |||
| 3493a74c6f | |||
| ec1182b560 | |||
| 4c5ce1aba0 | |||
| 369cabe996 | |||
| f069f62e26 | |||
| 44c474ae77 | |||
| 9c5e72e6ed | |||
| 9a8b93510e | |||
| 0812f18d77 | |||
| 0f621cd799 | |||
| 1356cbc92a | |||
| 9bce03d780 | |||
| 0a1e14a32d | |||
| 092dd5ec0a | |||
| 418a6307a0 | |||
| 07703037f2 | |||
| d9e0cdf506 | |||
| 2de8cef18d | |||
| 2f72b71fda | |||
| a957408c7e | |||
| 31dc899741 | |||
| 1d1c02a575 | |||
| 7f024534aa | |||
| f5b24cb923 | |||
| 0d497236b0 | |||
| cadb5793e7 | |||
| f87a873362 | |||
| e2882c1c6a | |||
| f5fbb3ecc4 | |||
| 02bb7e4daa | |||
| a8369249b4 | |||
| 7e35ccae6b | |||
| b82cca939f | |||
| 1edad99a1c | |||
| 7a22deea1b | |||
| 0f3863d446 | |||
| 9c5792c410 | |||
| 2aed053c42 | |||
| 424fa34cab | |||
| badf5ea311 | |||
| f1899bfe44 | |||
| bc59d1d91c | |||
| 1d20127af4 | |||
| 699d4378b2 | |||
| 1c5735eb41 | |||
| 9fb2b4ddc2 | |||
| c75b3e4a6d | |||
| 60a92dafe1 | |||
| e503db1c1b | |||
| 50bf111f03 | |||
| 341b293151 | |||
| 6db265ea99 | |||
| eb2e704ef9 | |||
| 0c44c54bd7 | |||
| b94cc1ac18 | |||
| 9ae562d92a | |||
| f5f97f676c | |||
| f98208b508 | |||
| 2a3c0fc98c | |||
| 009c53ad89 | |||
| 8076f082bc | |||
| 3c5c0dbf20 | |||
| 03ab2045b2 | |||
| 881382b399 | |||
| e5fd54f112 | |||
| c225599aa3 | |||
| df73fc87eb | |||
| 28222cbd2a | |||
| f0ff4fbfc0 | |||
| e24183eb9c | |||
| cde64a2683 | |||
| e9d9cc98b4 | |||
| 518bbb407c | |||
| 0eb27ab284 | |||
| a42e635282 | |||
| 3923e955fd | |||
| 759e168216 | |||
| 9e76acce29 | |||
| 84f8a77c72 | |||
| e5b44b2660 | |||
| ae572a5dbd | |||
| 1de0ef52ad | |||
| 496d411af6 | |||
| 79ce3efd0a | |||
| 616c239375 | |||
| 9781a20b38 | |||
| 385bd39aa1 | |||
| f7c8160c58 | |||
| 5c9db7244a | |||
| fbb83aaf7b | |||
| 2d2a83ea4c | |||
| f9eafa5b3d | |||
| 40e2229aa5 | |||
| 8dbf2a573e | |||
| 90331bfc07 | |||
| df757af7ec | |||
| fd553e1d24 | |||
| 5b668d28b7 | |||
| 2fdcc3e8aa | |||
| 350c5430a4 | |||
| 61ed6db9a3 | |||
| f0062dd9d9 | |||
| 6f155182b5 | |||
| 8b7a519e64 | |||
| 868460b023 | |||
| 003a5122fa | |||
| bc997633eb | |||
| 044a6acbbe | |||
| 5b3b706c28 | |||
| a159569c40 | |||
| 7fa9b61d49 | |||
| 86a920e934 | |||
| 28fb608b30 | |||
| 0d1ed3e666 | |||
| 4520281cb6 | |||
| 8169d92c6c | |||
| 3494d397ed | |||
| 514b1c32e7 | |||
| 08ae9269b1 | |||
| 686a13c64f | |||
| 726630027e | |||
| 03849b1d88 | |||
| 4190eb2bc6 | |||
| cd7e61e2c7 | |||
| f6dccbfc76 | |||
| 284395bb77 | |||
| aeba2e93e0 | |||
| 23f0c9c23e | |||
| 610a842b8a | |||
| 3750020663 | |||
| 4dd794c08f | |||
| b6a252287d | |||
| 34a24c9dec | |||
| 87ea5b47ee | |||
| 013fd29182 | |||
| 46041be837 | |||
| 045d2afa39 | |||
| 4037a961d1 | |||
| 5bac5f1c38 | |||
| ec4d6ce5ec | |||
| 883a32e0ca | |||
| bb46cace03 | |||
| dc3807356e | |||
| f8c6d3d089 | |||
| f2626bd5af | |||
| f3380e6c08 | |||
| eadf05e116 | |||
| 9896008335 | |||
| b9cfab1447 | |||
| f0c1ce9881 | |||
| ee7bff9062 | |||
| 0c2495b10a | |||
| fef8380860 | |||
| 3be7dac227 | |||
| e25a2bde69 | |||
| 3dfd2c7c08 | |||
| 1e95f1997d | |||
| 5877793b99 | |||
| ea5577c288 | |||
| a9b86fe7ff | |||
| b53facc16c | |||
| 8194fb2f28 | |||
| 94bb477cd9 | |||
| 8c52db9981 | |||
| 6272794cc5 | |||
| 5d832c98a7 | |||
| e3aef81ab6 | |||
| 92bf74683f | |||
| 2e1314ccd4 | |||
| 633a0599eb | |||
| e895359dac | |||
| b664bcacda | |||
| c64fd0c9b4 | |||
| 5789650c97 | |||
| 9e4f44491c | |||
| 078daeffc9 | |||
| ab9a8d0193 | |||
| 0f2e3493c4 | |||
| ae3a8b496e | |||
| cbc8a1afce | |||
| f49440367c | |||
| be6b968a43 | |||
| 2f5b9e5ff2 | |||
| 785873d3c8 | |||
| 99b894c580 | |||
| 92577e4ed9 | |||
| 95cad71ea0 | |||
| 8df5c24fa8 | |||
| e8c50b2f32 | |||
| a5f00f16ab | |||
| da99a6ec99 | |||
| 7effa77f82 | |||
| 1d02c04b7a | |||
| 39eba80e3f | |||
| daf1bc27d8 | |||
| 3140c9310b | |||
| 23b903e28a | |||
| 6f0147420a | |||
| 2fe43a5ad1 | |||
| a7e234cf10 | |||
| db554557fe | |||
| 1a2ad547be | |||
| 52518dc525 | |||
| 0b991026c7 | |||
| bdb33f1ec8 | |||
| d381036849 | |||
| 5cbcf626e8 | |||
| 852b5f444c | |||
| b4b598d1f5 | |||
| dfc7788087 | |||
| e910f1dcd3 | |||
| b1abbf6376 | |||
| b0a8529c1c | |||
| a3ae8ef5b8 | |||
| f24aeff55a | |||
| 543b4d949e | |||
| 571d32f07a | |||
| 31c6e03f4d | |||
| 2035e5b7d4 | |||
| 0bad0862a7 | |||
| 4322e75811 | |||
| 2c2bb582fd | |||
| 5e6cf22a9f | |||
| 9230b0caba | |||
| 68141a3da2 | |||
| 86f2ba01e5 | |||
| c6a9e53cff | |||
| 9364f0d9d6 | |||
| 522441b0ca | |||
| 316a50dad0 | |||
| 1363c1fb06 | |||
| 487dbf944c | |||
| 381c1ae23a | |||
| 32ed7ac3b5 | |||
| b2eeabe1ef | |||
| c63f9067d3 | |||
| 4e97f6723a | |||
| 9a0386f22d | |||
| fcf845af4d | |||
| 653b85d28d | |||
| 9a2afbfd3b | |||
| a1398623b0 | |||
| 9c4eff5056 | |||
| ad2e4bc398 | |||
| 2ad3698067 | |||
| 32e1c587d4 | |||
| 55437d83c4 | |||
| 15702a108d | |||
| ddf32797aa | |||
| df1bc4bf32 | |||
| 36fda30f27 | |||
| 1ab869ec26 | |||
| 52b78e44e4 | |||
| e827f4c527 | |||
| 73c85b1ff2 | |||
| c43b7b478c | |||
| c9188c222a | |||
| fcca74cea5 | |||
| 80672343b9 | |||
| d903a9fd7e | |||
| bed8a05057 | |||
| 8a312152a5 | |||
| eeb68496d5 | |||
| 65704b4a03 | |||
| f40113ef2c | |||
| 723699d363 | |||
| 0823a4ed41 | |||
| 22753d29c1 | |||
| 541cbe9a54 | |||
| 26d60fc8b2 | |||
| 520edd0ca6 | |||
| 73533b8c4f | |||
| be58c9ce01 | |||
| 6a49b92310 | |||
| 855b7c8bea | |||
| e10f05adb5 | |||
| ad6d08374e | |||
| 14d46d7e52 | |||
| b6fa71726e | |||
| 98c846ddc6 | |||
| 42f476919b | |||
| 13c88cb958 | |||
| 6e4375e459 | |||
| 59d1efb4fb | |||
| 611033b5cd | |||
| 4e98dcde41 | |||
| a304d9adf7 | |||
| 23d00c64ab | |||
| 4726d170a1 | |||
| 3fe421676c | |||
| b0b039875a | |||
| 840faab024 | |||
| 6c50eaefc7 | |||
| 9b2fcf1860 | |||
| f0c575d2c9 | |||
| d5a7bb320f | |||
| 4f91130cb5 | |||
| 2214ea8e7d | |||
| 5b7ef4051d | |||
| e3c39521a0 | |||
| b83719f16d | |||
| de0344d63d | |||
| 4ae1432943 | |||
| 84b8541822 | |||
| 544e55c4fd | |||
| feb2b756bd | |||
| 244c1b0bb0 | |||
| bfad74db22 | |||
| 864fde35ef | |||
| 934c048687 | |||
| c50ad288fa | |||
| 494dec8c61 | |||
| 975679b048 | |||
| 8e20fe1034 | |||
| 81aad9280c | |||
| 73c8865eb5 | |||
| 9d2b6a0286 | |||
| 588b5053e8 | |||
| 0597b21ac1 | |||
| a846e4d7ea | |||
| 16aecffdbe | |||
| 7810443a00 | |||
| 27c6d8b293 | |||
| 0822039a5f | |||
| 0b118800e4 | |||
| 90cc9587ca | |||
| 4c479676d2 | |||
| 4bcb10df81 | |||
| 664af7ffee | |||
| 2bd5c30094 | |||
| e909f347e3 | |||
| 8e4fe26ad9 | |||
| a8ade294b5 | |||
| 688a8a1706 | |||
| 598b8d0f9e | |||
| cff95aff00 | |||
| f07532c1e2 | |||
| b68c570d47 | |||
| 8bd251ac7d | |||
| 89ad6824e6 | |||
| f9edf64e83 | |||
| 37955c5701 | |||
| 5ed35362ac | |||
| 8e75f6af56 | |||
| 5b8dd6adb2 | |||
| 1273c99fdc | |||
|
|
e5bc2076dc | ||
|
|
5d45339337 | ||
|
|
f04d57382c | ||
|
|
5094554475 | ||
|
|
866f79998c | ||
| e71943f6e2 | |||
| f07103a516 | |||
| b84a893706 | |||
| 527ffd194f | |||
| 9e9491c98e | |||
|
|
8e4cff19d1 | ||
| 048fb0ef43 | |||
| a4bc03111d | |||
| 00abc4d1c0 | |||
| d4fcc8106f | |||
|
|
e4ea46c6cd | ||
|
|
e881cf0555 | ||
|
|
278544788e | ||
|
|
a12f4eb1c1 | ||
|
|
0eb8e2683c | ||
|
|
07b9b78252 | ||
|
|
fa9ad9dbae | ||
|
|
25699dffc0 | ||
| 487efb4d61 | |||
| 3f790cfbb1 | |||
| ee1962ef9c | |||
| a4d4703069 | |||
| fabb7bae19 | |||
|
|
70d6178829 | ||
|
|
7aba8554c0 | ||
|
|
dbef735a82 | ||
|
|
ccfea71a35 | ||
|
|
415cae18cd | ||
|
|
2a3ea32193 | ||
|
|
2ea68dcc89 | ||
|
|
acf3a7b003 | ||
|
|
1d352821fc | ||
| d1458443eb | |||
| 9c00b48de5 | |||
| 03ce304a0a | |||
| dde617d429 | |||
| 03a2df4fdf | |||
| 96ef70b209 | |||
| 4ae59ec881 | |||
|
|
3bd6dd4189 | ||
| c162e245bd | |||
| 481afd7533 | |||
| af42ebbf92 | |||
| 34e442dbe9 | |||
| 8783d0ac44 | |||
| 7f356b0c86 | |||
| e4a2ac6b47 | |||
| 6065b53ce9 | |||
| c644ccfb06 | |||
| 2dd7d0285d | |||
| cf5883b430 | |||
| 7af2b43193 | |||
| 90cc4d4c2a | |||
| db2ba2571f | |||
| 3b01c4623c | |||
| c5c9a43459 | |||
| b2e58f2539 | |||
| 58dc5d8c2e | |||
| fd7dbd1155 | |||
| 61b72cf595 | |||
| 81c6c377d4 | |||
| 92f2b4db95 | |||
| 5178adf71c | |||
| 4a6d639f1c | |||
| e9cf707fca | |||
| 2b7941202a | |||
| 92317bcbdf | |||
|
|
b0cb385756 | ||
|
|
118b316a35 | ||
|
|
55f5aac4e2 | ||
| ceade5cafd | |||
| 6060afc543 | |||
| ac6399d707 | |||
|
|
6006c6d3f7 | ||
|
|
0ddb4edbc9 | ||
|
|
7339194ba0 | ||
|
|
bd5e38a3ca | ||
|
|
12b713bb70 | ||
|
|
e55cea0dcc | ||
|
|
b2a3a0cf75 | ||
|
|
f142169371 | ||
|
|
0b778210ef | ||
|
|
748361d04f | ||
|
|
2e6a2b8e55 | ||
|
|
15894096f3 | ||
|
|
d488eac3ae | ||
|
|
19b2685714 | ||
|
|
068ca95afc | ||
|
|
0d28f4b87c | ||
|
|
2e454a530a | ||
|
|
6c39e1e574 | ||
|
|
b33260ec23 | ||
|
|
b108375ad5 | ||
|
|
e09ccf9356 | ||
| 8ee2b6cb06 | |||
| b14b7733ca | |||
|
|
b0b21bcba6 | ||
|
|
dec74e9b62 | ||
|
|
4e73babb71 | ||
| bc51b23563 | |||
| 057f8c1d58 | |||
| 4c1c22123e | |||
| e9929426e0 | |||
| eb404f3ef2 | |||
| c7b4623198 | |||
| ccd0c62e3c | |||
|
|
25e0c75dc6 | ||
|
|
fb59372c97 | ||
| 9f71c22a56 | |||
| 87d7fbebcd | |||
| c625174017 | |||
|
|
8dd2f5f8b8 | ||
|
|
c97fdc2b5f | ||
|
|
764ce13240 | ||
|
|
715b3debbb | ||
|
|
bb11c86816 | ||
| 53a33ca2e1 | |||
| e1dd40a1c3 | |||
| 45b11359b3 | |||
| 57fb8dbcc8 | |||
| 9a5929382b | |||
| cca09cc27e | |||
| f00aa15518 | |||
| 810f635987 | |||
| 49a6259d8a | |||
| 5a30538aa5 | |||
| 1e18105ce0 | |||
| e4c3bc1c5e | |||
| 14d30f55a7 | |||
| f1df067824 | |||
| 765610b184 | |||
| 48d99a0386 | |||
| a56e738064 | |||
| 1450e9b958 | |||
| eb114f2853 | |||
| 7faf80de03 | |||
| 8fe01c332e | |||
| 374c9295ef | |||
| 9ce2ca0ceb | |||
| 39e6e485ac | |||
| e9633a898c | |||
| 94c3bad02b | |||
| ed2d672dee | |||
| 449639df0e | |||
| a713ccb59c | |||
| f5924c4758 | |||
| 5c9f44e947 | |||
| 5f987806d6 | |||
| 92c62000af | |||
| 8b7300eac3 | |||
| babe74f2be | |||
| 8a38b9c3ea | |||
| ddd0a5d8bd | |||
| be9d1b52d2 | |||
| 514e0f8064 | |||
| ff47ef38c1 | |||
| fa2706395c | |||
| 2d1ac3a17c | |||
| 4fd8d2129b | |||
| db41f40a52 | |||
| 0e747710ab | |||
| 9f44f02a35 | |||
| 3addaa0f9d | |||
| 919a38afe5 | |||
| def31acfb1 | |||
| abb0fcbbd6 | |||
| fc6eb2b9cf | |||
| 6a0896d818 | |||
| a8129eb134 | |||
| 783f772da1 | |||
| b7930d5e2e | |||
| 1b709707b5 | |||
|
|
6a6e36ed7c | ||
|
|
d21c37966e | ||
|
|
b6554c104a | ||
|
|
a68bbddc1a | ||
|
|
32fa82e5e7 | ||
|
|
49e990dfea | ||
|
|
40e970a5dc | ||
|
|
b9b6ff85ea | ||
|
|
52c1605ce9 | ||
|
|
c2a9bda8b1 | ||
|
|
a13d9c55ea | ||
|
|
b737cae356 | ||
|
|
3857665444 | ||
|
|
4b900c12bf | ||
|
|
867d0c55da | ||
|
|
02d3220c1a | ||
|
|
3ca612b0a1 | ||
|
|
cb66b76e5a | ||
|
|
b9b72cdd50 | ||
|
|
4a56df7f8f | ||
|
|
f2a12265ad | ||
|
|
c82d72544e | ||
|
|
072021bd4c | ||
|
|
6a9e5dc9eb | ||
| cef02956b1 | |||
| a479cea5be | |||
| bbd41a65df | |||
| 2cadab004d | |||
| 6a3e184a95 | |||
| 5350113ee1 | |||
| bb79eecdfd | |||
| db51990695 | |||
| e483fc38f3 | |||
| 4019ed083f | |||
| 7be0b96491 | |||
| efdb5e0361 | |||
| 016c1ceb6e | |||
| 1b297d15b0 | |||
| 82ad2b74f8 | |||
| ed642e3d4f | |||
| 7cc3873a31 | |||
| b1e9bb3aeb | |||
| 4161e1e88b | |||
| d662680a34 | |||
| b69d2784df | |||
| d7f4b1c227 | |||
| 7a1752f773 | |||
| 57357f8aad | |||
| 806ed200f7 | |||
| d5bd0bca5f | |||
| 5da12939cb | |||
| 0d20b5847f | |||
| 973e327232 | |||
| 8c41f6a255 | |||
| 70f570489a | |||
| fa7740101b | |||
| 5aa2a335e3 | |||
| 80e601c10e | |||
| 2124c523bc | |||
| 7d0da81cf5 | |||
| cd3a5c2a49 | |||
| 4628e96456 | |||
| da61f45e8e | |||
| 9344f8212c | |||
| 6d42d59bad | |||
| 46118c274c | |||
| ab06662397 | |||
| 679f4c94d9 | |||
| 8bf2b9e3dd | |||
| cde40ef293 | |||
| 0fb6208345 | |||
| ec5a686f90 | |||
| f0c6dd8401 | |||
| 7bdad57cc8 | |||
| 5da1b6eecb | |||
| cdafd4bb6f | |||
| 6aee055df2 | |||
| 6d9871a5f0 | |||
|
|
be01b5f72a | ||
|
|
e2fe0c7d4d | ||
|
|
b72ab70e02 | ||
|
|
189daf76dd | ||
|
|
0f987f4c7a | ||
|
|
5dd8f31942 | ||
|
|
0394f5edf9 | ||
|
|
d8abad6fd8 | ||
|
|
7e6c309de0 | ||
|
|
3344575132 | ||
|
|
9b66e58dc0 | ||
|
|
56562b22bb | ||
| 4bbbcdd93f | |||
| fed5c55dd4 | |||
| c157644b44 | |||
| 5a5898787f | |||
| 9d3a45c01d | |||
| 485ac87fdb | |||
| 1ebcfc9e57 | |||
| 758f1e6c3a | |||
| 22ce742d43 | |||
| 7fc4bbfb13 | |||
| 857e05127d | |||
| 86982ada0f | |||
| e51bf3eabb | |||
| d7f08c5f50 | |||
| 9248284292 | |||
| 9a00e543d7 | |||
| 5ea7797b3e | |||
| c8b76ae0ea | |||
| c5fa540c5f | |||
| 3ceae8341b | |||
| 76ce3af643 | |||
| ab20bd764b | |||
| 8ca4e3210e | |||
| e384230a0b | |||
| 1e4fd2a164 | |||
| a491427a1d | |||
| caf60d046b | |||
| 38663c6b67 | |||
| a480530919 | |||
| ee84c60ba6 | |||
| b6dd0541ae | |||
| 906c8d8450 | |||
| 000bdc8cbc | |||
| 525acec1d3 | |||
| 497f30421d | |||
| adedb85eb2 | |||
| 2a72332bcd | |||
| 45a419cae7 | |||
| 7aa41abe61 | |||
| e1263d51ad | |||
| 8791b5296e | |||
| f2a4327166 | |||
| d34990ed50 | |||
| 2ef671884d | |||
| 2ef8eb6534 | |||
| 6f0e5ba5c2 | |||
| 1c07346aaf | |||
| 830a64b5dd | |||
| 9221f64fa5 | |||
| c4f6749882 | |||
| 14a043785e | |||
| 9eb9c0eb7f | |||
| 54ec865f04 | |||
| d5e7a17127 | |||
| 275f64b296 | |||
| 97ca62b083 | |||
| 32fb1550ff | |||
| d67972624e | |||
| 595cf6ead0 | |||
| 6faafe9fab | |||
| 66b90aac25 | |||
| d22855fc1a | |||
| 1be86bc3c5 | |||
| db3e8215fa | |||
| 2c4cef76d8 | |||
| a4ef9705f9 | |||
| 799c849570 | |||
| 799b7d8403 | |||
| 644728a9df | |||
| afb7a5f1d4 | |||
| 3d510d5b3d | |||
| a9d2325eee | |||
| 349ff948de | |||
| 0f79495775 |
@@ -1,35 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Erstelle eine Meldung für etwas, das nicht Funktioniert, wie es soll.
|
|
||||||
title: ''
|
|
||||||
labels: 'Task/Bug'
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Bug Report
|
|
||||||
|
|
||||||
## Beschreibung
|
|
||||||
[Eine klare und prägnante Beschreibung des Bugs]
|
|
||||||
|
|
||||||
## Schritte zur Reproduktion
|
|
||||||
1. Schritt 1
|
|
||||||
2. Schritt 2
|
|
||||||
3. ...
|
|
||||||
|
|
||||||
## Erwartetes Verhalten
|
|
||||||
[Was hätte passieren sollen]
|
|
||||||
|
|
||||||
## Tatsächliches Verhalten
|
|
||||||
[Was tatsächlich passiert ist]
|
|
||||||
|
|
||||||
## Screenshots/Protokolle
|
|
||||||
[Falls zutreffend, füge Screenshots, Error Logs oder Stack Traces hinzu]
|
|
||||||
|
|
||||||
## Umgebung
|
|
||||||
- Plattform: Android, iOS, Web
|
|
||||||
- OS: [z. B. iOS 18.5, Android 14]
|
|
||||||
- Flutter Version: [z.B. 3.35.6]
|
|
||||||
|
|
||||||
## Verwandte Issues
|
|
||||||
[Verweisen Sie auf ähnliche Issues oder PRs]
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
name: Enhancement
|
|
||||||
about: Enhancements for current features
|
|
||||||
title: ''
|
|
||||||
labels: 'Task\Enhancement'
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Enhancement
|
|
||||||
|
|
||||||
## Aktuelles Verhalten
|
|
||||||
[Beschreibe die bestehende Funktionalität]
|
|
||||||
|
|
||||||
## Einschränkungen/Probleme
|
|
||||||
[Was sind die aktuellen Mängel?]
|
|
||||||
|
|
||||||
## Vorgeschlagene Verbesserung
|
|
||||||
[Wie kann das Problem bzw. die Einschränkung verbessert werden?]
|
|
||||||
|
|
||||||
## Zugehörige Issues
|
|
||||||
[Links zu verwandten oder blockierenden Issues]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature
|
|
||||||
about: Neues Feature für die App
|
|
||||||
title: ''
|
|
||||||
labels: 'Task\Feature'
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Feature
|
|
||||||
|
|
||||||
## Beschreibung
|
|
||||||
[Ausführliche Erläuterung der vorgeschlagenen Funktion]
|
|
||||||
|
|
||||||
## Vorgeschlagene Lösung
|
|
||||||
[Beschreibe, wie die Feature funktionieren soll]
|
|
||||||
|
|
||||||
## Zugehörige Issues
|
|
||||||
[Links zu verwandten oder blockierenden Issues]
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# [PR Titel]
|
|
||||||
|
|
||||||
**Zugehörige Issue(s):**
|
|
||||||
Closes `<issue-no>`
|
|
||||||
|
|
||||||
## Beschreibung
|
|
||||||
*Eine klare und prägnante Übersicht über die vorgenommenen Änderungen. Erläutere nicht nur das was gemacht wurde, sondern auch warum.*
|
|
||||||
|
|
||||||
## Änderungen
|
|
||||||
- [ ] Neue Funktion X hinzugefügt
|
|
||||||
- [ ] Bug in Komponente Y behoben
|
|
||||||
- [ ] Modul Z für bessere Leistung refactored
|
|
||||||
- [ ] Dependencies aktualisiert
|
|
||||||
|
|
||||||
## Zusätzliche Anmerkungen
|
|
||||||
*Gibt es zusätzlichen Kontext, Einschränkungen oder Informationen, die Reviewer wissen sollten?*
|
|
||||||
53
.gitea/issue_template/bug.yaml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
name: Bug Report
|
||||||
|
about: Erstelle eine Bug Report
|
||||||
|
labels: 'Task/Bug'
|
||||||
|
title: ''
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Beschreibung
|
||||||
|
description: Beschreibe klar und pregnant das Fehlerverhalten
|
||||||
|
placeholder: |
|
||||||
|
- Was genau ist das unerwünschte Verhalten?
|
||||||
|
- Welche Auswirkungen hat der Fehler?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: reproduce
|
||||||
|
attributes:
|
||||||
|
label: Schritte zur Reproduktion
|
||||||
|
description: Beschreibe, wie der Fehler reproduziert werden kann
|
||||||
|
placeholder: |
|
||||||
|
- 1. Schritt 1
|
||||||
|
- 2. Schritt 2
|
||||||
|
- 3. ...
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: enviroment
|
||||||
|
attributes:
|
||||||
|
label: Umgebung
|
||||||
|
description: Gebe an, auf welchen Platformen dieser Fehler auftritt
|
||||||
|
list: false
|
||||||
|
multiple: true
|
||||||
|
options: ['Android', 'iOS']
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: solution
|
||||||
|
attributes:
|
||||||
|
label: Lösungsidee
|
||||||
|
description: Beschreibe, wie das Problem bzw. gelöst werden kann
|
||||||
|
placeholder: |
|
||||||
|
- Button X ändern, sodass ...
|
||||||
|
- Funktion X so erweitern, dass ...
|
||||||
|
- Design anpassen, sodass ...
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Verwandte Issues
|
||||||
|
description: Verweise auf ähnliche Issues oder PRs
|
||||||
|
placeholder: |
|
||||||
|
- Knüpft an Issue #35 an
|
||||||
|
- Ersetzt Issue #12
|
||||||
|
- Brauch Implementierung von #43
|
||||||
36
.gitea/issue_template/enhancement.yaml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
name: Enhancement
|
||||||
|
about: Erstelle ein Enhancement-Ticket
|
||||||
|
labels: 'Task/Enhancement'
|
||||||
|
title: ''
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Aktuelles Verhalten
|
||||||
|
description: Beschreibe, wie die Funktionalität aktuell gestaltet ist
|
||||||
|
placeholder: |
|
||||||
|
- Aktuell macht Button X folgendes ...
|
||||||
|
- Das Problem ist, dass ...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: solution
|
||||||
|
attributes:
|
||||||
|
label: Vorgeschlagene Verbesserung
|
||||||
|
description: Beschreibe, wie das Problem bzw. die Einschränkung verbessert werden kann
|
||||||
|
placeholder: |
|
||||||
|
- Button X ändern, sodass ...
|
||||||
|
- Funktion X so erweitern, dass ...
|
||||||
|
- Design anpassen, sodass ...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Zugehörige Issues
|
||||||
|
description: Links zu verwandten oder blockierenden Issues
|
||||||
|
placeholder: |
|
||||||
|
- Knüpft an Issue #35 an
|
||||||
|
- Ersetzt Issue #12
|
||||||
|
- Brauch Implementierung von #43
|
||||||
36
.gitea/issue_template/feature.yaml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
name: Feature
|
||||||
|
about: Erstelle ein Feature-Ticket
|
||||||
|
labels: 'Task/Feature'
|
||||||
|
title: ''
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Beschreibung
|
||||||
|
description: Ausführliche Erläuterung der vorgeschlagenen Funktion
|
||||||
|
placeholder: |
|
||||||
|
- Welchen Zweck erfüllt das Feature?
|
||||||
|
- Welches Problem löst das Feature?
|
||||||
|
- Wer profitiert davon?
|
||||||
|
- Warum ist es wichtig?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: solution
|
||||||
|
attributes:
|
||||||
|
label: Vorgeschlagene Lösung
|
||||||
|
description: Beschreibe, wie das Feature funktionieren soll
|
||||||
|
placeholder: |
|
||||||
|
- Neues Widget, das folgendermaßen aussieht ...
|
||||||
|
- Neue Ansicht, die folgende Inhalte hat
|
||||||
|
- Neue Funktionsweise von Komponente XY
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Zugehörige Issues
|
||||||
|
description: Links zu verwandten oder blockierenden Issues
|
||||||
|
placeholder: |
|
||||||
|
- Knüpft an Issue #35 an
|
||||||
|
- Ersetzt Issue #12
|
||||||
|
- Brauch Implementierung von #43
|
||||||
64
.gitea/pull_request_template.yaml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
name: Pull Request
|
||||||
|
about: Vorlage für Pull Requests
|
||||||
|
title: "WIP: [Name des Issues]"
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
id: related_issue
|
||||||
|
attributes:
|
||||||
|
label: Zugehörige Issue(s)
|
||||||
|
description: Issues welche mit diesem Pull Request geschlossen werden sollen
|
||||||
|
placeholder: "Closes #123"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Beschreibung
|
||||||
|
description: |
|
||||||
|
Eine klare und prägnante Zusammenfassung aller vorgenommenen Änderungen.
|
||||||
|
placeholder: |
|
||||||
|
Was wurde geändert?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: changes
|
||||||
|
attributes:
|
||||||
|
label: Änderungen
|
||||||
|
description: Liste alle Änderungen detailiert auf, die in diesem Pull Request vorgenommen wurden.
|
||||||
|
placeholder: |
|
||||||
|
- Neue Funktion X hinzugefügt
|
||||||
|
- Bug in Komponente X behoben
|
||||||
|
- Modul X für bessere Leistung refactored
|
||||||
|
- Dependencies aktualisiert
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: checklist
|
||||||
|
attributes:
|
||||||
|
label: PR-Checkliste
|
||||||
|
description: Stelle sicher, dass alle Punkte erfüllt sind, bevor du den Pull Request zum Review freigibst.
|
||||||
|
options:
|
||||||
|
- label: Ich habe für Navigationen den AdaptiveNavigator` verwendet
|
||||||
|
required: false
|
||||||
|
- label: Ich habe alle Strings lokalisiert (de/en)
|
||||||
|
required: false
|
||||||
|
- label: Ich habe die Testdaten aktualisiert oder erweitert
|
||||||
|
required: false
|
||||||
|
- label: Ich habe das JSON-Schema angepasst
|
||||||
|
required: false
|
||||||
|
- label: Ich habe Tests für neue Datenbank/DAO-Funktionen hinzugefügt
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: additional_notes
|
||||||
|
attributes:
|
||||||
|
label: Zusätzliche Anmerkungen
|
||||||
|
description: |
|
||||||
|
Gibt es zusätzlichen Kontext, Einschränkungen oder Informationen,
|
||||||
|
die Reviewer wissen sollten?
|
||||||
|
placeholder: |
|
||||||
|
- Bekannte Einschränkungen
|
||||||
|
- Offene TODOs
|
||||||
|
- Hinweise für Reviewer
|
||||||
@@ -10,22 +10,21 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Required for Flutter action
|
||||||
- name: Install jq
|
- name: Install jq
|
||||||
run: |
|
run: |
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y jq
|
apt-get install -y jq
|
||||||
|
|
||||||
- name: Install Flutter (wget)
|
- name: Set up Flutter
|
||||||
run: |
|
uses: subosito/flutter-action@v2
|
||||||
wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz
|
with:
|
||||||
tar xf flutter_linux_3.38.2-stable.tar.xz
|
channel: stable
|
||||||
# Set Git safe directory for Flutter path
|
|
||||||
git config --global --add safe.directory "$(pwd)/flutter"
|
|
||||||
# Set Flutter path
|
|
||||||
echo "$(pwd)/flutter/bin" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
- name: Get dependencies
|
- name: Get dependencies
|
||||||
run: flutter pub get
|
run: |
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
|
flutter pub get
|
||||||
|
|
||||||
- name: Analyze Formatting
|
- name: Analyze Formatting
|
||||||
run: flutter analyze lib test
|
run: flutter analyze lib test
|
||||||
@@ -36,22 +35,62 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install dependencies
|
# Required for Flutter action
|
||||||
|
- name: Install jq
|
||||||
run: |
|
run: |
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y jq
|
apt-get install -y jq
|
||||||
|
|
||||||
- name: Install Flutter (wget)
|
- name: Set up Flutter
|
||||||
run: |
|
uses: subosito/flutter-action@v2
|
||||||
wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz
|
with:
|
||||||
tar xf flutter_linux_3.38.2-stable.tar.xz
|
channel: stable
|
||||||
# Set Git safe directory for Flutter path
|
|
||||||
git config --global --add safe.directory "$(pwd)/flutter"
|
|
||||||
# Set Flutter path
|
|
||||||
echo "$(pwd)/flutter/bin" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
- name: Get dependencies
|
- name: Get dependencies
|
||||||
run: flutter pub get
|
run: |
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
|
flutter pub get
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: flutter test
|
run: flutter test
|
||||||
|
|
||||||
|
localizations:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Required for Flutter action
|
||||||
|
- name: Install jq
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y jq
|
||||||
|
|
||||||
|
- name: Set up Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
- name: Get dependencies
|
||||||
|
run: |
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
|
flutter pub get
|
||||||
|
|
||||||
|
- name: Check for untranslated messages
|
||||||
|
run: |
|
||||||
|
flutter gen-l10n --no-use-deferred-loading
|
||||||
|
|
||||||
|
UNTRANSLATED_FILE=lib/l10n/untranslated_messages.json
|
||||||
|
if [ ! -f "$UNTRANSLATED_FILE" ]; then
|
||||||
|
echo "Expected $UNTRANSLATED_FILE to be generated, but it does not exist."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CONTENT=$(tr -d '[:space:]' < "$UNTRANSLATED_FILE")
|
||||||
|
if [ "$CONTENT" != "{}" ]; then
|
||||||
|
echo "Found untranslated messages:"
|
||||||
|
cat "$UNTRANSLATED_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "All messages translated."
|
||||||
@@ -7,44 +7,310 @@ on:
|
|||||||
- "main"
|
- "main"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
format:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: false # Needs bot user
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install dependencies
|
# Required for Flutter action
|
||||||
|
- name: Install jq
|
||||||
run: |
|
run: |
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y jq
|
apt-get install -y jq
|
||||||
|
|
||||||
- name: Install Flutter (wget)
|
- name: Set up Flutter
|
||||||
run: |
|
uses: subosito/flutter-action@v2
|
||||||
wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz
|
with:
|
||||||
tar xf flutter_linux_3.38.2-stable.tar.xz
|
channel: stable
|
||||||
# Set Git safe directory for Flutter path
|
|
||||||
git config --global --add safe.directory "$(pwd)/flutter"
|
|
||||||
# Set Flutter path
|
|
||||||
echo "$(pwd)/flutter/bin" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
- name: Get & upgrade dependencies
|
- name: Get dependencies
|
||||||
run: |
|
run: |
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
flutter pub get
|
flutter pub get
|
||||||
flutter pub upgrade --major-versions
|
|
||||||
|
|
||||||
- name: Auto-format
|
- name: Run tests
|
||||||
run: |
|
run: flutter test
|
||||||
dart format lib
|
|
||||||
dart fix --apply lib
|
|
||||||
|
|
||||||
# Needs credentials, push access and the right files need to be staged
|
update_version:
|
||||||
- name: Commit Changes
|
runs-on: ubuntu-latest
|
||||||
|
if: gitea.ref == 'refs/heads/development'
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||||
|
BRANCH_NAME: ${{ gitea.ref_name }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
token: ${{ secrets.BOT_TOKEN }}
|
||||||
|
ref: ${{ gitea.ref_name }}
|
||||||
|
|
||||||
|
- name: Increment version number
|
||||||
|
uses: stikkyapp/update-pubspec-version@v2
|
||||||
|
with:
|
||||||
|
strategy: 'patch'
|
||||||
|
path: './pubspec.yaml'
|
||||||
|
|
||||||
|
- name: Commit version update
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
git config --global user.name "Gitea Actions"
|
git config --global user.name "Gitea Actions [bot]"
|
||||||
git config --global user.email "actions@gitea.com"
|
git config --global user.email "actions@yannick-weigert.de"
|
||||||
git status
|
git config pull.rebase false
|
||||||
git add lib/
|
git pull origin "$BRANCH_NAME"
|
||||||
git status
|
git add pubspec.yaml
|
||||||
git commit -m "Actions: Auto-formatting [skip ci]"
|
git commit -m "Updated version number [skip ci]"
|
||||||
git push
|
git push origin HEAD:$BRANCH_NAME
|
||||||
|
|
||||||
|
generate_licenses:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||||
|
BRANCH_NAME: ${{ gitea.ref_name }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
token: ${{ secrets.BOT_TOKEN }}
|
||||||
|
ref: ${{ gitea.ref_name }}
|
||||||
|
|
||||||
|
# Required for Flutter action
|
||||||
|
- name: Install jq
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y jq
|
||||||
|
|
||||||
|
- name: Set up Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
- name: Get dependencies
|
||||||
|
run: |
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
|
flutter pub get
|
||||||
|
|
||||||
|
- name: Generate oss_licenses.dart
|
||||||
|
run: flutter pub run dart_pubspec_licenses:generate -o lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart
|
||||||
|
|
||||||
|
- name: Commit license update
|
||||||
|
run: |
|
||||||
|
if [ -n "$(git status --porcelain lib test)" ]; then
|
||||||
|
git config --global user.name "Gitea Actions [bot]"
|
||||||
|
git config --global user.email "actions@yannick-weigert.de"
|
||||||
|
git config pull.rebase false
|
||||||
|
git pull origin "$BRANCH_NAME"
|
||||||
|
git add lib test
|
||||||
|
git commit -m "Updated licenses [skip ci]"
|
||||||
|
git push origin HEAD:$BRANCH_NAME
|
||||||
|
else
|
||||||
|
echo "No changes to commit"
|
||||||
|
fi
|
||||||
|
|
||||||
|
generate_localizations:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Required for Flutter action
|
||||||
|
- name: Install jq
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y jq
|
||||||
|
|
||||||
|
- name: Set up Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
- name: Get dependencies
|
||||||
|
run: |
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
|
flutter pub get
|
||||||
|
|
||||||
|
- name: Generate localizations
|
||||||
|
run: flutter gen-l10n --no-use-deferred-loading
|
||||||
|
|
||||||
|
- name: Check for changes
|
||||||
|
id: check_changes
|
||||||
|
run: |
|
||||||
|
if [ -n "$(git status --porcelain lib/l10n)" ]; then
|
||||||
|
echo "has_changes=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "has_changes=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Commit generated localizations
|
||||||
|
if: steps.check_changes.outputs.has_changes == 'true'
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||||
|
BRANCH_NAME: ${{ gitea.ref_name }}
|
||||||
|
run: |
|
||||||
|
git fetch origin "$BRANCH_NAME"
|
||||||
|
git checkout "$BRANCH_NAME"
|
||||||
|
|
||||||
|
if [ -n "$(git status --porcelain lib test)" ]; then
|
||||||
|
git config --global user.name "Gitea Actions [bot]"
|
||||||
|
git config --global user.email "actions@yannick-weigert.de"
|
||||||
|
git config pull.rebase false
|
||||||
|
git pull origin "$BRANCH_NAME"
|
||||||
|
git add lib/l10n
|
||||||
|
git commit -m "Generated localizations [skip ci]"
|
||||||
|
git push origin "HEAD:$BRANCH_NAME"
|
||||||
|
else
|
||||||
|
echo "No changes to commit"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sort_arb_files:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Required for Flutter action
|
||||||
|
- name: Install jq
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y jq
|
||||||
|
|
||||||
|
- name: Set up Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
- name: Get dependencies
|
||||||
|
run: |
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
|
flutter pub get
|
||||||
|
|
||||||
|
- name: Sort .arb-Files
|
||||||
|
run: |
|
||||||
|
shopt -s nullglob
|
||||||
|
for file in lib/l10n/arb/app_*.arb; do
|
||||||
|
echo "Sorting $file"
|
||||||
|
dart run arb_utils sort "$file"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Check for changes
|
||||||
|
id: check_changes
|
||||||
|
run: |
|
||||||
|
if [ -n "$(git status --porcelain lib/l10n/arb)" ]; then
|
||||||
|
echo "has_changes=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "has_changes=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Commit sorted .arb-Files
|
||||||
|
if: steps.check_changes.outputs.has_changes == 'true'
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||||
|
BRANCH_NAME: ${{ gitea.ref_name }}
|
||||||
|
run: |
|
||||||
|
git fetch origin "$BRANCH_NAME"
|
||||||
|
git checkout "$BRANCH_NAME"
|
||||||
|
|
||||||
|
if [ -n "$(git status --porcelain lib/l10n/arb)" ]; then
|
||||||
|
git config --global user.name "Gitea Actions [bot]"
|
||||||
|
git config --global user.email "actions@yannick-weigert.de"
|
||||||
|
git config pull.rebase false
|
||||||
|
git pull origin "$BRANCH_NAME"
|
||||||
|
git add lib/l10n/arb
|
||||||
|
git commit -m "Sort .arb files [skip ci]"
|
||||||
|
git push origin "HEAD:$BRANCH_NAME"
|
||||||
|
else
|
||||||
|
echo "No changes to commit"
|
||||||
|
fi
|
||||||
|
|
||||||
|
format:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [update_version, generate_licenses, generate_localizations, sort_arb_files]
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Required for Flutter action
|
||||||
|
- name: Install jq
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y jq
|
||||||
|
|
||||||
|
- name: Set up Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
- name: Get dependencies
|
||||||
|
run: |
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
|
flutter pub get
|
||||||
|
|
||||||
|
- name: Check code format
|
||||||
|
id: check_format
|
||||||
|
continue-on-error: true
|
||||||
|
run: flutter analyze lib test
|
||||||
|
|
||||||
|
- name: Format code
|
||||||
|
if: steps.check_format.outcome == 'failure'
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||||
|
BRANCH_NAME: ${{ gitea.ref_name }}
|
||||||
|
run: |
|
||||||
|
git fetch origin "$BRANCH_NAME"
|
||||||
|
git checkout "$BRANCH_NAME"
|
||||||
|
|
||||||
|
dart fix --apply lib
|
||||||
|
dart fix --apply test
|
||||||
|
|
||||||
|
if [ -n "$(git status --porcelain lib test)" ]; then
|
||||||
|
git config --global user.name "Gitea Actions [bot]"
|
||||||
|
git config --global user.email "actions@yannick-weigert.de"
|
||||||
|
git config pull.rebase false
|
||||||
|
git pull origin "$BRANCH_NAME"
|
||||||
|
git add lib test
|
||||||
|
git commit -m "Auto-format code [skip ci]"
|
||||||
|
git push origin HEAD:$BRANCH_NAME
|
||||||
|
else
|
||||||
|
echo "No changes to commit"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Verify format
|
||||||
|
run: flutter analyze lib test
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: format
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Java (Temurin 17)
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: '17'
|
||||||
|
|
||||||
|
- name: Setup Android SDK
|
||||||
|
uses: android-actions/setup-android@v3
|
||||||
|
|
||||||
|
# Required for Flutter action
|
||||||
|
- name: Install jq
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y jq
|
||||||
|
|
||||||
|
- name: Set up Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
- name: Get dependencies
|
||||||
|
run: |
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
|
flutter pub get
|
||||||
|
|
||||||
|
- name: Build APK
|
||||||
|
run: flutter build apk --release
|
||||||
35
.gitea/workflows/renovate.yaml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
name: Renovate
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 12 * * 0'
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- setup/222-actions-&-pr-template-verbessern
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
renovate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: node:20-bookworm
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Upgrade git
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y git
|
||||||
|
git --version
|
||||||
|
|
||||||
|
- name: Run Renovate
|
||||||
|
env:
|
||||||
|
RENOVATE_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||||
|
RENOVATE_PLATFORM: gitea
|
||||||
|
RENOVATE_ENDPOINT: https://git.yannick-weigert.de
|
||||||
|
RENOVATE_REPOSITORIES: liquid-development/game-tracker
|
||||||
|
RENOVATE_GIT_AUTHOR: "Gitea Actions <actions@yannick-weigert.de>"
|
||||||
|
RENOVATE_CONFIG_FILE: renovate.json
|
||||||
|
LOG_LEVEL: info
|
||||||
|
run: |
|
||||||
|
npm install -g renovate
|
||||||
|
renovate
|
||||||
29
.gitignore
vendored
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
# Miscellaneous
|
# Miscellaneous
|
||||||
*.class
|
*.class
|
||||||
*.lock
|
|
||||||
*.log
|
*.log
|
||||||
*.pyc
|
*.pyc
|
||||||
*.swp
|
*.swp
|
||||||
@@ -79,6 +78,7 @@ unlinked_spec.ds
|
|||||||
local.properties
|
local.properties
|
||||||
|
|
||||||
# iOS/XCode related
|
# iOS/XCode related
|
||||||
|
Podfile.lock
|
||||||
**/ios/**/*.mode1v3
|
**/ios/**/*.mode1v3
|
||||||
**/ios/**/*.mode2v3
|
**/ios/**/*.mode2v3
|
||||||
**/ios/**/*.moved-aside
|
**/ios/**/*.moved-aside
|
||||||
@@ -150,25 +150,9 @@ app.*.symbols
|
|||||||
.gclient_previous_custom_vars
|
.gclient_previous_custom_vars
|
||||||
.gclient_previous_sync_commits
|
.gclient_previous_sync_commits
|
||||||
|
|
||||||
# Miscellaneous
|
|
||||||
*.class
|
|
||||||
*.log
|
|
||||||
*.pyc
|
|
||||||
*.swp
|
|
||||||
.DS_Store
|
|
||||||
.atom/
|
|
||||||
.build/
|
|
||||||
.buildlog/
|
|
||||||
.history
|
|
||||||
.svn/
|
|
||||||
.swiftpm/
|
.swiftpm/
|
||||||
migrate_working_dir/
|
migrate_working_dir/
|
||||||
|
|
||||||
# IntelliJ related
|
|
||||||
*.iml
|
|
||||||
*.ipr
|
|
||||||
*.iws
|
|
||||||
.idea/
|
|
||||||
|
|
||||||
# The .vscode folder contains launch configuration and tasks you configure in
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
# VS Code which you may wish to be included in version control, so this line
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
@@ -176,17 +160,8 @@ migrate_working_dir/
|
|||||||
#.vscode/
|
#.vscode/
|
||||||
|
|
||||||
# Flutter/Dart/Pub related
|
# Flutter/Dart/Pub related
|
||||||
**/doc/api/
|
|
||||||
**/ios/Flutter/.last_build_id
|
|
||||||
.dart_tool/
|
|
||||||
.flutter-plugins
|
|
||||||
.flutter-plugins-dependencies
|
|
||||||
.pub-cache/
|
|
||||||
.pub/
|
|
||||||
/build/
|
/build/
|
||||||
|
|
||||||
# Symbolication related
|
|
||||||
app.*.symbols
|
|
||||||
|
|
||||||
# Obfuscation related
|
# Obfuscation related
|
||||||
app.*.map.json
|
app.*.map.json
|
||||||
@@ -196,3 +171,5 @@ app.*.map.json
|
|||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
/devtools_options.yaml
|
/devtools_options.yaml
|
||||||
|
|
||||||
|
untranslated_messages.json
|
||||||
13
CONTRIBUTING.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
`<insert link to code of conduct here>`
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
`<insert styling guidelines here>`
|
||||||
|
|
||||||
|
## Repository structure
|
||||||
|
|
||||||
|
`<insert folder structure and explanation here>`
|
||||||
165
LICENSE
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the
|
||||||
|
Library side by side in a single library together with other library
|
||||||
|
facilities that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU Lesser General Public License from time to time. Such new
|
||||||
|
versions will be similar in spirit to the present version, but may
|
||||||
|
differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Library as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
||||||
66
README.md
@@ -1,7 +1,63 @@
|
|||||||
# Game Tracker
|
<p align="center">
|
||||||
|
<img alt="Tallee Logo" src="/artefacts/app-logo.png" width="200"/>
|
||||||
|
<h2 align="center">Tallee</h2>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
An open-source app to track card- and board games, manage players & groups and get statistics about your played games.
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://apps.apple.com/">
|
||||||
|
<img src="https://tools.applemediaservices.com/api/badges/download-on-the-app-store/black/en-US"
|
||||||
|
alt="Download on the App Store"
|
||||||
|
height="48"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<a href="https://play.google.com/">
|
||||||
|
<img alt="Get it on Google Play"
|
||||||
|
title="Google Play"
|
||||||
|
src="https://raw.githubusercontent.com/pd4d10/git-touch/main/assets/google-play-badge.png"
|
||||||
|
height="48"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
<table align="center" cellspacing="8">
|
||||||
|
<tr>
|
||||||
|
<td><img src="/artefacts/screenshot-1.png" alt="Screenshot 1" width="240" /></td>
|
||||||
|
<td><img src="/artefacts/screenshot-2.png" alt="Screenshot 2" width="240" /></td>
|
||||||
|
<td><img src="/artefacts/screenshot-3.png" alt="Screenshot 3" width="240" /></td>
|
||||||
|
<td><img src="/artefacts/screenshot-4.png" alt="Screenshot 4" width="240" /></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! If you find a bug or have a feature request, please open an issue on GitHub. If you'd like to
|
||||||
|
contribute code, feel free to fork the repository and submit a pull request. For contribution guidelines, please refer
|
||||||
|
to [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the GNU LGPLv3 License. See the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
<a href="https://github.com/liquiddevelopmentde/game-tracker/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=liquiddevelopmentde/game-tracker" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
Tallee is developed and maintained by [Liquid Development](https://liquid-dev.de). For more information or support regarding Tallee, contact us through our website or [hello@liquid-dev.de](mailto:hello@liquid-dev.de).
|
||||||
|
|
||||||
|

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

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
A all-in-one app to track card- and board games, manage players and groups and get statistics about your played games.
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
include: package:flutter_lints/flutter.yaml
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
- lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
rules:
|
rules:
|
||||||
avoid_print: false
|
avoid_print: false
|
||||||
@@ -10,4 +14,5 @@ linter:
|
|||||||
prefer_const_declarations: true
|
prefer_const_declarations: true
|
||||||
prefer_const_literals_to_create_immutables: true
|
prefer_const_literals_to_create_immutables: true
|
||||||
unnecessary_const: true
|
unnecessary_const: true
|
||||||
lines_longer_than_80_chars: false
|
lines_longer_than_80_chars: false
|
||||||
|
constant_identifier_names: false
|
||||||
@@ -6,7 +6,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.example.game_tracker"
|
namespace = "de.liquid.tallee"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId = "com.example.game_tracker"
|
applicationId = "de.liquid.tallee"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
minSdk = flutter.minSdkVersion
|
minSdk = flutter.minSdkVersion
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<application
|
<application
|
||||||
android:label="game_tracker"
|
android:label="Tallee"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
@@ -42,5 +43,14 @@
|
|||||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
<data android:mimeType="text/plain"/>
|
<data android:mimeType="text/plain"/>
|
||||||
</intent>
|
</intent>
|
||||||
|
<!-- Required for url_launcher to open URLs in external browser -->
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<data android:scheme="http" />
|
||||||
|
</intent>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 17 KiB |
@@ -1,4 +1,4 @@
|
|||||||
package com.example.game_tracker
|
package de.liquid.tallee
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 828 B After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 448 B After Width: | Height: | Size: 884 B |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 656 B After Width: | Height: | Size: 938 B |
|
Before Width: | Height: | Size: 348 B After Width: | Height: | Size: 596 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 508 B After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 824 B After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 9.0 KiB |
@@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="app_icon_background">#E6F1E4</color>
|
<color name="launch_background">#ef681f</color>
|
||||||
<color name="launch_background">#0B0B0B</color>
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Referenz unbedingt als @color/launch_background (nicht @colors/...) -->
|
<color name="ic_launcher_background">#EF681F</color>
|
||||||
<color name="ic_launcher_background">@color/app_icon_background</color>
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -18,7 +18,7 @@ pluginManagement {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
id("com.android.application") version "8.7.3" apply false
|
id("com.android.application") version "8.9.1" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
artefacts/app-logo.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
artefacts/screenshot-1.png
Normal file
|
After Width: | Height: | Size: 291 KiB |
BIN
artefacts/screenshot-2.png
Normal file
|
After Width: | Height: | Size: 279 KiB |
BIN
artefacts/screenshot-3.png
Normal file
|
After Width: | Height: | Size: 354 KiB |
BIN
artefacts/screenshot-4.png
Normal file
|
After Width: | Height: | Size: 294 KiB |
@@ -15,12 +15,56 @@
|
|||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id",
|
||||||
"createdAt",
|
"createdAt",
|
||||||
"name"
|
"name",
|
||||||
|
"description"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"games": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"ruleset": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"createdAt",
|
||||||
|
"name",
|
||||||
|
"ruleset",
|
||||||
|
"description",
|
||||||
|
"color",
|
||||||
|
"icon"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -38,6 +82,9 @@
|
|||||||
"createdAt": {
|
"createdAt": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"memberIds": {
|
"memberIds": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -45,10 +92,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id",
|
||||||
"name",
|
"name",
|
||||||
"createdAt",
|
"createdAt",
|
||||||
|
"description",
|
||||||
"memberIds"
|
"memberIds"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -67,10 +116,19 @@
|
|||||||
"createdAt": {
|
"createdAt": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"endedAt": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gameId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"groupId": {
|
"groupId": {
|
||||||
"anyOf": [
|
"type": [
|
||||||
{"type": "string"},
|
"string",
|
||||||
{"type": "null"}
|
"null"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"playerIds": {
|
"playerIds": {
|
||||||
@@ -79,25 +137,59 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"winnerId": {
|
"scores": {
|
||||||
"anyOf": [
|
"type": "object",
|
||||||
{"type": "string"},
|
"additionalProperties": {
|
||||||
{"type": "null"}
|
"oneOf": [
|
||||||
]
|
{
|
||||||
|
"type": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"roundNumber": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"score": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"change": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["roundNumber", "score", "change"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"isTeamMatch": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"teams": {
|
||||||
|
"type": ["array", "null"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id",
|
||||||
"name",
|
"name",
|
||||||
"createdAt",
|
"createdAt",
|
||||||
"groupId",
|
"gameId",
|
||||||
"playerIds"
|
"playerIds",
|
||||||
|
"notes",
|
||||||
|
"isTeamMatch"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
"required": [
|
"required": [
|
||||||
"players",
|
"players",
|
||||||
|
"games",
|
||||||
"groups",
|
"groups",
|
||||||
"matches"
|
"matches"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -20,7 +20,5 @@
|
|||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
|
||||||
<string>13.0</string>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
DDD6907F99188C9B97C6B11F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D622CF241440C10C19C0D397 /* Pods_Runner.framework */; };
|
DDD6907F99188C9B97C6B11F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D622CF241440C10C19C0D397 /* Pods_Runner.framework */; };
|
||||||
|
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -65,6 +66,7 @@
|
|||||||
B68CF4A64F0B5E45B43D6900 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
B68CF4A64F0B5E45B43D6900 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
D622CF241440C10C19C0D397 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
D622CF241440C10C19C0D397 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
E754D1191B3E54E52B6DCC49 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
E754D1191B3E54E52B6DCC49 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -80,6 +82,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
|
||||||
DDD6907F99188C9B97C6B11F /* Pods_Runner.framework in Frameworks */,
|
DDD6907F99188C9B97C6B11F /* Pods_Runner.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -98,6 +101,7 @@
|
|||||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
|
||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
@@ -152,7 +156,6 @@
|
|||||||
B68CF4A64F0B5E45B43D6900 /* Pods-RunnerTests.release.xcconfig */,
|
B68CF4A64F0B5E45B43D6900 /* Pods-RunnerTests.release.xcconfig */,
|
||||||
E754D1191B3E54E52B6DCC49 /* Pods-RunnerTests.profile.xcconfig */,
|
E754D1191B3E54E52B6DCC49 /* Pods-RunnerTests.profile.xcconfig */,
|
||||||
);
|
);
|
||||||
name = Pods;
|
|
||||||
path = Pods;
|
path = Pods;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -188,6 +191,9 @@
|
|||||||
productType = "com.apple.product-type.bundle.unit-test";
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
};
|
};
|
||||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
|
packageProductDependencies = (
|
||||||
|
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
||||||
|
);
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
@@ -213,6 +219,9 @@
|
|||||||
|
|
||||||
/* Begin PBXProject section */
|
/* Begin PBXProject section */
|
||||||
97C146E61CF9000F007C117D /* Project object */ = {
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
|
packageReferences = (
|
||||||
|
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
|
||||||
|
);
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = YES;
|
BuildIndependentTargetsInParallel = YES;
|
||||||
@@ -478,7 +487,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker;
|
PRODUCT_BUNDLE_IDENTIFIER = de.liquid.tallee;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -661,7 +670,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker;
|
PRODUCT_BUNDLE_IDENTIFIER = de.liquid.tallee;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
@@ -684,7 +693,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker;
|
PRODUCT_BUNDLE_IDENTIFIER = de.liquid.tallee;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -726,6 +735,18 @@
|
|||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
/* Begin XCLocalSwiftPackageReference section */
|
||||||
|
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
|
||||||
|
isa = XCLocalSwiftPackageReference;
|
||||||
|
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
|
||||||
|
};
|
||||||
|
/* End XCLocalSwiftPackageReference section */
|
||||||
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = FlutterGeneratedPluginSwiftPackage;
|
||||||
|
};
|
||||||
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "csqlite",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/simolus3/CSQLite.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "1ee46d19a4f451a7aa64ffc64fc99b4748131e62"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "dkcamera",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/zhangao0086/DKCamera",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "master",
|
||||||
|
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "dkimagepickercontroller",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/zhangao0086/DKImagePickerController",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "4.3.9",
|
||||||
|
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "dkphotogallery",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "master",
|
||||||
|
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "sdwebimage",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/SDWebImage/SDWebImage",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "2de3a496eaf6df9a1312862adcfd54acd73c39c0",
|
||||||
|
"version" : "5.21.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftygif",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/kirualex/SwiftyGif.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
|
||||||
|
"version" : "5.4.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "tocropviewcontroller",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/TimOliver/TOCropViewController",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "d4a6d8100f4b886fdbc8ae399bf144ff3e9afb7e",
|
||||||
|
"version" : "2.8.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 2
|
||||||
|
}
|
||||||
@@ -5,6 +5,24 @@
|
|||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES">
|
buildImplicitDependencies = "YES">
|
||||||
|
<PreActions>
|
||||||
|
<ExecutionAction
|
||||||
|
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||||
|
<ActionContent
|
||||||
|
title = "Run Prepare Flutter Framework Script"
|
||||||
|
scriptText = "/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ">
|
||||||
|
<EnvironmentBuildable>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</EnvironmentBuildable>
|
||||||
|
</ActionContent>
|
||||||
|
</ExecutionAction>
|
||||||
|
</PreActions>
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
|
|||||||
67
ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "csqlite",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/simolus3/CSQLite.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "1ee46d19a4f451a7aa64ffc64fc99b4748131e62"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "dkcamera",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/zhangao0086/DKCamera",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "master",
|
||||||
|
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "dkimagepickercontroller",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/zhangao0086/DKImagePickerController",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "4.3.9",
|
||||||
|
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "dkphotogallery",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "master",
|
||||||
|
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "sdwebimage",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/SDWebImage/SDWebImage",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "2de3a496eaf6df9a1312862adcfd54acd73c39c0",
|
||||||
|
"version" : "5.21.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftygif",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/kirualex/SwiftyGif.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
|
||||||
|
"version" : "5.4.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "tocropviewcontroller",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/TimOliver/TOCropViewController",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "d4a6d8100f4b886fdbc8ae399bf144ff3e9afb7e",
|
||||||
|
"version" : "2.8.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 2
|
||||||
|
}
|
||||||
@@ -2,12 +2,15 @@ import Flutter
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@main
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||||
|
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
@@ -1,14 +1 @@
|
|||||||
{
|
{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"}]}
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"filename" : "icon_x1024.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"platform" : "ios",
|
|
||||||
"size" : "1024x1024"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 8.8 KiB |
@@ -5,9 +5,9 @@
|
|||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.043",
|
"blue" : "0.122",
|
||||||
"green" : "0.043",
|
"green" : "0.408",
|
||||||
"red" : "0.043"
|
"red" : "0.937"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
@@ -1,17 +1,8 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "icon.png",
|
"filename" : "icon-transparent.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal"
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
|
|||||||
BIN
ios/Runner/Assets.xcassets/LauncherIcon.imageset/icon-transparent.png
vendored
Normal file
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
@@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24412" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24405"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24504"/>
|
||||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
@@ -20,12 +20,19 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Tallee" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="m4u-iU-Cmv">
|
||||||
|
<rect key="frame" x="153" y="747" width="87" height="37"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" name="Futura-Bold" family="Futura" pointSize="28"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="LauncherIcon" translatesAutoresizingMaskIntoConstraints="NO" id="ygV-Op-Bu5">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="LauncherIcon" translatesAutoresizingMaskIntoConstraints="NO" id="ygV-Op-Bu5">
|
||||||
<rect key="frame" x="46" y="334" width="301" height="184"/>
|
<rect key="frame" x="46" y="334" width="301" height="184"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" name="LauncherBackgroundColor"/>
|
<color key="backgroundColor" name="LauncherColor"/>
|
||||||
</view>
|
</view>
|
||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
@@ -36,8 +43,8 @@
|
|||||||
<color key="tintColor" red="0.90196078431372551" green="0.94509803921568625" blue="0.89411764705882346" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="tintColor" red="0.90196078431372551" green="0.94509803921568625" blue="0.89411764705882346" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="LauncherIcon" width="1000" height="1000"/>
|
<image name="LauncherIcon" width="1000" height="1000"/>
|
||||||
<namedColor name="LauncherBackgroundColor">
|
<namedColor name="LauncherColor">
|
||||||
<color red="0.90196078431372551" green="0.94509803921568625" blue="0.89411764705882346" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.93699997663497925" green="0.40799999237060547" blue="0.12200000137090683" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>Game Tracker</string>
|
<string>Tallee</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
@@ -13,7 +15,7 @@
|
|||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>game_tracker</string>
|
<string>tallee</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
@@ -22,8 +24,36 @@
|
|||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>https</string>
|
||||||
|
<string>http</string>
|
||||||
|
</array>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>UIApplicationSceneManifest</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
|
<false/>
|
||||||
|
<key>UISceneConfigurations</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIWindowSceneSessionRoleApplication</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UISceneClassName</key>
|
||||||
|
<string>UIWindowScene</string>
|
||||||
|
<key>UISceneConfigurationName</key>
|
||||||
|
<string>flutter</string>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>FlutterSceneDelegate</string>
|
||||||
|
<key>UISceneStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
@@ -39,9 +69,5 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
|
||||||
<true/>
|
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ arb-dir: lib/l10n/arb
|
|||||||
template-arb-file: app_en.arb
|
template-arb-file: app_en.arb
|
||||||
output-localization-file: app_localizations.dart
|
output-localization-file: app_localizations.dart
|
||||||
output-dir: lib/l10n/generated
|
output-dir: lib/l10n/generated
|
||||||
nullable-getter: false
|
nullable-getter: false
|
||||||
|
untranslated-messages-file: lib/l10n/untranslated_messages.json
|
||||||
23
lib/core/adaptive_page_route.dart
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Returns a platform-adaptive page route based on the current platform.
|
||||||
|
/// - On iOS, it returns a [CupertinoPageRoute].
|
||||||
|
/// - On other platforms, it returns a [MaterialPageRoute].
|
||||||
|
Route<T> adaptivePageRoute<T>({
|
||||||
|
required Widget Function(BuildContext) builder,
|
||||||
|
bool fullscreenDialog = false,
|
||||||
|
}) {
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
return CupertinoPageRoute<T>(
|
||||||
|
builder: builder,
|
||||||
|
fullscreenDialog: fullscreenDialog,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return MaterialPageRoute<T>(
|
||||||
|
builder: builder,
|
||||||
|
fullscreenDialog: fullscreenDialog,
|
||||||
|
);
|
||||||
|
}
|
||||||
145
lib/core/common.dart
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fluttericon/rpg_awesome_icons.dart';
|
||||||
|
import 'package:tallee/core/enums.dart';
|
||||||
|
import 'package:tallee/data/models/match.dart';
|
||||||
|
import 'package:tallee/data/models/player.dart';
|
||||||
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
|
|
||||||
|
/// Translates a [Ruleset] enum value to its corresponding localized string.
|
||||||
|
String translateRulesetToString(Ruleset ruleset, BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
switch (ruleset) {
|
||||||
|
case Ruleset.highestScore:
|
||||||
|
return loc.highest_score;
|
||||||
|
case Ruleset.lowestScore:
|
||||||
|
return loc.lowest_score;
|
||||||
|
case Ruleset.singleWinner:
|
||||||
|
return loc.single_winner;
|
||||||
|
case Ruleset.singleLoser:
|
||||||
|
return loc.single_loser;
|
||||||
|
case Ruleset.multipleWinners:
|
||||||
|
return loc.multiple_winners;
|
||||||
|
case Ruleset.placement:
|
||||||
|
return loc.placement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a AppColor enum value based on the provided team [index].
|
||||||
|
AppColor getTeamColor(int index) {
|
||||||
|
final colors = [
|
||||||
|
AppColor.red,
|
||||||
|
AppColor.blue,
|
||||||
|
AppColor.green,
|
||||||
|
AppColor.yellow,
|
||||||
|
AppColor.purple,
|
||||||
|
AppColor.orange,
|
||||||
|
AppColor.pink,
|
||||||
|
AppColor.teal,
|
||||||
|
];
|
||||||
|
return colors[index % colors.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Translates a [AppColor] enum value to its corresponding localized string.
|
||||||
|
String translateGameColorToString(AppColor color, BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
switch (color) {
|
||||||
|
case AppColor.red:
|
||||||
|
return loc.color_red;
|
||||||
|
case AppColor.blue:
|
||||||
|
return loc.color_blue;
|
||||||
|
case AppColor.green:
|
||||||
|
return loc.color_green;
|
||||||
|
case AppColor.yellow:
|
||||||
|
return loc.color_yellow;
|
||||||
|
case AppColor.purple:
|
||||||
|
return loc.color_purple;
|
||||||
|
case AppColor.orange:
|
||||||
|
return loc.color_orange;
|
||||||
|
case AppColor.pink:
|
||||||
|
return loc.color_pink;
|
||||||
|
case AppColor.teal:
|
||||||
|
return loc.color_teal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the [Color] object corresponding to a [AppColor] enum value.
|
||||||
|
Color getColorFromGameColor(AppColor color) {
|
||||||
|
switch (color) {
|
||||||
|
case AppColor.red:
|
||||||
|
return Colors.red;
|
||||||
|
case AppColor.blue:
|
||||||
|
return Colors.blue;
|
||||||
|
case AppColor.green:
|
||||||
|
return Colors.green;
|
||||||
|
case AppColor.yellow:
|
||||||
|
return const Color(0xFFF7CA28);
|
||||||
|
case AppColor.purple:
|
||||||
|
return Colors.purple;
|
||||||
|
case AppColor.orange:
|
||||||
|
return const Color(0xFFef681f);
|
||||||
|
case AppColor.pink:
|
||||||
|
return Colors.pink;
|
||||||
|
case AppColor.teal:
|
||||||
|
return Colors.teal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns [IconData] corresponding to a [Ruleset] enum value.
|
||||||
|
IconData getRulesetIcon(Ruleset ruleset) {
|
||||||
|
switch (ruleset) {
|
||||||
|
case Ruleset.highestScore:
|
||||||
|
return Icons.arrow_upward;
|
||||||
|
case Ruleset.lowestScore:
|
||||||
|
return Icons.arrow_downward;
|
||||||
|
case Ruleset.singleWinner:
|
||||||
|
case Ruleset.multipleWinners:
|
||||||
|
return Icons.emoji_events;
|
||||||
|
case Ruleset.singleLoser:
|
||||||
|
return Icons.sentiment_dissatisfied;
|
||||||
|
case Ruleset.placement:
|
||||||
|
return RpgAwesome.podium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Counts how many players in the [match] are not part of the group
|
||||||
|
///
|
||||||
|
/// Returns the text you append after the group name, e.g. " + 5" or an empty
|
||||||
|
/// string if there are no extra players
|
||||||
|
String getExtraPlayerCount(Match match) {
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
if (match.group == null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
final groupMembers = match.group!.members;
|
||||||
|
final players = match.players;
|
||||||
|
|
||||||
|
for (var player in players) {
|
||||||
|
if (!groupMembers.any((member) => member.id == player.id)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count == 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return ' + ${count.toString()}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the player name count if greater 0 in the format " #2", otherwise an empty string
|
||||||
|
String getNameCountText(Player player) {
|
||||||
|
if (player.nameCount >= 1) {
|
||||||
|
return ' #${player.nameCount}';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the correct singular or plural form of "point(s)" based on the [points] value.
|
||||||
|
String getPointLabel(AppLocalizations loc, int points) {
|
||||||
|
if (points == 1) {
|
||||||
|
return '$points ${loc.point}';
|
||||||
|
} else {
|
||||||
|
return '$points ${loc.points}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,25 @@
|
|||||||
|
/// Application-wide constants
|
||||||
class Constants {
|
class Constants {
|
||||||
Constants._(); // Private constructor to prevent instantiation
|
Constants._(); // Private constructor to prevent instantiation
|
||||||
|
|
||||||
/// Minimum duration of all app skeletons
|
/// Minimum duration of all app skeletons
|
||||||
static Duration minimumSkeletonDuration = const Duration(milliseconds: 250);
|
static const Duration MINIMUM_SKELETON_DURATION = Duration(milliseconds: 250);
|
||||||
|
|
||||||
|
/// Maximum length for player names
|
||||||
|
static const int MAX_PLAYER_NAME_LENGTH = 32;
|
||||||
|
|
||||||
|
/// Maximum length for group names
|
||||||
|
static const int MAX_GROUP_NAME_LENGTH = 32;
|
||||||
|
|
||||||
|
/// Maximum length for match names
|
||||||
|
static const int MAX_MATCH_NAME_LENGTH = 32;
|
||||||
|
|
||||||
|
/// Maximum length for game names
|
||||||
|
static const int MAX_GAME_NAME_LENGTH = 32;
|
||||||
|
|
||||||
|
/// Maximum length for team names
|
||||||
|
static const int MAX_TEAM_NAME_LENGTH = 32;
|
||||||
|
|
||||||
|
/// Maximum length for game descriptions
|
||||||
|
static const int MAX_GAME_DESCRIPTION_LENGTH = 256;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,45 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/buttons/haptic_back_button.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/buttons/haptic_close_button.dart';
|
||||||
|
|
||||||
|
/// Theme class that defines colors, border radius, padding, and decorations
|
||||||
class CustomTheme {
|
class CustomTheme {
|
||||||
CustomTheme._(); // Private constructor to prevent instantiation
|
CustomTheme._(); // Private constructor to prevent instantiation
|
||||||
|
|
||||||
// ==================== Colors ====================
|
// ==================== Colors ====================
|
||||||
static Color primaryColor = const Color(0xFF7505E4);
|
|
||||||
static Color secondaryColor = const Color(0xFFAFA2FF);
|
/// Primary color of the app theme
|
||||||
static Color backgroundColor = const Color(0xFF0B0B0B);
|
static const Color primaryColor = Color(0xFFef681f);
|
||||||
static Color boxColor = const Color(0xFF101010);
|
|
||||||
static Color onBoxColor = const Color(0xFF181818);
|
/// Secondary color of the app theme
|
||||||
static Color boxBorder = const Color(0xFF272727);
|
static const Color secondaryColor = Color(0xFFf2a981);
|
||||||
static const Color textColor = Colors.white;
|
|
||||||
|
/// Background color of the app theme
|
||||||
|
static const Color backgroundColor = Color(0xFF0B0B0B);
|
||||||
|
|
||||||
|
/// Default color for boxes and containers
|
||||||
|
static const Color boxColor = Color(0xFF101010);
|
||||||
|
|
||||||
|
/// Default border color for boxes and containers
|
||||||
|
static const Color boxBorderColor = Color(0xFF272727);
|
||||||
|
|
||||||
|
/// Color for boxes and containers displayed on boxes
|
||||||
|
static const Color onBoxColor = Color(0xFF181818);
|
||||||
|
|
||||||
|
/// Text color used throughout the app
|
||||||
|
static const Color textColor = Color(0xFFFFFFFF);
|
||||||
|
|
||||||
|
/// Text color used throughout the app
|
||||||
|
static const Color hintColor = Color(0xFF888888);
|
||||||
|
|
||||||
|
/// Background color for the navigation bar
|
||||||
|
static const Color navBarBackgroundColor = Color(0xFF131313);
|
||||||
|
|
||||||
|
/// Selected color for the [NavbarItem]
|
||||||
|
static Color navBarItemSelectedColor = primaryColor.withGreen(100);
|
||||||
|
|
||||||
|
/// Unselected color for the [NavbarItem]
|
||||||
|
static Color navBarItemUnselectedColor = Colors.grey.shade400;
|
||||||
|
|
||||||
// ==================== Border Radius ====================
|
// ==================== Border Radius ====================
|
||||||
static const double standardBorderRadius = 12.0;
|
static const double standardBorderRadius = 12.0;
|
||||||
@@ -30,30 +59,57 @@ class CustomTheme {
|
|||||||
// ==================== Decorations ====================
|
// ==================== Decorations ====================
|
||||||
static BoxDecoration standardBoxDecoration = BoxDecoration(
|
static BoxDecoration standardBoxDecoration = BoxDecoration(
|
||||||
color: boxColor,
|
color: boxColor,
|
||||||
border: Border.all(color: boxBorder),
|
border: Border.all(color: boxBorderColor),
|
||||||
borderRadius: standardBorderRadiusAll,
|
borderRadius: standardBorderRadiusAll,
|
||||||
);
|
);
|
||||||
|
|
||||||
static BoxDecoration highlightedBoxDecoration = BoxDecoration(
|
static BoxDecoration highlightedBoxDecoration = BoxDecoration(
|
||||||
color: boxColor,
|
color: boxColor,
|
||||||
border: Border.all(color: primaryColor),
|
border: Border.all(
|
||||||
|
color: textColor,
|
||||||
|
width: 2,
|
||||||
|
strokeAlign: BorderSide.strokeAlignCenter,
|
||||||
|
),
|
||||||
borderRadius: standardBorderRadiusAll,
|
borderRadius: standardBorderRadiusAll,
|
||||||
boxShadow: [BoxShadow(color: primaryColor.withAlpha(120), blurRadius: 12)],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// ==================== App Bar Theme ====================
|
// ==================== Component Themes ====================
|
||||||
static AppBarTheme appBarTheme = AppBarTheme(
|
static const AppBarTheme appBarTheme = AppBarTheme(
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
foregroundColor: textColor,
|
foregroundColor: textColor,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
scrolledUnderElevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
titleTextStyle: const TextStyle(
|
titleTextStyle: TextStyle(
|
||||||
color: textColor,
|
color: textColor,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
iconTheme: const IconThemeData(color: textColor),
|
iconTheme: IconThemeData(color: textColor),
|
||||||
|
);
|
||||||
|
|
||||||
|
static final ActionIconThemeData actionIconTheme = ActionIconThemeData(
|
||||||
|
backButtonIconBuilder: (context) => const HapticBackButton(),
|
||||||
|
closeButtonIconBuilder: (context) => const HapticCloseButton(),
|
||||||
|
);
|
||||||
|
|
||||||
|
static const SearchBarThemeData searchBarTheme = SearchBarThemeData(
|
||||||
|
textStyle: WidgetStatePropertyAll(TextStyle(color: textColor)),
|
||||||
|
hintStyle: WidgetStatePropertyAll(TextStyle(color: hintColor)),
|
||||||
|
);
|
||||||
|
|
||||||
|
static final RadioThemeData radioTheme = RadioThemeData(
|
||||||
|
fillColor: WidgetStateProperty.resolveWith<Color>((states) {
|
||||||
|
if (states.contains(WidgetState.selected)) {
|
||||||
|
return primaryColor;
|
||||||
|
}
|
||||||
|
return textColor;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
static const InputDecorationTheme inputDecorationTheme = InputDecorationTheme(
|
||||||
|
labelStyle: TextStyle(color: textColor),
|
||||||
|
hintStyle: TextStyle(color: hintColor),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
|
||||||
|
|
||||||
/// Button types used for styling the [CustomWidthButton]
|
/// Button types used for styling the [CustomWidthButton]
|
||||||
/// - [ButtonType.primary]: Primary button style.
|
/// - [ButtonType.primary]: Primary button style.
|
||||||
/// - [ButtonType.secondary]: Secondary button style.
|
/// - [ButtonType.secondary]: Secondary button style.
|
||||||
@@ -29,24 +26,21 @@ enum ImportResult {
|
|||||||
/// - [ExportResult.unknownException]: An exception occurred during export.
|
/// - [ExportResult.unknownException]: An exception occurred during export.
|
||||||
enum ExportResult { success, canceled, unknownException }
|
enum ExportResult { success, canceled, unknownException }
|
||||||
|
|
||||||
/// Different rulesets available for matches
|
/// Different rulesets available for games
|
||||||
/// - [Ruleset.singleWinner]: The match is won by a single player
|
/// - [Ruleset.highestScore]: The player with the highest score wins.
|
||||||
/// - [Ruleset.singleLoser]: The match is lost by a single player
|
/// - [Ruleset.lowestScore]: The player with the lowest score wins.
|
||||||
/// - [Ruleset.mostPoints]: The player with the most points wins.
|
/// - [Ruleset.singleWinner]: The match is won by a single player.
|
||||||
/// - [Ruleset.leastPoints]: The player with the fewest points wins.
|
/// - [Ruleset.singleLoser]: The match has a single loser.
|
||||||
enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints }
|
/// - [Ruleset.multipleWinners]: Multiple players can be winners.
|
||||||
|
/// - [Ruleset.placement]: The player with the highest placement wins.
|
||||||
/// Translates a [Ruleset] enum value to its corresponding localized string.
|
enum Ruleset {
|
||||||
String translateRulesetToString(Ruleset ruleset, BuildContext context) {
|
singleWinner,
|
||||||
final loc = AppLocalizations.of(context);
|
multipleWinners,
|
||||||
switch (ruleset) {
|
highestScore,
|
||||||
case Ruleset.singleWinner:
|
lowestScore,
|
||||||
return loc.single_winner;
|
placement,
|
||||||
case Ruleset.singleLoser:
|
singleLoser,
|
||||||
return loc.single_loser;
|
|
||||||
case Ruleset.mostPoints:
|
|
||||||
return loc.most_points;
|
|
||||||
case Ruleset.leastPoints:
|
|
||||||
return loc.least_points;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Different colors for highlighting content
|
||||||
|
enum AppColor { red, orange, yellow, green, teal, blue, purple, pink }
|
||||||
|
|||||||
218
lib/data/dao/game_dao.dart
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:tallee/core/enums.dart';
|
||||||
|
import 'package:tallee/data/db/database.dart';
|
||||||
|
import 'package:tallee/data/db/tables/game_table.dart';
|
||||||
|
import 'package:tallee/data/models/game.dart';
|
||||||
|
|
||||||
|
part 'game_dao.g.dart';
|
||||||
|
|
||||||
|
@DriftAccessor(tables: [GameTable])
|
||||||
|
class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
|
||||||
|
GameDao(super.db);
|
||||||
|
|
||||||
|
/* Create */
|
||||||
|
|
||||||
|
/// Adds a new [game] to the database.
|
||||||
|
/// If a game with the same ID already exists, no action is taken.
|
||||||
|
/// Returns `true` if the game was added, `false` otherwise.
|
||||||
|
Future<bool> addGame({required Game game}) async {
|
||||||
|
if (!await gameExists(gameId: game.id)) {
|
||||||
|
await into(gameTable).insert(
|
||||||
|
GameTableCompanion.insert(
|
||||||
|
id: game.id,
|
||||||
|
name: game.name,
|
||||||
|
ruleset: game.ruleset.name,
|
||||||
|
description: game.description,
|
||||||
|
color: game.color.name,
|
||||||
|
icon: game.icon,
|
||||||
|
createdAt: game.createdAt,
|
||||||
|
),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds multiple [games] to the database in a batch operation.
|
||||||
|
/// Uses insertOrIgnore to avoid overwriting existing games.
|
||||||
|
Future<bool> addGamesAsList({required List<Game> games}) async {
|
||||||
|
if (games.isEmpty) return false;
|
||||||
|
|
||||||
|
await db.batch(
|
||||||
|
(b) => b.insertAll(
|
||||||
|
gameTable,
|
||||||
|
games
|
||||||
|
.map(
|
||||||
|
(game) => GameTableCompanion.insert(
|
||||||
|
id: game.id,
|
||||||
|
name: game.name,
|
||||||
|
ruleset: game.ruleset.name,
|
||||||
|
description: game.description,
|
||||||
|
color: game.color.name,
|
||||||
|
icon: game.icon,
|
||||||
|
createdAt: game.createdAt,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
mode: InsertMode.insertOrIgnore,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read */
|
||||||
|
|
||||||
|
/// Retrieves the total count of games in the database.
|
||||||
|
Future<int> getGameCount() async {
|
||||||
|
final count =
|
||||||
|
await (selectOnly(gameTable)..addColumns([gameTable.id.count()]))
|
||||||
|
.map((row) => row.read(gameTable.id.count()))
|
||||||
|
.getSingle();
|
||||||
|
return count ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a game with the given [gameId] exists in the database.
|
||||||
|
/// Returns `true` if the game exists, `false` otherwise.
|
||||||
|
Future<bool> gameExists({required String gameId}) async {
|
||||||
|
final query = select(gameTable)..where((g) => g.id.equals(gameId));
|
||||||
|
final result = await query.getSingleOrNull();
|
||||||
|
return result != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves all games from the database.
|
||||||
|
Future<List<Game>> getAllGames() async {
|
||||||
|
final query = select(gameTable);
|
||||||
|
final result = await query.get();
|
||||||
|
return result
|
||||||
|
.map(
|
||||||
|
(row) => Game(
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
ruleset: Ruleset.values.firstWhere((e) => e.name == row.ruleset),
|
||||||
|
description: row.description,
|
||||||
|
color: AppColor.values.firstWhere((e) => e.name == row.color),
|
||||||
|
icon: row.icon,
|
||||||
|
createdAt: row.createdAt,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves a [Game] by its [gameId].
|
||||||
|
Future<Game> getGameById({required String gameId}) async {
|
||||||
|
final query = select(gameTable)..where((g) => g.id.equals(gameId));
|
||||||
|
final result = await query.getSingle();
|
||||||
|
return Game(
|
||||||
|
id: result.id,
|
||||||
|
name: result.name,
|
||||||
|
ruleset: Ruleset.values.firstWhere((e) => e.name == result.ruleset),
|
||||||
|
description: result.description,
|
||||||
|
color: AppColor.values.firstWhere((e) => e.name == result.color),
|
||||||
|
icon: result.icon,
|
||||||
|
createdAt: result.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update */
|
||||||
|
|
||||||
|
/// Updates the name of the game with the given [gameId] to [name].
|
||||||
|
Future<bool> updateGameName({
|
||||||
|
required String gameId,
|
||||||
|
required String name,
|
||||||
|
}) async {
|
||||||
|
final rowsAffected =
|
||||||
|
await (update(gameTable)..where((g) => g.id.equals(gameId))).write(
|
||||||
|
GameTableCompanion(name: Value(name)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the ruleset of the game with the given [gameId].
|
||||||
|
Future<bool> updateGameRuleset({
|
||||||
|
required String gameId,
|
||||||
|
required Ruleset ruleset,
|
||||||
|
}) async {
|
||||||
|
final rowsAffected =
|
||||||
|
await (update(gameTable)..where((g) => g.id.equals(gameId))).write(
|
||||||
|
GameTableCompanion(ruleset: Value(ruleset.name)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the description of the game with the given [gameId].
|
||||||
|
Future<bool> updateGameDescription({
|
||||||
|
required String gameId,
|
||||||
|
required String description,
|
||||||
|
}) async {
|
||||||
|
final rowsAffected =
|
||||||
|
await (update(gameTable)..where((g) => g.id.equals(gameId))).write(
|
||||||
|
GameTableCompanion(description: Value(description)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the color of the game with the given [gameId].
|
||||||
|
Future<bool> updateGameColor({
|
||||||
|
required String gameId,
|
||||||
|
required AppColor color,
|
||||||
|
}) async {
|
||||||
|
final rowsAffected =
|
||||||
|
await (update(gameTable)..where((g) => g.id.equals(gameId))).write(
|
||||||
|
GameTableCompanion(color: Value(color.name)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the icon of the game with the given [gameId].
|
||||||
|
Future<bool> updateGameIcon({
|
||||||
|
required String gameId,
|
||||||
|
required String icon,
|
||||||
|
}) async {
|
||||||
|
final rowsAffected =
|
||||||
|
await (update(gameTable)..where((g) => g.id.equals(gameId))).write(
|
||||||
|
GameTableCompanion(icon: Value(icon)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delete */
|
||||||
|
|
||||||
|
/// Deletes the game with the given [gameId] from the database.
|
||||||
|
/// Returns `true` if the game was deleted, `false` if the game did not exist.
|
||||||
|
Future<bool> deleteGame({required String gameId}) async {
|
||||||
|
final query = delete(gameTable)..where((g) => g.id.equals(gameId));
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes all games from the database.
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> deleteAllGames() async {
|
||||||
|
final query = delete(gameTable);
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves all games with their respective match counts.
|
||||||
|
/// Returns a list of tuples (Game, matchCount).
|
||||||
|
Future<List<(Game, int)>> getGameUsage() async {
|
||||||
|
final games = await getAllGames();
|
||||||
|
|
||||||
|
final results = <(Game, int)>[];
|
||||||
|
|
||||||
|
for (final game in games) {
|
||||||
|
final matchCount =
|
||||||
|
await (selectOnly(db.matchTable)
|
||||||
|
..where(db.matchTable.gameId.equals(game.id))
|
||||||
|
..addColumns([db.matchTable.id.count()]))
|
||||||
|
.map((row) => row.read(db.matchTable.id.count()))
|
||||||
|
.getSingle();
|
||||||
|
|
||||||
|
results.add((game, matchCount ?? 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
lib/data/dao/game_dao.g.dart
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'game_dao.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
mixin _$GameDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||||
|
$GameTableTable get gameTable => attachedDatabase.gameTable;
|
||||||
|
GameDaoManager get managers => GameDaoManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class GameDaoManager {
|
||||||
|
final _$GameDaoMixin _db;
|
||||||
|
GameDaoManager(this._db);
|
||||||
|
$$GameTableTableTableManager get gameTable =>
|
||||||
|
$$GameTableTableTableManager(_db.attachedDatabase, _db.gameTable);
|
||||||
|
}
|
||||||
@@ -1,51 +1,18 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:game_tracker/data/db/database.dart';
|
import 'package:tallee/data/db/database.dart';
|
||||||
import 'package:game_tracker/data/db/tables/group_table.dart';
|
import 'package:tallee/data/db/tables/group_table.dart';
|
||||||
import 'package:game_tracker/data/db/tables/player_group_table.dart';
|
import 'package:tallee/data/db/tables/match_table.dart';
|
||||||
import 'package:game_tracker/data/dto/group.dart';
|
import 'package:tallee/data/db/tables/player_group_table.dart';
|
||||||
import 'package:game_tracker/data/dto/player.dart';
|
import 'package:tallee/data/models/group.dart';
|
||||||
|
import 'package:tallee/data/models/player.dart';
|
||||||
|
|
||||||
part 'group_dao.g.dart';
|
part 'group_dao.g.dart';
|
||||||
|
|
||||||
@DriftAccessor(tables: [GroupTable, PlayerGroupTable])
|
@DriftAccessor(tables: [GroupTable, PlayerGroupTable, MatchTable])
|
||||||
class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||||
GroupDao(super.db);
|
GroupDao(super.db);
|
||||||
|
|
||||||
/// Retrieves all groups from the database.
|
/* Create */
|
||||||
Future<List<Group>> getAllGroups() async {
|
|
||||||
final query = select(groupTable);
|
|
||||||
final result = await query.get();
|
|
||||||
return Future.wait(
|
|
||||||
result.map((groupData) async {
|
|
||||||
final members = await db.playerGroupDao.getPlayersOfGroup(
|
|
||||||
groupId: groupData.id,
|
|
||||||
);
|
|
||||||
return Group(
|
|
||||||
id: groupData.id,
|
|
||||||
name: groupData.name,
|
|
||||||
members: members,
|
|
||||||
createdAt: groupData.createdAt,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves a [Group] by its [groupId], including its members.
|
|
||||||
Future<Group> getGroupById({required String groupId}) async {
|
|
||||||
final query = select(groupTable)..where((g) => g.id.equals(groupId));
|
|
||||||
final result = await query.getSingle();
|
|
||||||
|
|
||||||
List<Player> members = await db.playerGroupDao.getPlayersOfGroup(
|
|
||||||
groupId: groupId,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Group(
|
|
||||||
id: result.id,
|
|
||||||
name: result.name,
|
|
||||||
members: members,
|
|
||||||
createdAt: result.createdAt,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a new group with the given [id] and [name] to the database.
|
/// Adds a new group with the given [id] and [name] to the database.
|
||||||
/// This method also adds the group's members to the [PlayerGroupTable].
|
/// This method also adds the group's members to the [PlayerGroupTable].
|
||||||
@@ -56,6 +23,7 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
|||||||
GroupTableCompanion.insert(
|
GroupTableCompanion.insert(
|
||||||
id: group.id,
|
id: group.id,
|
||||||
name: group.name,
|
name: group.name,
|
||||||
|
description: group.description,
|
||||||
createdAt: group.createdAt,
|
createdAt: group.createdAt,
|
||||||
),
|
),
|
||||||
mode: InsertMode.insertOrReplace,
|
mode: InsertMode.insertOrReplace,
|
||||||
@@ -105,6 +73,7 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
|||||||
(group) => GroupTableCompanion.insert(
|
(group) => GroupTableCompanion.insert(
|
||||||
id: group.id,
|
id: group.id,
|
||||||
name: group.name,
|
name: group.name,
|
||||||
|
description: group.description,
|
||||||
createdAt: group.createdAt,
|
createdAt: group.createdAt,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -132,6 +101,7 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
|||||||
(p) => PlayerTableCompanion.insert(
|
(p) => PlayerTableCompanion.insert(
|
||||||
id: p.id,
|
id: p.id,
|
||||||
name: p.name,
|
name: p.name,
|
||||||
|
description: p.description,
|
||||||
createdAt: p.createdAt,
|
createdAt: p.createdAt,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -166,25 +136,44 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes the group with the given [id] from the database.
|
/* Read */
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
|
||||||
Future<bool> deleteGroup({required String groupId}) async {
|
/// Retrieves all groups from the database.
|
||||||
final query = (delete(groupTable)..where((g) => g.id.equals(groupId)));
|
Future<List<Group>> getAllGroups() async {
|
||||||
final rowsAffected = await query.go();
|
final query = select(groupTable);
|
||||||
return rowsAffected > 0;
|
final result = await query.get();
|
||||||
|
return Future.wait(
|
||||||
|
result.map((groupData) async {
|
||||||
|
final members = await db.playerGroupDao.getPlayersOfGroup(
|
||||||
|
groupId: groupData.id,
|
||||||
|
);
|
||||||
|
return Group(
|
||||||
|
id: groupData.id,
|
||||||
|
name: groupData.name,
|
||||||
|
description: groupData.description,
|
||||||
|
members: members,
|
||||||
|
createdAt: groupData.createdAt,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the name of the group with the given [id] to [newName].
|
/// Retrieves a [Group] by its [groupId], including its members.
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
Future<Group> getGroupById({required String groupId}) async {
|
||||||
Future<bool> updateGroupname({
|
final query = select(groupTable)..where((g) => g.id.equals(groupId));
|
||||||
required String groupId,
|
final result = await query.getSingle();
|
||||||
required String newName,
|
|
||||||
}) async {
|
List<Player> members = await db.playerGroupDao.getPlayersOfGroup(
|
||||||
final rowsAffected =
|
groupId: groupId,
|
||||||
await (update(groupTable)..where((g) => g.id.equals(groupId))).write(
|
);
|
||||||
GroupTableCompanion(name: Value(newName)),
|
|
||||||
);
|
return Group(
|
||||||
return rowsAffected > 0;
|
id: result.id,
|
||||||
|
name: result.name,
|
||||||
|
description: result.description,
|
||||||
|
members: members,
|
||||||
|
createdAt: result.createdAt,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves the number of groups in the database.
|
/// Retrieves the number of groups in the database.
|
||||||
@@ -204,6 +193,16 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
|||||||
return result != null;
|
return result != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Delete */
|
||||||
|
|
||||||
|
/// Deletes the group with the given [id] from the database.
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> deleteGroup({required String groupId}) async {
|
||||||
|
final query = (delete(groupTable)..where((g) => g.id.equals(groupId)));
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// Deletes all groups from the database.
|
/// Deletes all groups from the database.
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
Future<bool> deleteAllGroups() async {
|
Future<bool> deleteAllGroups() async {
|
||||||
@@ -211,4 +210,32 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
|||||||
final rowsAffected = await query.go();
|
final rowsAffected = await query.go();
|
||||||
return rowsAffected > 0;
|
return rowsAffected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Update */
|
||||||
|
|
||||||
|
/// Updates the name of the group with the given [id] to [name].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> updateGroupName({
|
||||||
|
required String groupId,
|
||||||
|
required String name,
|
||||||
|
}) async {
|
||||||
|
final rowsAffected =
|
||||||
|
await (update(groupTable)..where((g) => g.id.equals(groupId))).write(
|
||||||
|
GroupTableCompanion(name: Value(name)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the description of the group with the given [groupId] to [description].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> updateGroupDescription({
|
||||||
|
required String groupId,
|
||||||
|
required String description,
|
||||||
|
}) async {
|
||||||
|
final rowsAffected =
|
||||||
|
await (update(groupTable)..where((g) => g.id.equals(groupId))).write(
|
||||||
|
GroupTableCompanion(description: Value(description)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,4 +8,25 @@ mixin _$GroupDaoMixin on DatabaseAccessor<AppDatabase> {
|
|||||||
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
||||||
$PlayerGroupTableTable get playerGroupTable =>
|
$PlayerGroupTableTable get playerGroupTable =>
|
||||||
attachedDatabase.playerGroupTable;
|
attachedDatabase.playerGroupTable;
|
||||||
|
$GameTableTable get gameTable => attachedDatabase.gameTable;
|
||||||
|
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
||||||
|
GroupDaoManager get managers => GroupDaoManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class GroupDaoManager {
|
||||||
|
final _$GroupDaoMixin _db;
|
||||||
|
GroupDaoManager(this._db);
|
||||||
|
$$GroupTableTableTableManager get groupTable =>
|
||||||
|
$$GroupTableTableTableManager(_db.attachedDatabase, _db.groupTable);
|
||||||
|
$$PlayerTableTableTableManager get playerTable =>
|
||||||
|
$$PlayerTableTableTableManager(_db.attachedDatabase, _db.playerTable);
|
||||||
|
$$PlayerGroupTableTableTableManager get playerGroupTable =>
|
||||||
|
$$PlayerGroupTableTableTableManager(
|
||||||
|
_db.attachedDatabase,
|
||||||
|
_db.playerGroupTable,
|
||||||
|
);
|
||||||
|
$$GameTableTableTableManager get gameTable =>
|
||||||
|
$$GameTableTableTableManager(_db.attachedDatabase, _db.gameTable);
|
||||||
|
$$MatchTableTableTableManager get matchTable =>
|
||||||
|
$$MatchTableTableTableManager(_db.attachedDatabase, _db.matchTable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:game_tracker/data/db/database.dart';
|
|
||||||
import 'package:game_tracker/data/db/tables/group_match_table.dart';
|
|
||||||
import 'package:game_tracker/data/dto/group.dart';
|
|
||||||
|
|
||||||
part 'group_match_dao.g.dart';
|
|
||||||
|
|
||||||
@DriftAccessor(tables: [GroupMatchTable])
|
|
||||||
class GroupMatchDao extends DatabaseAccessor<AppDatabase>
|
|
||||||
with _$GroupMatchDaoMixin {
|
|
||||||
GroupMatchDao(super.db);
|
|
||||||
|
|
||||||
/// Associates a group with a match by inserting a record into the
|
|
||||||
/// [GroupMatchTable].
|
|
||||||
Future<void> addGroupToMatch({
|
|
||||||
required String matchId,
|
|
||||||
required String groupId,
|
|
||||||
}) async {
|
|
||||||
if (await matchHasGroup(matchId: matchId)) {
|
|
||||||
throw Exception('Match already has a group');
|
|
||||||
}
|
|
||||||
await into(groupMatchTable).insert(
|
|
||||||
GroupMatchTableCompanion.insert(groupId: groupId, matchId: matchId),
|
|
||||||
mode: InsertMode.insertOrIgnore,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves the [Group] associated with the given [matchId].
|
|
||||||
/// Returns `null` if no group is found.
|
|
||||||
Future<Group?> getGroupOfMatch({required String matchId}) async {
|
|
||||||
final result = await (select(
|
|
||||||
groupMatchTable,
|
|
||||||
)..where((g) => g.matchId.equals(matchId))).getSingleOrNull();
|
|
||||||
|
|
||||||
if (result == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final group = await db.groupDao.getGroupById(groupId: result.groupId);
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if there is a group associated with the given [matchId].
|
|
||||||
/// Returns `true` if there is a group, otherwise `false`.
|
|
||||||
Future<bool> matchHasGroup({required String matchId}) async {
|
|
||||||
final count =
|
|
||||||
await (selectOnly(groupMatchTable)
|
|
||||||
..where(groupMatchTable.matchId.equals(matchId))
|
|
||||||
..addColumns([groupMatchTable.groupId.count()]))
|
|
||||||
.map((row) => row.read(groupMatchTable.groupId.count()))
|
|
||||||
.getSingle();
|
|
||||||
return (count ?? 0) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if a specific group is associated with a specific match.
|
|
||||||
/// Returns `true` if the group is in the match, otherwise `false`.
|
|
||||||
Future<bool> isGroupInMatch({
|
|
||||||
required String matchId,
|
|
||||||
required String groupId,
|
|
||||||
}) async {
|
|
||||||
final count =
|
|
||||||
await (selectOnly(groupMatchTable)
|
|
||||||
..where(
|
|
||||||
groupMatchTable.matchId.equals(matchId) &
|
|
||||||
groupMatchTable.groupId.equals(groupId),
|
|
||||||
)
|
|
||||||
..addColumns([groupMatchTable.groupId.count()]))
|
|
||||||
.map((row) => row.read(groupMatchTable.groupId.count()))
|
|
||||||
.getSingle();
|
|
||||||
return (count ?? 0) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes the association of a group from a match based on [groupId] and
|
|
||||||
/// [matchId].
|
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
|
||||||
Future<bool> removeGroupFromMatch({
|
|
||||||
required String matchId,
|
|
||||||
required String groupId,
|
|
||||||
}) async {
|
|
||||||
final query = delete(groupMatchTable)
|
|
||||||
..where((g) => g.matchId.equals(matchId) & g.groupId.equals(groupId));
|
|
||||||
final rowsAffected = await query.go();
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the group associated with a match to [newGroupId] based on
|
|
||||||
/// [matchId].
|
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
|
||||||
Future<bool> updateGroupOfMatch({
|
|
||||||
required String matchId,
|
|
||||||
required String newGroupId,
|
|
||||||
}) async {
|
|
||||||
final updatedRows =
|
|
||||||
await (update(groupMatchTable)..where((g) => g.matchId.equals(matchId)))
|
|
||||||
.write(GroupMatchTableCompanion(groupId: Value(newGroupId)));
|
|
||||||
return updatedRows > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'group_match_dao.dart';
|
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
mixin _$GroupMatchDaoMixin on DatabaseAccessor<AppDatabase> {
|
|
||||||
$GroupTableTable get groupTable => attachedDatabase.groupTable;
|
|
||||||
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
|
||||||
$GroupMatchTableTable get groupMatchTable => attachedDatabase.groupMatchTable;
|
|
||||||
}
|
|
||||||
@@ -1,87 +1,61 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:game_tracker/data/db/database.dart';
|
import 'package:tallee/data/db/database.dart';
|
||||||
import 'package:game_tracker/data/db/tables/match_table.dart';
|
import 'package:tallee/data/db/tables/game_table.dart';
|
||||||
import 'package:game_tracker/data/dto/group.dart';
|
import 'package:tallee/data/db/tables/group_table.dart';
|
||||||
import 'package:game_tracker/data/dto/match.dart';
|
import 'package:tallee/data/db/tables/match_table.dart';
|
||||||
import 'package:game_tracker/data/dto/player.dart';
|
import 'package:tallee/data/db/tables/player_match_table.dart';
|
||||||
|
import 'package:tallee/data/models/game.dart';
|
||||||
|
import 'package:tallee/data/models/group.dart';
|
||||||
|
import 'package:tallee/data/models/match.dart';
|
||||||
|
import 'package:tallee/data/models/player.dart';
|
||||||
|
import 'package:tallee/data/models/team.dart';
|
||||||
|
|
||||||
part 'match_dao.g.dart';
|
part 'match_dao.g.dart';
|
||||||
|
|
||||||
@DriftAccessor(tables: [MatchTable])
|
@DriftAccessor(tables: [MatchTable, GameTable, GroupTable, PlayerMatchTable])
|
||||||
class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
||||||
MatchDao(super.db);
|
MatchDao(super.db);
|
||||||
|
|
||||||
/// Retrieves all matches from the database.
|
/* Create */
|
||||||
Future<List<Match>> getAllMatches() async {
|
|
||||||
final query = select(matchTable);
|
|
||||||
final result = await query.get();
|
|
||||||
|
|
||||||
return Future.wait(
|
/// Adds a new [Match] to the database. Also adds players associations and teams.
|
||||||
result.map((row) async {
|
/// This method assumes that the game and group (if any) are already present
|
||||||
final group = await db.groupMatchDao.getGroupOfMatch(matchId: row.id);
|
/// in the database.
|
||||||
final players = await db.playerMatchDao.getPlayersOfMatch(
|
Future<bool> addMatch({required Match match}) async {
|
||||||
matchId: row.id,
|
if (await matchExists(matchId: match.id)) return false;
|
||||||
);
|
|
||||||
final winner = row.winnerId != null
|
|
||||||
? await db.playerDao.getPlayerById(playerId: row.winnerId!)
|
|
||||||
: null;
|
|
||||||
return Match(
|
|
||||||
id: row.id,
|
|
||||||
name: row.name,
|
|
||||||
group: group,
|
|
||||||
players: players,
|
|
||||||
createdAt: row.createdAt,
|
|
||||||
winner: winner,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves a [Match] by its [matchId].
|
|
||||||
Future<Match> getMatchById({required String matchId}) async {
|
|
||||||
final query = select(matchTable)..where((g) => g.id.equals(matchId));
|
|
||||||
final result = await query.getSingle();
|
|
||||||
|
|
||||||
List<Player>? players;
|
|
||||||
if (await db.playerMatchDao.matchHasPlayers(matchId: matchId)) {
|
|
||||||
players = await db.playerMatchDao.getPlayersOfMatch(matchId: matchId);
|
|
||||||
}
|
|
||||||
Group? group;
|
|
||||||
if (await db.groupMatchDao.matchHasGroup(matchId: matchId)) {
|
|
||||||
group = await db.groupMatchDao.getGroupOfMatch(matchId: matchId);
|
|
||||||
}
|
|
||||||
Player? winner;
|
|
||||||
if (result.winnerId != null) {
|
|
||||||
winner = await db.playerDao.getPlayerById(playerId: result.winnerId!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Match(
|
|
||||||
id: result.id,
|
|
||||||
name: result.name,
|
|
||||||
players: players,
|
|
||||||
group: group,
|
|
||||||
winner: winner,
|
|
||||||
createdAt: result.createdAt,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a new [Match] to the database. Also adds players and group
|
|
||||||
/// associations. This method assumes that the players and groups added to
|
|
||||||
/// this match are already present in the database.
|
|
||||||
Future<void> addMatch({required Match match}) async {
|
|
||||||
await db.transaction(() async {
|
await db.transaction(() async {
|
||||||
await into(matchTable).insert(
|
await into(matchTable).insert(
|
||||||
MatchTableCompanion.insert(
|
MatchTableCompanion.insert(
|
||||||
id: match.id,
|
id: match.id,
|
||||||
|
gameId: match.game.id,
|
||||||
|
groupId: Value(match.group?.id),
|
||||||
name: match.name,
|
name: match.name,
|
||||||
winnerId: Value(match.winner?.id),
|
isTeamMatch: Value(match.isTeamMatch),
|
||||||
|
notes: match.notes,
|
||||||
createdAt: match.createdAt,
|
createdAt: match.createdAt,
|
||||||
|
endedAt: Value(match.endedAt),
|
||||||
),
|
),
|
||||||
mode: InsertMode.insertOrReplace,
|
mode: InsertMode.insertOrReplace,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (match.players != null) {
|
// Add teams
|
||||||
for (final p in match.players ?? []) {
|
if (match.teams != null && match.teams!.isNotEmpty) {
|
||||||
|
await db.teamDao.addTeamsAsList(teams: match.teams!, matchId: match.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all player IDs that are already in teams
|
||||||
|
final playersInTeams = <String>{};
|
||||||
|
if (match.teams != null) {
|
||||||
|
for (final team in match.teams!) {
|
||||||
|
for (final member in team.members) {
|
||||||
|
playersInTeams.add(member.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add players that are not in teams
|
||||||
|
for (final p in match.players) {
|
||||||
|
if (!playersInTeams.contains(p.id)) {
|
||||||
await db.playerMatchDao.addPlayerToMatch(
|
await db.playerMatchDao.addPlayerToMatch(
|
||||||
matchId: match.id,
|
matchId: match.id,
|
||||||
playerId: p.id,
|
playerId: p.id,
|
||||||
@@ -89,53 +63,68 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (match.group != null) {
|
for (final pid in match.scores.keys) {
|
||||||
await db.groupMatchDao.addGroupToMatch(
|
final playerScores = match.scores[pid];
|
||||||
matchId: match.id,
|
if (playerScores != null) {
|
||||||
groupId: match.group!.id,
|
await db.scoreEntryDao.addScore(
|
||||||
);
|
entry: playerScores,
|
||||||
|
playerId: pid,
|
||||||
|
matchId: match.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds multiple [Match]s to the database in a batch operation.
|
/// Adds multiple [Match]es to the database in a batch operation.
|
||||||
/// Also adds associated players and groups if they exist.
|
/// Also adds associated players and groups if they exist.
|
||||||
/// If the [matches] list is empty, the method returns immediately.
|
/// If the [matches] list is empty, the method returns immediately.
|
||||||
/// This Method should only be used to import matches from a different device.
|
/// This method should only be used to import matches from a different device.
|
||||||
Future<void> addMatchAsList({required List<Match> matches}) async {
|
Future<bool> addMatchesAsList({required List<Match> matches}) async {
|
||||||
if (matches.isEmpty) return;
|
if (matches.isEmpty) return false;
|
||||||
await db.transaction(() async {
|
await db.transaction(() async {
|
||||||
// Add all matches in batch
|
// Add all games first (deduplicated)
|
||||||
await db.batch(
|
final uniqueGames = <String, Game>{};
|
||||||
(b) => b.insertAll(
|
for (final match in matches) {
|
||||||
matchTable,
|
uniqueGames[match.game.id] = match.game;
|
||||||
matches
|
}
|
||||||
.map(
|
|
||||||
(match) => MatchTableCompanion.insert(
|
|
||||||
id: match.id,
|
|
||||||
name: match.name,
|
|
||||||
createdAt: match.createdAt,
|
|
||||||
winnerId: Value(match.winner?.id),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
mode: InsertMode.insertOrReplace,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add all groups of the matches in batch
|
// Add games
|
||||||
// Using insertOrIgnore to avoid overwriting existing groups (which would
|
if (uniqueGames.isNotEmpty) {
|
||||||
// trigger cascade deletes on player_group associations)
|
await db.batch(
|
||||||
|
(b) => b.insertAll(
|
||||||
|
db.gameTable,
|
||||||
|
uniqueGames.values
|
||||||
|
.map(
|
||||||
|
(game) => GameTableCompanion.insert(
|
||||||
|
id: game.id,
|
||||||
|
name: game.name,
|
||||||
|
ruleset: game.ruleset.name,
|
||||||
|
description: game.description,
|
||||||
|
color: game.color.name,
|
||||||
|
icon: game.icon,
|
||||||
|
createdAt: game.createdAt,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
mode: InsertMode.insertOrIgnore,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add groups
|
||||||
await db.batch(
|
await db.batch(
|
||||||
(b) => b.insertAll(
|
(b) => b.insertAll(
|
||||||
db.groupTable,
|
db.groupTable,
|
||||||
matches
|
matches
|
||||||
.where((match) => match.group != null)
|
.where((match) => match.group != null)
|
||||||
.map(
|
.map(
|
||||||
(matches) => GroupTableCompanion.insert(
|
(match) => GroupTableCompanion.insert(
|
||||||
id: matches.group!.id,
|
id: match.group!.id,
|
||||||
name: matches.group!.name,
|
name: match.group!.name,
|
||||||
createdAt: matches.group!.createdAt,
|
description: match.group!.description,
|
||||||
|
createdAt: match.group!.createdAt,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
@@ -143,13 +132,33 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add all players of the matches in batch (unique)
|
// Add matches
|
||||||
|
await db.batch(
|
||||||
|
(b) => b.insertAll(
|
||||||
|
matchTable,
|
||||||
|
matches
|
||||||
|
.map(
|
||||||
|
(match) => MatchTableCompanion.insert(
|
||||||
|
id: match.id,
|
||||||
|
gameId: match.game.id,
|
||||||
|
groupId: Value(match.group?.id),
|
||||||
|
name: match.name,
|
||||||
|
isTeamMatch: Value(match.isTeamMatch),
|
||||||
|
notes: match.notes,
|
||||||
|
createdAt: match.createdAt,
|
||||||
|
endedAt: Value(match.endedAt),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add players
|
||||||
final uniquePlayers = <String, Player>{};
|
final uniquePlayers = <String, Player>{};
|
||||||
for (final match in matches) {
|
for (final match in matches) {
|
||||||
if (match.players != null) {
|
for (final p in match.players) {
|
||||||
for (final p in match.players!) {
|
uniquePlayers[p.id] = p;
|
||||||
uniquePlayers[p.id] = p;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Also include members of groups
|
// Also include members of groups
|
||||||
if (match.group != null) {
|
if (match.group != null) {
|
||||||
@@ -160,8 +169,6 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (uniquePlayers.isNotEmpty) {
|
if (uniquePlayers.isNotEmpty) {
|
||||||
// Using insertOrIgnore to avoid triggering cascade deletes on
|
|
||||||
// player_group/player_match associations when players already exist
|
|
||||||
await db.batch(
|
await db.batch(
|
||||||
(b) => b.insertAll(
|
(b) => b.insertAll(
|
||||||
db.playerTable,
|
db.playerTable,
|
||||||
@@ -170,6 +177,7 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
(p) => PlayerTableCompanion.insert(
|
(p) => PlayerTableCompanion.insert(
|
||||||
id: p.id,
|
id: p.id,
|
||||||
name: p.name,
|
name: p.name,
|
||||||
|
description: p.description,
|
||||||
createdAt: p.createdAt,
|
createdAt: p.createdAt,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -179,25 +187,43 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add all player-match associations in batch
|
|
||||||
await db.batch((b) {
|
await db.batch((b) {
|
||||||
for (final match in matches) {
|
for (final match in matches) {
|
||||||
if (match.players != null) {
|
for (final entry in match.scores.entries) {
|
||||||
for (final p in match.players ?? []) {
|
if (entry.value != null) {
|
||||||
b.insert(
|
b.insert(
|
||||||
db.playerMatchTable,
|
db.scoreEntryTable,
|
||||||
PlayerMatchTableCompanion.insert(
|
ScoreEntryTableCompanion.insert(
|
||||||
matchId: match.id,
|
matchId: match.id,
|
||||||
playerId: p.id,
|
playerId: entry.key,
|
||||||
|
score: entry.value!.score,
|
||||||
|
roundNumber: entry.value!.roundNumber,
|
||||||
|
change: entry.value!.change,
|
||||||
),
|
),
|
||||||
mode: InsertMode.insertOrIgnore,
|
mode: InsertMode.insertOrReplace,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add all player-group associations in batch
|
// Add player-match associations
|
||||||
|
await db.batch((b) {
|
||||||
|
for (final match in matches) {
|
||||||
|
for (final p in match.players) {
|
||||||
|
b.insert(
|
||||||
|
db.playerMatchTable,
|
||||||
|
PlayerMatchTableCompanion.insert(
|
||||||
|
matchId: match.id,
|
||||||
|
playerId: p.id,
|
||||||
|
),
|
||||||
|
mode: InsertMode.insertOrIgnore,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add player-group associations
|
||||||
await db.batch((b) {
|
await db.batch((b) {
|
||||||
for (final match in matches) {
|
for (final match in matches) {
|
||||||
if (match.group != null) {
|
if (match.group != null) {
|
||||||
@@ -215,30 +241,27 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add all group-match associations in batch
|
// Add teams for matches
|
||||||
await db.batch((b) {
|
for (final match in matches) {
|
||||||
for (final match in matches) {
|
if (match.teams != null && match.teams!.isNotEmpty) {
|
||||||
if (match.group != null) {
|
await db.teamDao.addTeamsAsList(
|
||||||
b.insert(
|
teams: match.teams!,
|
||||||
db.groupMatchTable,
|
matchId: match.id,
|
||||||
GroupMatchTableCompanion.insert(
|
);
|
||||||
matchId: match.id,
|
|
||||||
groupId: match.group!.id,
|
|
||||||
),
|
|
||||||
mode: InsertMode.insertOrIgnore,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes the match with the given [matchId] from the database.
|
/* Read */
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
|
||||||
Future<bool> deleteMatch({required String matchId}) async {
|
/// Checks if a match with the given [matchId] exists in the database.
|
||||||
final query = delete(matchTable)..where((g) => g.id.equals(matchId));
|
/// Returns `true` if the match exists, otherwise `false`.
|
||||||
final rowsAffected = await query.go();
|
Future<bool> matchExists({required String matchId}) async {
|
||||||
return rowsAffected > 0;
|
final query = select(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
|
final result = await query.getSingleOrNull();
|
||||||
|
return result != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves the number of matches in the database.
|
/// Retrieves the number of matches in the database.
|
||||||
@@ -250,12 +273,219 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
return count ?? 0;
|
return count ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if a match with the given [matchId] exists in the database.
|
/// Retrieves all matches from the database.
|
||||||
/// Returns `true` if the match exists, otherwise `false`.
|
Future<List<Match>> getAllMatches() async {
|
||||||
Future<bool> matchExists({required String matchId}) async {
|
final query = select(matchTable);
|
||||||
|
final result = await query.get();
|
||||||
|
|
||||||
|
return Future.wait(
|
||||||
|
result.map((row) async {
|
||||||
|
final game = await db.gameDao.getGameById(gameId: row.gameId);
|
||||||
|
Group? group;
|
||||||
|
if (row.groupId != null) {
|
||||||
|
group = await db.groupDao.getGroupById(groupId: row.groupId!);
|
||||||
|
}
|
||||||
|
final players = await db.playerMatchDao.getPlayersOfMatch(
|
||||||
|
matchId: row.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
final scores = await db.scoreEntryDao.getAllMatchScores(
|
||||||
|
matchId: row.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
final teams = await _getMatchTeams(matchId: row.id);
|
||||||
|
|
||||||
|
return Match(
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
game: game,
|
||||||
|
group: group,
|
||||||
|
players: players,
|
||||||
|
teams: teams.isEmpty ? null : teams,
|
||||||
|
isTeamMatch: row.isTeamMatch,
|
||||||
|
notes: row.notes,
|
||||||
|
createdAt: row.createdAt,
|
||||||
|
endedAt: row.endedAt,
|
||||||
|
scores: scores,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves a [Match] by its [matchId].
|
||||||
|
Future<Match> getMatchById({required String matchId}) async {
|
||||||
final query = select(matchTable)..where((g) => g.id.equals(matchId));
|
final query = select(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
final result = await query.getSingleOrNull();
|
final result = await query.getSingle();
|
||||||
return result != null;
|
|
||||||
|
final game = await db.gameDao.getGameById(gameId: result.gameId);
|
||||||
|
|
||||||
|
Group? group;
|
||||||
|
if (result.groupId != null) {
|
||||||
|
group = await db.groupDao.getGroupById(groupId: result.groupId!);
|
||||||
|
}
|
||||||
|
|
||||||
|
final players = await db.playerMatchDao.getPlayersOfMatch(matchId: matchId);
|
||||||
|
|
||||||
|
final scores = await db.scoreEntryDao.getAllMatchScores(matchId: matchId);
|
||||||
|
|
||||||
|
final teams = await _getMatchTeams(matchId: matchId);
|
||||||
|
|
||||||
|
return Match(
|
||||||
|
id: result.id,
|
||||||
|
name: result.name,
|
||||||
|
game: game,
|
||||||
|
group: group,
|
||||||
|
players: players,
|
||||||
|
teams: teams.isEmpty ? null : teams,
|
||||||
|
isTeamMatch: result.isTeamMatch,
|
||||||
|
notes: result.notes,
|
||||||
|
createdAt: result.createdAt,
|
||||||
|
endedAt: result.endedAt,
|
||||||
|
scores: scores,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the number of matches associated with a specific game.
|
||||||
|
Future<int> getMatchCountByGame({required String gameId}) async {
|
||||||
|
final count =
|
||||||
|
await (selectOnly(matchTable)
|
||||||
|
..where(matchTable.gameId.equals(gameId))
|
||||||
|
..addColumns([matchTable.id.count()]))
|
||||||
|
.map((row) => row.read(matchTable.id.count()))
|
||||||
|
.getSingle();
|
||||||
|
return count ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves all matches associated with the given [groupId].
|
||||||
|
/// Queries the database directly, filtering by [groupId].
|
||||||
|
Future<List<Match>> getMatchesByGroup({required String groupId}) async {
|
||||||
|
final query = select(matchTable)..where((m) => m.groupId.equals(groupId));
|
||||||
|
final rows = await query.get();
|
||||||
|
|
||||||
|
return Future.wait(
|
||||||
|
rows.map((row) async {
|
||||||
|
final game = await db.gameDao.getGameById(gameId: row.gameId);
|
||||||
|
final group = await db.groupDao.getGroupById(groupId: groupId);
|
||||||
|
final players = await db.playerMatchDao.getPlayersOfMatch(
|
||||||
|
matchId: row.id,
|
||||||
|
);
|
||||||
|
final teams = await _getMatchTeams(matchId: row.id);
|
||||||
|
return Match(
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
game: game,
|
||||||
|
group: group,
|
||||||
|
players: players,
|
||||||
|
teams: teams.isEmpty ? null : teams,
|
||||||
|
isTeamMatch: row.isTeamMatch,
|
||||||
|
notes: row.notes,
|
||||||
|
createdAt: row.createdAt,
|
||||||
|
endedAt: row.endedAt,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper method to retrieve teams for a specific match
|
||||||
|
Future<List<Team>> _getMatchTeams({required String matchId}) async {
|
||||||
|
// Get all unique team IDs from PlayerMatchTable for this match
|
||||||
|
final playerMatchQuery = select(db.playerMatchTable)
|
||||||
|
..where((pm) => pm.matchId.equals(matchId) & pm.teamId.isNotNull());
|
||||||
|
final playerMatches = await playerMatchQuery.get();
|
||||||
|
|
||||||
|
if (playerMatches.isEmpty) return [];
|
||||||
|
|
||||||
|
final teamIds = playerMatches
|
||||||
|
.map((pm) => pm.teamId)
|
||||||
|
.whereType<String>()
|
||||||
|
.toSet()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Fetch all teams
|
||||||
|
final teams = await Future.wait(
|
||||||
|
teamIds.map((teamId) => db.teamDao.getTeamById(teamId: teamId)),
|
||||||
|
);
|
||||||
|
|
||||||
|
return teams
|
||||||
|
..sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update */
|
||||||
|
|
||||||
|
/// Changes the name of the match with the given [matchId] to [name].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> updateMatchName({
|
||||||
|
required String matchId,
|
||||||
|
required String name,
|
||||||
|
}) async {
|
||||||
|
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
|
final rowsAffected = await query.write(
|
||||||
|
MatchTableCompanion(name: Value(name)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the group of the match with the given [matchId].
|
||||||
|
/// Replaces the existing group association with the new group specified by [groupId].
|
||||||
|
/// Pass null to remove the group association.
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> updateMatchGroup({
|
||||||
|
required String matchId,
|
||||||
|
required String? groupId,
|
||||||
|
}) async {
|
||||||
|
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
|
final rowsAffected = await query.write(
|
||||||
|
MatchTableCompanion(groupId: Value(groupId)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the notes of the match with the given [matchId].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> updateMatchNotes({
|
||||||
|
required String matchId,
|
||||||
|
required String notes,
|
||||||
|
}) async {
|
||||||
|
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
|
final rowsAffected = await query.write(
|
||||||
|
MatchTableCompanion(notes: Value(notes)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the group association of the match with the given [matchId].
|
||||||
|
/// Sets the groupId to null.
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> removeMatchGroup({required String matchId}) async {
|
||||||
|
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
|
final rowsAffected = await query.write(
|
||||||
|
const MatchTableCompanion(groupId: Value(null)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the endedAt timestamp of the match with the given [matchId].
|
||||||
|
/// Pass null to remove the ended time (mark match as ongoing).
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> updateMatchEndedAt({
|
||||||
|
required String matchId,
|
||||||
|
required DateTime endedAt,
|
||||||
|
}) async {
|
||||||
|
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
|
final rowsAffected = await query.write(
|
||||||
|
MatchTableCompanion(endedAt: Value(endedAt)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delete */
|
||||||
|
|
||||||
|
/// Deletes the match with the given [matchId] from the database.
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> deleteMatch({required String matchId}) async {
|
||||||
|
final query = delete(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes all matches from the database.
|
/// Deletes all matches from the database.
|
||||||
@@ -266,61 +496,11 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
return rowsAffected > 0;
|
return rowsAffected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the winner of the match with the given [matchId] to the player with
|
/// Deletes all matches associated with a specific game.
|
||||||
/// the given [winnerId].
|
/// Returns the number of matches deleted.
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
Future<int> deleteMatchesByGame({required String gameId}) async {
|
||||||
Future<bool> setWinner({
|
final query = delete(matchTable)..where((m) => m.gameId.equals(gameId));
|
||||||
required String matchId,
|
final rowsAffected = await query.go();
|
||||||
required String winnerId,
|
return rowsAffected;
|
||||||
}) async {
|
|
||||||
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
|
||||||
final rowsAffected = await query.write(
|
|
||||||
MatchTableCompanion(winnerId: Value(winnerId)),
|
|
||||||
);
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves the winner of the match with the given [matchId].
|
|
||||||
/// Returns the [Player] who won the match, or `null` if no winner is set.
|
|
||||||
Future<Player?> getWinner({required String matchId}) async {
|
|
||||||
final query = select(matchTable)..where((g) => g.id.equals(matchId));
|
|
||||||
final result = await query.getSingleOrNull();
|
|
||||||
if (result == null || result.winnerId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final winner = await db.playerDao.getPlayerById(playerId: result.winnerId!);
|
|
||||||
return winner;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes the winner of the match with the given [matchId].
|
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
|
||||||
Future<bool> removeWinner({required String matchId}) async {
|
|
||||||
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
|
||||||
final rowsAffected = await query.write(
|
|
||||||
const MatchTableCompanion(winnerId: Value(null)),
|
|
||||||
);
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if the match with the given [matchId] has a winner set.
|
|
||||||
/// Returns `true` if a winner is set, otherwise `false`.
|
|
||||||
Future<bool> hasWinner({required String matchId}) async {
|
|
||||||
final query = select(matchTable)
|
|
||||||
..where((g) => g.id.equals(matchId) & g.winnerId.isNotNull());
|
|
||||||
final result = await query.getSingleOrNull();
|
|
||||||
return result != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Changes the title of the match with the given [matchId] to [newName].
|
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
|
||||||
Future<bool> updateMatchName({
|
|
||||||
required String matchId,
|
|
||||||
required String newName,
|
|
||||||
}) async {
|
|
||||||
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
|
||||||
final rowsAffected = await query.write(
|
|
||||||
MatchTableCompanion(name: Value(newName)),
|
|
||||||
);
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,32 @@ part of 'match_dao.dart';
|
|||||||
|
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
mixin _$MatchDaoMixin on DatabaseAccessor<AppDatabase> {
|
mixin _$MatchDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||||
|
$GameTableTable get gameTable => attachedDatabase.gameTable;
|
||||||
|
$GroupTableTable get groupTable => attachedDatabase.groupTable;
|
||||||
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
||||||
|
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
||||||
|
$TeamTableTable get teamTable => attachedDatabase.teamTable;
|
||||||
|
$PlayerMatchTableTable get playerMatchTable =>
|
||||||
|
attachedDatabase.playerMatchTable;
|
||||||
|
MatchDaoManager get managers => MatchDaoManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MatchDaoManager {
|
||||||
|
final _$MatchDaoMixin _db;
|
||||||
|
MatchDaoManager(this._db);
|
||||||
|
$$GameTableTableTableManager get gameTable =>
|
||||||
|
$$GameTableTableTableManager(_db.attachedDatabase, _db.gameTable);
|
||||||
|
$$GroupTableTableTableManager get groupTable =>
|
||||||
|
$$GroupTableTableTableManager(_db.attachedDatabase, _db.groupTable);
|
||||||
|
$$MatchTableTableTableManager get matchTable =>
|
||||||
|
$$MatchTableTableTableManager(_db.attachedDatabase, _db.matchTable);
|
||||||
|
$$PlayerTableTableTableManager get playerTable =>
|
||||||
|
$$PlayerTableTableTableManager(_db.attachedDatabase, _db.playerTable);
|
||||||
|
$$TeamTableTableTableManager get teamTable =>
|
||||||
|
$$TeamTableTableTableManager(_db.attachedDatabase, _db.teamTable);
|
||||||
|
$$PlayerMatchTableTableTableManager get playerMatchTable =>
|
||||||
|
$$PlayerMatchTableTableTableManager(
|
||||||
|
_db.attachedDatabase,
|
||||||
|
_db.playerMatchTable,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:game_tracker/data/db/database.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:game_tracker/data/db/tables/player_table.dart';
|
import 'package:tallee/data/db/database.dart';
|
||||||
import 'package:game_tracker/data/dto/player.dart';
|
import 'package:tallee/data/db/tables/player_table.dart';
|
||||||
|
import 'package:tallee/data/models/player.dart';
|
||||||
|
|
||||||
part 'player_dao.g.dart';
|
part 'player_dao.g.dart';
|
||||||
|
|
||||||
@@ -9,38 +10,22 @@ part 'player_dao.g.dart';
|
|||||||
class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
||||||
PlayerDao(super.db);
|
PlayerDao(super.db);
|
||||||
|
|
||||||
/// Retrieves all players from the database.
|
/* Create */
|
||||||
Future<List<Player>> getAllPlayers() async {
|
|
||||||
final query = select(playerTable);
|
|
||||||
final result = await query.get();
|
|
||||||
return result
|
|
||||||
.map(
|
|
||||||
(row) => Player(id: row.id, name: row.name, createdAt: row.createdAt),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves a [Player] by their [id].
|
|
||||||
Future<Player> getPlayerById({required String playerId}) async {
|
|
||||||
final query = select(playerTable)..where((p) => p.id.equals(playerId));
|
|
||||||
final result = await query.getSingle();
|
|
||||||
return Player(
|
|
||||||
id: result.id,
|
|
||||||
name: result.name,
|
|
||||||
createdAt: result.createdAt,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a new [player] to the database.
|
/// Adds a new [player] to the database.
|
||||||
/// If a player with the same ID already exists, updates their name to
|
/// If a player with the same ID already exists, updates their name to
|
||||||
/// the new one.
|
/// the new one.
|
||||||
Future<bool> addPlayer({required Player player}) async {
|
Future<bool> addPlayer({required Player player}) async {
|
||||||
if (!await playerExists(playerId: player.id)) {
|
if (!await playerExists(playerId: player.id)) {
|
||||||
|
final int nameCount = await calculateNameCount(name: player.name);
|
||||||
|
|
||||||
await into(playerTable).insert(
|
await into(playerTable).insert(
|
||||||
PlayerTableCompanion.insert(
|
PlayerTableCompanion.insert(
|
||||||
id: player.id,
|
id: player.id,
|
||||||
name: player.name,
|
name: player.name,
|
||||||
|
description: player.description,
|
||||||
createdAt: player.createdAt,
|
createdAt: player.createdAt,
|
||||||
|
nameCount: Value(nameCount),
|
||||||
),
|
),
|
||||||
mode: InsertMode.insertOrReplace,
|
mode: InsertMode.insertOrReplace,
|
||||||
);
|
);
|
||||||
@@ -55,50 +40,74 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
|||||||
Future<bool> addPlayersAsList({required List<Player> players}) async {
|
Future<bool> addPlayersAsList({required List<Player> players}) async {
|
||||||
if (players.isEmpty) return false;
|
if (players.isEmpty) return false;
|
||||||
|
|
||||||
|
// Filter out players that already exist
|
||||||
|
final newPlayers = <Player>[];
|
||||||
|
for (final player in players) {
|
||||||
|
if (!await playerExists(playerId: player.id)) {
|
||||||
|
newPlayers.add(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPlayers.isEmpty) return false;
|
||||||
|
|
||||||
|
// Group players by name
|
||||||
|
final nameGroups = <String, List<Player>>{};
|
||||||
|
for (final player in newPlayers) {
|
||||||
|
nameGroups.putIfAbsent(player.name, () => []).add(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
final playersToInsert = <PlayerTableCompanion>[];
|
||||||
|
|
||||||
|
// Process each group of players with the same name
|
||||||
|
for (final entry in nameGroups.entries) {
|
||||||
|
final name = entry.key;
|
||||||
|
final playersWithName = entry.value;
|
||||||
|
|
||||||
|
// Get the current nameCount
|
||||||
|
var nameCount = await calculateNameCount(name: name);
|
||||||
|
|
||||||
|
// One player with the same name
|
||||||
|
if (playersWithName.length == 1) {
|
||||||
|
final player = playersWithName[0];
|
||||||
|
playersToInsert.add(
|
||||||
|
PlayerTableCompanion.insert(
|
||||||
|
id: player.id,
|
||||||
|
name: player.name,
|
||||||
|
description: player.description,
|
||||||
|
createdAt: player.createdAt,
|
||||||
|
nameCount: Value(nameCount),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (nameCount == 0) nameCount++;
|
||||||
|
// Multiple players with the same name
|
||||||
|
for (var i = 0; i < playersWithName.length; i++) {
|
||||||
|
final player = playersWithName[i];
|
||||||
|
playersToInsert.add(
|
||||||
|
PlayerTableCompanion.insert(
|
||||||
|
id: player.id,
|
||||||
|
name: player.name,
|
||||||
|
description: player.description,
|
||||||
|
createdAt: player.createdAt,
|
||||||
|
nameCount: Value(nameCount + i),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await db.batch(
|
await db.batch(
|
||||||
(b) => b.insertAll(
|
(b) => b.insertAll(
|
||||||
playerTable,
|
playerTable,
|
||||||
players
|
playersToInsert,
|
||||||
.map(
|
mode: InsertMode.insertOrReplace,
|
||||||
(player) => PlayerTableCompanion.insert(
|
|
||||||
id: player.id,
|
|
||||||
name: player.name,
|
|
||||||
createdAt: player.createdAt,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
mode: InsertMode.insertOrIgnore,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes the player with the given [id] from the database.
|
/* Read */
|
||||||
/// Returns `true` if the player was deleted, `false` if the player did not exist.
|
|
||||||
Future<bool> deletePlayer({required String playerId}) async {
|
|
||||||
final query = delete(playerTable)..where((p) => p.id.equals(playerId));
|
|
||||||
final rowsAffected = await query.go();
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if a player with the given [id] exists in the database.
|
|
||||||
/// Returns `true` if the player exists, `false` otherwise.
|
|
||||||
Future<bool> playerExists({required String playerId}) async {
|
|
||||||
final query = select(playerTable)..where((p) => p.id.equals(playerId));
|
|
||||||
final result = await query.getSingleOrNull();
|
|
||||||
return result != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the name of the player with the given [playerId] to [newName].
|
|
||||||
Future<void> updatePlayername({
|
|
||||||
required String playerId,
|
|
||||||
required String newName,
|
|
||||||
}) async {
|
|
||||||
await (update(playerTable)..where((p) => p.id.equals(playerId))).write(
|
|
||||||
PlayerTableCompanion(name: Value(newName)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves the total count of players in the database.
|
/// Retrieves the total count of players in the database.
|
||||||
Future<int> getPlayerCount() async {
|
Future<int> getPlayerCount() async {
|
||||||
@@ -109,6 +118,183 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
|||||||
return count ?? 0;
|
return count ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if a player with the given [playerId] exists in the database.
|
||||||
|
/// Returns `true` if the player exists, `false` otherwise.
|
||||||
|
Future<bool> playerExists({required String playerId}) async {
|
||||||
|
final query = select(playerTable)..where((p) => p.id.equals(playerId));
|
||||||
|
final result = await query.getSingleOrNull();
|
||||||
|
return result != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves all players from the database.
|
||||||
|
Future<List<Player>> getAllPlayers() async {
|
||||||
|
final query = select(playerTable);
|
||||||
|
final result = await query.get();
|
||||||
|
return result
|
||||||
|
.map(
|
||||||
|
(row) => Player(
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
description: row.description,
|
||||||
|
createdAt: row.createdAt,
|
||||||
|
nameCount: row.nameCount,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves a [Player] by their [id].
|
||||||
|
Future<Player> getPlayerById({required String playerId}) async {
|
||||||
|
final query = select(playerTable)..where((p) => p.id.equals(playerId));
|
||||||
|
final result = await query.getSingle();
|
||||||
|
return Player(
|
||||||
|
id: result.id,
|
||||||
|
name: result.name,
|
||||||
|
description: result.description,
|
||||||
|
createdAt: result.createdAt,
|
||||||
|
nameCount: result.nameCount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update */
|
||||||
|
|
||||||
|
/// Updates the name of the player with the given [playerId] to [name].
|
||||||
|
Future<bool> updatePlayerName({
|
||||||
|
required String playerId,
|
||||||
|
required String name,
|
||||||
|
}) async {
|
||||||
|
// Get previous name and name count for the player before updating
|
||||||
|
final previousPlayerName =
|
||||||
|
await (select(playerTable)..where((p) => p.id.equals(playerId)))
|
||||||
|
.map((row) => row.name)
|
||||||
|
.getSingleOrNull() ??
|
||||||
|
'';
|
||||||
|
final previousNameCount = await getNameCount(name: previousPlayerName);
|
||||||
|
|
||||||
|
final rowsAffected =
|
||||||
|
await (update(playerTable)..where((p) => p.id.equals(playerId))).write(
|
||||||
|
PlayerTableCompanion(name: Value(name)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update name count for the new name
|
||||||
|
final count = await calculateNameCount(name: name);
|
||||||
|
if (count > 0) {
|
||||||
|
await (update(playerTable)..where((p) => p.name.equals(name))).write(
|
||||||
|
PlayerTableCompanion(nameCount: Value(count)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousNameCount > 0) {
|
||||||
|
// Get the player with that name and the hightest nameCount, and update their nameCount to previousNameCount
|
||||||
|
final player = await getPlayerWithHighestNameCount(
|
||||||
|
name: previousPlayerName,
|
||||||
|
);
|
||||||
|
if (player != null) {
|
||||||
|
await updateNameCount(
|
||||||
|
playerId: player.id,
|
||||||
|
nameCount: previousNameCount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the description of the player with the given [playerId] to
|
||||||
|
/// [description].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> updatePlayerDescription({
|
||||||
|
required String playerId,
|
||||||
|
required String description,
|
||||||
|
}) async {
|
||||||
|
final rowsAffected =
|
||||||
|
await (update(playerTable)..where((g) => g.id.equals(playerId))).write(
|
||||||
|
PlayerTableCompanion(description: Value(description)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delete */
|
||||||
|
|
||||||
|
/// Deletes the player with the given [id] from the database.
|
||||||
|
/// Returns `true` if the player was deleted, `false` if the player did not exist.
|
||||||
|
Future<bool> deletePlayer({required String playerId}) async {
|
||||||
|
final query = delete(playerTable)..where((p) => p.id.equals(playerId));
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name count management */
|
||||||
|
|
||||||
|
/// Retrieves the count of players with the given [name].
|
||||||
|
Future<int> getNameCount({required String name}) async {
|
||||||
|
final query = select(playerTable)..where((p) => p.name.equals(name));
|
||||||
|
final result = await query.get();
|
||||||
|
return result.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the nameCount for the player with the given [playerId] to [nameCount].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> updateNameCount({
|
||||||
|
required String playerId,
|
||||||
|
required int nameCount,
|
||||||
|
}) async {
|
||||||
|
final query = update(playerTable)..where((p) => p.id.equals(playerId));
|
||||||
|
final rowsAffected = await query.write(
|
||||||
|
PlayerTableCompanion(nameCount: Value(nameCount)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
Future<Player?> getPlayerWithHighestNameCount({required String name}) async {
|
||||||
|
final query = select(playerTable)
|
||||||
|
..where((p) => p.name.equals(name))
|
||||||
|
..orderBy([(p) => OrderingTerm.desc(p.nameCount)])
|
||||||
|
..limit(1);
|
||||||
|
final result = await query.getSingleOrNull();
|
||||||
|
if (result != null) {
|
||||||
|
return Player(
|
||||||
|
id: result.id,
|
||||||
|
name: result.name,
|
||||||
|
description: result.description,
|
||||||
|
createdAt: result.createdAt,
|
||||||
|
nameCount: result.nameCount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
Future<int> calculateNameCount({required String name}) async {
|
||||||
|
final count = await getNameCount(name: name);
|
||||||
|
final int nameCount;
|
||||||
|
|
||||||
|
if (count == 1) {
|
||||||
|
// If one other player exists with the same name, initialize the nameCount
|
||||||
|
await initializeNameCount(name: name);
|
||||||
|
// And for the new player, set nameCount to 2
|
||||||
|
nameCount = 2;
|
||||||
|
} else if (count > 1) {
|
||||||
|
// If more than one player exists with the same name, just increment
|
||||||
|
// the nameCount for the new player
|
||||||
|
nameCount = count + 1;
|
||||||
|
} else {
|
||||||
|
// If no other players exist with the same name, set nameCount to 0
|
||||||
|
nameCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nameCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
Future<bool> initializeNameCount({required String name}) async {
|
||||||
|
final rowsAffected =
|
||||||
|
await (update(playerTable)..where((p) => p.name.equals(name))).write(
|
||||||
|
const PlayerTableCompanion(nameCount: Value(1)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// Deletes all players from the database.
|
/// Deletes all players from the database.
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
Future<bool> deleteAllPlayers() async {
|
Future<bool> deleteAllPlayers() async {
|
||||||
|
|||||||
@@ -5,4 +5,12 @@ part of 'player_dao.dart';
|
|||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
mixin _$PlayerDaoMixin on DatabaseAccessor<AppDatabase> {
|
mixin _$PlayerDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||||
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
||||||
|
PlayerDaoManager get managers => PlayerDaoManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PlayerDaoManager {
|
||||||
|
final _$PlayerDaoMixin _db;
|
||||||
|
PlayerDaoManager(this._db);
|
||||||
|
$$PlayerTableTableTableManager get playerTable =>
|
||||||
|
$$PlayerTableTableTableManager(_db.attachedDatabase, _db.playerTable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:game_tracker/data/db/database.dart';
|
import 'package:tallee/data/db/database.dart';
|
||||||
import 'package:game_tracker/data/db/tables/player_group_table.dart';
|
import 'package:tallee/data/db/tables/player_group_table.dart';
|
||||||
import 'package:game_tracker/data/db/tables/player_table.dart';
|
import 'package:tallee/data/db/tables/player_table.dart';
|
||||||
import 'package:game_tracker/data/dto/player.dart';
|
import 'package:tallee/data/models/player.dart';
|
||||||
|
|
||||||
part 'player_group_dao.g.dart';
|
part 'player_group_dao.g.dart';
|
||||||
|
|
||||||
@@ -11,8 +11,7 @@ class PlayerGroupDao extends DatabaseAccessor<AppDatabase>
|
|||||||
with _$PlayerGroupDaoMixin {
|
with _$PlayerGroupDaoMixin {
|
||||||
PlayerGroupDao(super.db);
|
PlayerGroupDao(super.db);
|
||||||
|
|
||||||
/// No need for a groupHasPlayers method since the members attribute is
|
/* Create */
|
||||||
/// not nullable
|
|
||||||
|
|
||||||
/// Adds a [player] to a group with the given [groupId].
|
/// Adds a [player] to a group with the given [groupId].
|
||||||
/// If the player is already in the group, no action is taken.
|
/// If the player is already in the group, no action is taken.
|
||||||
@@ -27,16 +26,17 @@ class PlayerGroupDao extends DatabaseAccessor<AppDatabase>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!await db.playerDao.playerExists(playerId: player.id)) {
|
if (!await db.playerDao.playerExists(playerId: player.id)) {
|
||||||
db.playerDao.addPlayer(player: player);
|
await db.playerDao.addPlayer(player: player);
|
||||||
}
|
}
|
||||||
|
|
||||||
await into(playerGroupTable).insert(
|
await into(playerGroupTable).insert(
|
||||||
PlayerGroupTableCompanion.insert(playerId: player.id, groupId: groupId),
|
PlayerGroupTableCompanion.insert(playerId: player.id, groupId: groupId),
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Read */
|
||||||
|
|
||||||
/// Retrieves all players belonging to a specific group by [groupId].
|
/// Retrieves all players belonging to a specific group by [groupId].
|
||||||
Future<List<Player>> getPlayersOfGroup({required String groupId}) async {
|
Future<List<Player>> getPlayersOfGroup({required String groupId}) async {
|
||||||
final query = select(playerGroupTable)
|
final query = select(playerGroupTable)
|
||||||
@@ -53,18 +53,6 @@ class PlayerGroupDao extends DatabaseAccessor<AppDatabase>
|
|||||||
return groupMembers;
|
return groupMembers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a player from a group based on [playerId] and [groupId].
|
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
|
||||||
Future<bool> removePlayerFromGroup({
|
|
||||||
required String playerId,
|
|
||||||
required String groupId,
|
|
||||||
}) async {
|
|
||||||
final query = delete(playerGroupTable)
|
|
||||||
..where((p) => p.playerId.equals(playerId) & p.groupId.equals(groupId));
|
|
||||||
final rowsAffected = await query.go();
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if a player with [playerId] is in the group with [groupId].
|
/// Checks if a player with [playerId] is in the group with [groupId].
|
||||||
/// Returns `true` if the player is in the group, otherwise `false`.
|
/// Returns `true` if the player is in the group, otherwise `false`.
|
||||||
Future<bool> isPlayerInGroup({
|
Future<bool> isPlayerInGroup({
|
||||||
@@ -76,4 +64,65 @@ class PlayerGroupDao extends DatabaseAccessor<AppDatabase>
|
|||||||
final result = await query.getSingleOrNull();
|
final result = await query.getSingleOrNull();
|
||||||
return result != null;
|
return result != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Update */
|
||||||
|
|
||||||
|
/// Replaces all players in a group with the provided list of players.
|
||||||
|
/// Removes all existing players from the group and adds the new players.
|
||||||
|
/// Also adds any new players to the player table if they don't exist.
|
||||||
|
/// Returns `true` if the group exists and players were replaced, `false` otherwise.
|
||||||
|
Future<bool> replaceGroupPlayers({
|
||||||
|
required String groupId,
|
||||||
|
required List<Player> newPlayers,
|
||||||
|
}) async {
|
||||||
|
if (!await db.groupDao.groupExists(groupId: groupId)) return false;
|
||||||
|
if (newPlayers.isEmpty) return false;
|
||||||
|
|
||||||
|
await db.transaction(() async {
|
||||||
|
// Remove all existing players from the group
|
||||||
|
final deleteQuery = delete(db.playerGroupTable)
|
||||||
|
..where((p) => p.groupId.equals(groupId));
|
||||||
|
await deleteQuery.go();
|
||||||
|
|
||||||
|
// Add new players to the player table if they don't exist
|
||||||
|
await Future.wait(
|
||||||
|
newPlayers.map((player) async {
|
||||||
|
if (!await db.playerDao.playerExists(playerId: player.id)) {
|
||||||
|
await db.playerDao.addPlayer(player: player);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add the new players to the group
|
||||||
|
await db.batch(
|
||||||
|
(b) => b.insertAll(
|
||||||
|
db.playerGroupTable,
|
||||||
|
newPlayers
|
||||||
|
.map(
|
||||||
|
(player) => PlayerGroupTableCompanion.insert(
|
||||||
|
playerId: player.id,
|
||||||
|
groupId: groupId,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delete */
|
||||||
|
|
||||||
|
/// Removes a player from a group based on [playerId] and [groupId].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> removePlayerFromGroup({
|
||||||
|
required String playerId,
|
||||||
|
required String groupId,
|
||||||
|
}) async {
|
||||||
|
final query = delete(playerGroupTable)
|
||||||
|
..where((p) => p.playerId.equals(playerId) & p.groupId.equals(groupId));
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,4 +8,19 @@ mixin _$PlayerGroupDaoMixin on DatabaseAccessor<AppDatabase> {
|
|||||||
$GroupTableTable get groupTable => attachedDatabase.groupTable;
|
$GroupTableTable get groupTable => attachedDatabase.groupTable;
|
||||||
$PlayerGroupTableTable get playerGroupTable =>
|
$PlayerGroupTableTable get playerGroupTable =>
|
||||||
attachedDatabase.playerGroupTable;
|
attachedDatabase.playerGroupTable;
|
||||||
|
PlayerGroupDaoManager get managers => PlayerGroupDaoManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PlayerGroupDaoManager {
|
||||||
|
final _$PlayerGroupDaoMixin _db;
|
||||||
|
PlayerGroupDaoManager(this._db);
|
||||||
|
$$PlayerTableTableTableManager get playerTable =>
|
||||||
|
$$PlayerTableTableTableManager(_db.attachedDatabase, _db.playerTable);
|
||||||
|
$$GroupTableTableTableManager get groupTable =>
|
||||||
|
$$GroupTableTableTableManager(_db.attachedDatabase, _db.groupTable);
|
||||||
|
$$PlayerGroupTableTableTableManager get playerGroupTable =>
|
||||||
|
$$PlayerGroupTableTableTableManager(
|
||||||
|
_db.attachedDatabase,
|
||||||
|
_db.playerGroupTable,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,41 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:game_tracker/data/db/database.dart';
|
import 'package:tallee/data/db/database.dart';
|
||||||
import 'package:game_tracker/data/db/tables/player_match_table.dart';
|
import 'package:tallee/data/db/tables/player_match_table.dart';
|
||||||
import 'package:game_tracker/data/dto/player.dart';
|
import 'package:tallee/data/db/tables/team_table.dart';
|
||||||
|
import 'package:tallee/data/models/player.dart';
|
||||||
|
|
||||||
part 'player_match_dao.g.dart';
|
part 'player_match_dao.g.dart';
|
||||||
|
|
||||||
@DriftAccessor(tables: [PlayerMatchTable])
|
@DriftAccessor(tables: [PlayerMatchTable, TeamTable])
|
||||||
class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
|
class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
|
||||||
with _$PlayerMatchDaoMixin {
|
with _$PlayerMatchDaoMixin {
|
||||||
PlayerMatchDao(super.db);
|
PlayerMatchDao(super.db);
|
||||||
|
|
||||||
|
/* Create */
|
||||||
|
|
||||||
/// Associates a player with a match by inserting a record into the
|
/// Associates a player with a match by inserting a record into the
|
||||||
/// [PlayerMatchTable].
|
/// [PlayerMatchTable]. Optionally associates with a team and sets initial score.
|
||||||
Future<void> addPlayerToMatch({
|
Future<bool> addPlayerToMatch({
|
||||||
required String matchId,
|
required String matchId,
|
||||||
required String playerId,
|
required String playerId,
|
||||||
|
String? teamId,
|
||||||
}) async {
|
}) async {
|
||||||
await into(playerMatchTable).insert(
|
final rowsAffected = await into(playerMatchTable).insert(
|
||||||
PlayerMatchTableCompanion.insert(playerId: playerId, matchId: matchId),
|
PlayerMatchTableCompanion.insert(
|
||||||
mode: InsertMode.insertOrIgnore,
|
playerId: playerId,
|
||||||
|
matchId: matchId,
|
||||||
|
teamId: Value(teamId),
|
||||||
|
),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
);
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves a list of [Player]s associated with the given [matchId].
|
/* Read */
|
||||||
/// Returns null if no players are found.
|
|
||||||
Future<List<Player>?> getPlayersOfMatch({required String matchId}) async {
|
|
||||||
final result = await (select(
|
|
||||||
playerMatchTable,
|
|
||||||
)..where((p) => p.matchId.equals(matchId))).get();
|
|
||||||
|
|
||||||
if (result.isEmpty) return null;
|
|
||||||
|
|
||||||
final futures = result.map(
|
|
||||||
(row) => db.playerDao.getPlayerById(playerId: row.playerId),
|
|
||||||
);
|
|
||||||
final players = await Future.wait(futures);
|
|
||||||
return players;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if there are any players associated with the given [matchId].
|
/// Checks if there are any players associated with the given [matchId].
|
||||||
/// Returns `true` if there are players, otherwise `false`.
|
/// Returns `true` if there are players, otherwise `false`.
|
||||||
Future<bool> matchHasPlayers({required String matchId}) async {
|
Future<bool> hasMatchPlayers({required String matchId}) async {
|
||||||
final count =
|
final count =
|
||||||
await (selectOnly(playerMatchTable)
|
await (selectOnly(playerMatchTable)
|
||||||
..where(playerMatchTable.matchId.equals(matchId))
|
..where(playerMatchTable.matchId.equals(matchId))
|
||||||
@@ -66,37 +61,86 @@ class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
|
|||||||
return (count ?? 0) > 0;
|
return (count ?? 0) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the association of a player with a match by deleting the record
|
/// Retrieves a list of [Player]s associated with the given [matchId].
|
||||||
/// from the [PlayerMatchTable].
|
/// Returns empty list if no players are found.
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
Future<List<Player>> getPlayersOfMatch({required String matchId}) async {
|
||||||
Future<bool> removePlayerFromMatch({
|
final result = await (select(
|
||||||
|
playerMatchTable,
|
||||||
|
)..where((p) => p.matchId.equals(matchId))).get();
|
||||||
|
|
||||||
|
if (result.isEmpty) return [];
|
||||||
|
|
||||||
|
final futures = result.map(
|
||||||
|
(row) => db.playerDao.getPlayerById(playerId: row.playerId),
|
||||||
|
);
|
||||||
|
final players = await Future.wait(futures);
|
||||||
|
return players
|
||||||
|
..sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves a list of [Player]s associated with a specific team in a match.
|
||||||
|
/// Returns empty list if no players are found for the team in the match.
|
||||||
|
Future<List<Player>> getPlayersOfTeamInMatch({
|
||||||
|
required String matchId,
|
||||||
|
required String teamId,
|
||||||
|
}) async {
|
||||||
|
final result =
|
||||||
|
await (select(playerMatchTable)
|
||||||
|
..where((p) => p.matchId.equals(matchId))
|
||||||
|
..where((p) => p.teamId.equals(teamId)))
|
||||||
|
.get();
|
||||||
|
|
||||||
|
if (result.isEmpty) return [];
|
||||||
|
|
||||||
|
final futures = result.map(
|
||||||
|
(row) => db.playerDao.getPlayerById(playerId: row.playerId),
|
||||||
|
);
|
||||||
|
final players = await Future.wait(futures);
|
||||||
|
return players;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Updated */
|
||||||
|
|
||||||
|
/// Updates the team for a player in a match.
|
||||||
|
/// Returns `true` if the update was successful, otherwise `false`.
|
||||||
|
Future<bool> updatePlayersTeam({
|
||||||
required String matchId,
|
required String matchId,
|
||||||
required String playerId,
|
required String playerId,
|
||||||
|
required String? teamId,
|
||||||
}) async {
|
}) async {
|
||||||
final query = delete(playerMatchTable)
|
final rowsAffected =
|
||||||
..where((pg) => pg.matchId.equals(matchId))
|
await (update(playerMatchTable)..where(
|
||||||
..where((pg) => pg.playerId.equals(playerId));
|
(p) => p.matchId.equals(matchId) & p.playerId.equals(playerId),
|
||||||
final rowsAffected = await query.go();
|
))
|
||||||
|
.write(PlayerMatchTableCompanion(teamId: Value(teamId)));
|
||||||
return rowsAffected > 0;
|
return rowsAffected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the players associated with a match based on the provided
|
/// Updates the players associated with a match based on the provided
|
||||||
/// [newPlayer] list. It adds new players and removes players that are no
|
/// [player] list. It adds new players and removes players that are no
|
||||||
/// longer associated with the match.
|
/// longer associated with the match.
|
||||||
Future<void> updatePlayersFromMatch({
|
Future<bool> updateMatchPlayers({
|
||||||
required String matchId,
|
required String matchId,
|
||||||
required List<Player> newPlayer,
|
required List<Player> player,
|
||||||
}) async {
|
}) async {
|
||||||
|
if (player.isEmpty) return false;
|
||||||
|
|
||||||
final currentPlayers = await getPlayersOfMatch(matchId: matchId);
|
final currentPlayers = await getPlayersOfMatch(matchId: matchId);
|
||||||
// Create sets of player IDs for easy comparison
|
// Create sets of player IDs for easy comparison
|
||||||
final currentPlayerIds = currentPlayers?.map((p) => p.id).toSet() ?? {};
|
final currentPlayerIds = currentPlayers.map((p) => p.id).toSet();
|
||||||
final newPlayerIdsSet = newPlayer.map((p) => p.id).toSet();
|
final newPlayerIdsSet = player.map((p) => p.id).toSet();
|
||||||
|
|
||||||
|
// Are the current and new player identical?
|
||||||
|
if (currentPlayerIds.containsAll(newPlayerIdsSet) &&
|
||||||
|
newPlayerIdsSet.containsAll(currentPlayerIds)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Determine players to add and remove
|
// Determine players to add and remove
|
||||||
final playersToAdd = newPlayerIdsSet.difference(currentPlayerIds);
|
final playersToAdd = newPlayerIdsSet.difference(currentPlayerIds);
|
||||||
final playersToRemove = currentPlayerIds.difference(newPlayerIdsSet);
|
final playersToRemove = currentPlayerIds.difference(newPlayerIdsSet);
|
||||||
|
|
||||||
db.transaction(() async {
|
await db.transaction(() async {
|
||||||
// Remove old players
|
// Remove old players
|
||||||
if (playersToRemove.isNotEmpty) {
|
if (playersToRemove.isNotEmpty) {
|
||||||
await (delete(playerMatchTable)..where(
|
await (delete(playerMatchTable)..where(
|
||||||
@@ -126,5 +170,22 @@ class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delete */
|
||||||
|
|
||||||
|
/// Removes the association of a player with a match by deleting the record
|
||||||
|
/// from the [PlayerMatchTable].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> removePlayerFromMatch({
|
||||||
|
required String matchId,
|
||||||
|
required String playerId,
|
||||||
|
}) async {
|
||||||
|
final query = delete(playerMatchTable)
|
||||||
|
..where((pg) => pg.matchId.equals(matchId))
|
||||||
|
..where((pg) => pg.playerId.equals(playerId));
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,31 @@ part of 'player_match_dao.dart';
|
|||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
mixin _$PlayerMatchDaoMixin on DatabaseAccessor<AppDatabase> {
|
mixin _$PlayerMatchDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||||
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
||||||
|
$GameTableTable get gameTable => attachedDatabase.gameTable;
|
||||||
|
$GroupTableTable get groupTable => attachedDatabase.groupTable;
|
||||||
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
||||||
|
$TeamTableTable get teamTable => attachedDatabase.teamTable;
|
||||||
$PlayerMatchTableTable get playerMatchTable =>
|
$PlayerMatchTableTable get playerMatchTable =>
|
||||||
attachedDatabase.playerMatchTable;
|
attachedDatabase.playerMatchTable;
|
||||||
|
PlayerMatchDaoManager get managers => PlayerMatchDaoManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PlayerMatchDaoManager {
|
||||||
|
final _$PlayerMatchDaoMixin _db;
|
||||||
|
PlayerMatchDaoManager(this._db);
|
||||||
|
$$PlayerTableTableTableManager get playerTable =>
|
||||||
|
$$PlayerTableTableTableManager(_db.attachedDatabase, _db.playerTable);
|
||||||
|
$$GameTableTableTableManager get gameTable =>
|
||||||
|
$$GameTableTableTableManager(_db.attachedDatabase, _db.gameTable);
|
||||||
|
$$GroupTableTableTableManager get groupTable =>
|
||||||
|
$$GroupTableTableTableManager(_db.attachedDatabase, _db.groupTable);
|
||||||
|
$$MatchTableTableTableManager get matchTable =>
|
||||||
|
$$MatchTableTableTableManager(_db.attachedDatabase, _db.matchTable);
|
||||||
|
$$TeamTableTableTableManager get teamTable =>
|
||||||
|
$$TeamTableTableTableManager(_db.attachedDatabase, _db.teamTable);
|
||||||
|
$$PlayerMatchTableTableTableManager get playerMatchTable =>
|
||||||
|
$$PlayerMatchTableTableTableManager(
|
||||||
|
_db.attachedDatabase,
|
||||||
|
_db.playerMatchTable,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
404
lib/data/dao/score_entry_dao.dart
Normal file
@@ -0,0 +1,404 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:tallee/data/db/database.dart';
|
||||||
|
import 'package:tallee/data/db/tables/score_entry_table.dart';
|
||||||
|
import 'package:tallee/data/models/player.dart';
|
||||||
|
import 'package:tallee/data/models/score_entry.dart';
|
||||||
|
|
||||||
|
part 'score_entry_dao.g.dart';
|
||||||
|
|
||||||
|
@DriftAccessor(tables: [ScoreEntryTable])
|
||||||
|
class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
|
||||||
|
with _$ScoreEntryDaoMixin {
|
||||||
|
ScoreEntryDao(super.db);
|
||||||
|
|
||||||
|
/* Create */
|
||||||
|
|
||||||
|
/// Adds a score entry to the database.
|
||||||
|
Future<bool> addScore({
|
||||||
|
required String playerId,
|
||||||
|
required String matchId,
|
||||||
|
required ScoreEntry entry,
|
||||||
|
}) async {
|
||||||
|
final rowsAffected = await into(scoreEntryTable).insert(
|
||||||
|
ScoreEntryTableCompanion.insert(
|
||||||
|
playerId: playerId,
|
||||||
|
matchId: matchId,
|
||||||
|
roundNumber: entry.roundNumber,
|
||||||
|
score: entry.score,
|
||||||
|
change: entry.change,
|
||||||
|
),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addScoresAsList({
|
||||||
|
required List<ScoreEntry> entrys,
|
||||||
|
required String playerId,
|
||||||
|
required String matchId,
|
||||||
|
}) async {
|
||||||
|
if (entrys.isEmpty) return;
|
||||||
|
final entries = entrys
|
||||||
|
.map(
|
||||||
|
(score) => ScoreEntryTableCompanion.insert(
|
||||||
|
playerId: playerId,
|
||||||
|
matchId: matchId,
|
||||||
|
roundNumber: score.roundNumber,
|
||||||
|
score: score.score,
|
||||||
|
change: score.change,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
await batch((batch) {
|
||||||
|
batch.insertAll(
|
||||||
|
scoreEntryTable,
|
||||||
|
entries,
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read */
|
||||||
|
|
||||||
|
/// Retrieves the score for a specific round.
|
||||||
|
Future<ScoreEntry?> getScore({
|
||||||
|
required String playerId,
|
||||||
|
required String matchId,
|
||||||
|
int roundNumber = 0,
|
||||||
|
}) async {
|
||||||
|
final query = select(scoreEntryTable)
|
||||||
|
..where(
|
||||||
|
(s) =>
|
||||||
|
s.playerId.equals(playerId) &
|
||||||
|
s.matchId.equals(matchId) &
|
||||||
|
s.roundNumber.equals(roundNumber),
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = await query.getSingleOrNull();
|
||||||
|
if (result == null) return null;
|
||||||
|
|
||||||
|
return ScoreEntry(
|
||||||
|
roundNumber: result.roundNumber,
|
||||||
|
score: result.score,
|
||||||
|
change: result.change,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves all scores for a specific match.
|
||||||
|
Future<Map<String, ScoreEntry?>> getAllMatchScores({
|
||||||
|
required String matchId,
|
||||||
|
}) async {
|
||||||
|
final query = select(scoreEntryTable)
|
||||||
|
..where((s) => s.matchId.equals(matchId));
|
||||||
|
final result = await query.get();
|
||||||
|
|
||||||
|
final Map<String, ScoreEntry?> scoresByPlayer = {};
|
||||||
|
for (final row in result) {
|
||||||
|
final score = ScoreEntry(
|
||||||
|
roundNumber: row.roundNumber,
|
||||||
|
score: row.score,
|
||||||
|
change: row.change,
|
||||||
|
);
|
||||||
|
scoresByPlayer[row.playerId] = score;
|
||||||
|
}
|
||||||
|
|
||||||
|
return scoresByPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves all scores for a specific player in a match.
|
||||||
|
Future<List<ScoreEntry>> getAllPlayerScoresInMatch({
|
||||||
|
required String playerId,
|
||||||
|
required String matchId,
|
||||||
|
}) async {
|
||||||
|
final query = select(scoreEntryTable)
|
||||||
|
..where((s) => s.playerId.equals(playerId) & s.matchId.equals(matchId))
|
||||||
|
..orderBy([(s) => OrderingTerm.asc(s.roundNumber)]);
|
||||||
|
final result = await query.get();
|
||||||
|
return result
|
||||||
|
.map(
|
||||||
|
(row) => ScoreEntry(
|
||||||
|
roundNumber: row.roundNumber,
|
||||||
|
score: row.score,
|
||||||
|
change: row.change,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList()
|
||||||
|
..sort(
|
||||||
|
(scoreA, scoreB) => scoreA.roundNumber.compareTo(scoreB.roundNumber),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the highest (latest) round number for a match.
|
||||||
|
/// Returns `null` if there are no scores for the match.
|
||||||
|
Future<int?> getLatestRoundNumber({required String matchId}) async {
|
||||||
|
final query = selectOnly(scoreEntryTable)
|
||||||
|
..where(scoreEntryTable.matchId.equals(matchId))
|
||||||
|
..addColumns([scoreEntryTable.roundNumber.max()]);
|
||||||
|
final result = await query.getSingle();
|
||||||
|
return result.read(scoreEntryTable.roundNumber.max());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Aggregates the total score for a player in a match by summing all their
|
||||||
|
/// score entry changes. Returns `0` if there are no scores for the player
|
||||||
|
/// in the match.
|
||||||
|
Future<int> getTotalScoreForPlayer({
|
||||||
|
required String playerId,
|
||||||
|
required String matchId,
|
||||||
|
}) async {
|
||||||
|
final scores = await getAllPlayerScoresInMatch(
|
||||||
|
playerId: playerId,
|
||||||
|
matchId: matchId,
|
||||||
|
);
|
||||||
|
if (scores.isEmpty) return 0;
|
||||||
|
// Return the sum of all score changes
|
||||||
|
return scores.fold<int>(0, (sum, element) => sum + element.change);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update */
|
||||||
|
|
||||||
|
/// Updates a score entry.
|
||||||
|
Future<bool> updateScore({
|
||||||
|
required String playerId,
|
||||||
|
required String matchId,
|
||||||
|
required ScoreEntry entry,
|
||||||
|
}) async {
|
||||||
|
final rowsAffected =
|
||||||
|
await (update(scoreEntryTable)..where(
|
||||||
|
(s) =>
|
||||||
|
s.playerId.equals(playerId) &
|
||||||
|
s.matchId.equals(matchId) &
|
||||||
|
s.roundNumber.equals(entry.roundNumber),
|
||||||
|
))
|
||||||
|
.write(
|
||||||
|
ScoreEntryTableCompanion(
|
||||||
|
score: Value(entry.score),
|
||||||
|
change: Value(entry.change),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delete */
|
||||||
|
|
||||||
|
/// Deletes a score entry.
|
||||||
|
Future<bool> deleteScore({
|
||||||
|
required String playerId,
|
||||||
|
required String matchId,
|
||||||
|
int roundNumber = 0,
|
||||||
|
}) async {
|
||||||
|
final query = delete(scoreEntryTable)
|
||||||
|
..where(
|
||||||
|
(s) =>
|
||||||
|
s.playerId.equals(playerId) &
|
||||||
|
s.matchId.equals(matchId) &
|
||||||
|
s.roundNumber.equals(roundNumber),
|
||||||
|
);
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> deleteAllScoresForMatch({required String matchId}) async {
|
||||||
|
final query = delete(scoreEntryTable)
|
||||||
|
..where((s) => s.matchId.equals(matchId));
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> deleteAllScoresForPlayerInMatch({
|
||||||
|
required String matchId,
|
||||||
|
required String playerId,
|
||||||
|
}) async {
|
||||||
|
final query = delete(scoreEntryTable)
|
||||||
|
..where((s) => s.playerId.equals(playerId) & s.matchId.equals(matchId));
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Winner handling */
|
||||||
|
|
||||||
|
Future<bool> hasWinner({required String matchId}) async {
|
||||||
|
return await getWinner(matchId: matchId) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting the winner for a game and clearing previous winner if exists.
|
||||||
|
Future<bool> setWinner({
|
||||||
|
required String matchId,
|
||||||
|
required String playerId,
|
||||||
|
}) async {
|
||||||
|
// Clear previous winner if exists
|
||||||
|
await deleteAllScoresForMatch(matchId: matchId);
|
||||||
|
|
||||||
|
// Set the winner's score to 1
|
||||||
|
final rowsAffected = await into(scoreEntryTable).insert(
|
||||||
|
ScoreEntryTableCompanion.insert(
|
||||||
|
playerId: playerId,
|
||||||
|
matchId: matchId,
|
||||||
|
roundNumber: 0,
|
||||||
|
score: 1,
|
||||||
|
change: 0,
|
||||||
|
),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the winner of a match by looking for a score entry where score
|
||||||
|
/// is 1. Returns `null` if no player found, else the first with the score.
|
||||||
|
Future<Player?> getWinner({required String matchId}) async {
|
||||||
|
final query =
|
||||||
|
select(scoreEntryTable).join([
|
||||||
|
innerJoin(
|
||||||
|
db.playerTable,
|
||||||
|
db.playerTable.id.equalsExp(scoreEntryTable.playerId),
|
||||||
|
),
|
||||||
|
])..where(
|
||||||
|
scoreEntryTable.matchId.equals(matchId) &
|
||||||
|
scoreEntryTable.score.equals(1),
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = await query.get();
|
||||||
|
if (result.isEmpty) return null;
|
||||||
|
|
||||||
|
final playerData = result.first.readTable(db.playerTable);
|
||||||
|
return Player(
|
||||||
|
id: playerData.id,
|
||||||
|
name: playerData.name,
|
||||||
|
createdAt: playerData.createdAt,
|
||||||
|
description: playerData.description,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the winner of a match.
|
||||||
|
///
|
||||||
|
/// Returns `true` if the winner was removed, `false` if there are multiple
|
||||||
|
/// scores or if the winner cannot be removed.
|
||||||
|
Future<bool> removeWinner({required String matchId}) async {
|
||||||
|
return await deleteAllScoresForMatch(matchId: matchId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* multiple winners handling */
|
||||||
|
|
||||||
|
/// Sets the winners for a match.
|
||||||
|
///
|
||||||
|
/// Returns `true` if more than 0 rows were affected
|
||||||
|
Future<bool> setWinners({
|
||||||
|
required List<Player> winners,
|
||||||
|
required String matchId,
|
||||||
|
}) async {
|
||||||
|
// Clear previous winners if exists
|
||||||
|
await deleteAllScoresForMatch(matchId: matchId);
|
||||||
|
|
||||||
|
if (winners.isEmpty) return false;
|
||||||
|
|
||||||
|
await batch((batch) {
|
||||||
|
batch.insertAll(
|
||||||
|
scoreEntryTable,
|
||||||
|
winners
|
||||||
|
.map(
|
||||||
|
(player) => ScoreEntryTableCompanion.insert(
|
||||||
|
playerId: player.id,
|
||||||
|
matchId: matchId,
|
||||||
|
roundNumber: 0,
|
||||||
|
score: 1,
|
||||||
|
change: 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loser handling */
|
||||||
|
|
||||||
|
Future<bool> hasLoser({required String matchId}) async {
|
||||||
|
return await getLoser(matchId: matchId) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting the looser for a game and clearing previous looser if exists.
|
||||||
|
Future<bool> setLoser({
|
||||||
|
required String matchId,
|
||||||
|
required String playerId,
|
||||||
|
}) async {
|
||||||
|
// Clear previous loosers if exists
|
||||||
|
deleteAllScoresForMatch(matchId: matchId);
|
||||||
|
|
||||||
|
// Set the loosers score to 0
|
||||||
|
final rowsAffected = await into(scoreEntryTable).insert(
|
||||||
|
ScoreEntryTableCompanion.insert(
|
||||||
|
playerId: playerId,
|
||||||
|
matchId: matchId,
|
||||||
|
roundNumber: 0,
|
||||||
|
score: 0,
|
||||||
|
change: 0,
|
||||||
|
),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the looser of a match by looking for a score entry where score
|
||||||
|
/// is 0. Returns `null` if no player found, else the first with the score.
|
||||||
|
Future<Player?> getLoser({required String matchId}) async {
|
||||||
|
final query =
|
||||||
|
select(scoreEntryTable).join([
|
||||||
|
innerJoin(
|
||||||
|
db.playerTable,
|
||||||
|
db.playerTable.id.equalsExp(scoreEntryTable.playerId),
|
||||||
|
),
|
||||||
|
])..where(
|
||||||
|
scoreEntryTable.matchId.equals(matchId) &
|
||||||
|
scoreEntryTable.score.equals(0),
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = await query.get();
|
||||||
|
if (result.isEmpty) return null;
|
||||||
|
|
||||||
|
final playerData = result.first.readTable(db.playerTable);
|
||||||
|
return Player(
|
||||||
|
id: playerData.id,
|
||||||
|
name: playerData.name,
|
||||||
|
createdAt: playerData.createdAt,
|
||||||
|
description: playerData.description,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the looser of a match.
|
||||||
|
///
|
||||||
|
/// Returns `true` if the looser was removed, `false` if there are multiple
|
||||||
|
/// scores or if the looser cannot be removed.
|
||||||
|
Future<bool> removeLoser({required String matchId}) async {
|
||||||
|
final scores = await getAllMatchScores(matchId: matchId);
|
||||||
|
|
||||||
|
if (scores.length > 1) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return await deleteAllScoresForMatch(matchId: matchId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* placement handling */
|
||||||
|
|
||||||
|
/// Sets the placement for each player in a match.
|
||||||
|
/// The highest score is assigned to the first player, the second highest to the second player, and so on.
|
||||||
|
Future<void> setPlacements({
|
||||||
|
required String matchId,
|
||||||
|
required List<Player> players,
|
||||||
|
}) async {
|
||||||
|
for (int i = 0; i < players.length; i++) {
|
||||||
|
await db.scoreEntryDao.addScore(
|
||||||
|
matchId: matchId,
|
||||||
|
playerId: players[i].id,
|
||||||
|
entry: ScoreEntry(roundNumber: 0, score: players.length - i, change: 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
lib/data/dao/score_entry_dao.g.dart
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'score_entry_dao.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
mixin _$ScoreEntryDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||||
|
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
||||||
|
$GameTableTable get gameTable => attachedDatabase.gameTable;
|
||||||
|
$GroupTableTable get groupTable => attachedDatabase.groupTable;
|
||||||
|
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
||||||
|
$ScoreEntryTableTable get scoreEntryTable => attachedDatabase.scoreEntryTable;
|
||||||
|
ScoreEntryDaoManager get managers => ScoreEntryDaoManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScoreEntryDaoManager {
|
||||||
|
final _$ScoreEntryDaoMixin _db;
|
||||||
|
ScoreEntryDaoManager(this._db);
|
||||||
|
$$PlayerTableTableTableManager get playerTable =>
|
||||||
|
$$PlayerTableTableTableManager(_db.attachedDatabase, _db.playerTable);
|
||||||
|
$$GameTableTableTableManager get gameTable =>
|
||||||
|
$$GameTableTableTableManager(_db.attachedDatabase, _db.gameTable);
|
||||||
|
$$GroupTableTableTableManager get groupTable =>
|
||||||
|
$$GroupTableTableTableManager(_db.attachedDatabase, _db.groupTable);
|
||||||
|
$$MatchTableTableTableManager get matchTable =>
|
||||||
|
$$MatchTableTableTableManager(_db.attachedDatabase, _db.matchTable);
|
||||||
|
$$ScoreEntryTableTableTableManager get scoreEntryTable =>
|
||||||
|
$$ScoreEntryTableTableTableManager(
|
||||||
|
_db.attachedDatabase,
|
||||||
|
_db.scoreEntryTable,
|
||||||
|
);
|
||||||
|
}
|
||||||
374
lib/data/dao/team_dao.dart
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:tallee/core/enums.dart';
|
||||||
|
import 'package:tallee/data/db/database.dart';
|
||||||
|
import 'package:tallee/data/db/tables/player_match_table.dart';
|
||||||
|
import 'package:tallee/data/db/tables/team_table.dart';
|
||||||
|
import 'package:tallee/data/models/player.dart';
|
||||||
|
import 'package:tallee/data/models/score_entry.dart';
|
||||||
|
import 'package:tallee/data/models/team.dart';
|
||||||
|
|
||||||
|
part 'team_dao.g.dart';
|
||||||
|
|
||||||
|
@DriftAccessor(tables: [TeamTable, PlayerMatchTable])
|
||||||
|
class TeamDao extends DatabaseAccessor<AppDatabase> with _$TeamDaoMixin {
|
||||||
|
TeamDao(super.db);
|
||||||
|
|
||||||
|
/* Create */
|
||||||
|
|
||||||
|
/// Adds a new [team] to the database.
|
||||||
|
/// Returns `true` if the team was added, `false` otherwise.
|
||||||
|
Future<bool> addTeam({required Team team, required String matchId}) async {
|
||||||
|
if (await teamExists(teamId: team.id)) return false;
|
||||||
|
await into(teamTable).insert(
|
||||||
|
TeamTableCompanion.insert(
|
||||||
|
id: team.id,
|
||||||
|
name: team.name,
|
||||||
|
createdAt: team.createdAt,
|
||||||
|
color: Value(team.color.name),
|
||||||
|
score: Value(team.score),
|
||||||
|
),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
await db.batch((batch) async {
|
||||||
|
for (final player in team.members) {
|
||||||
|
await into(playerMatchTable).insert(
|
||||||
|
PlayerMatchTableCompanion.insert(
|
||||||
|
playerId: player.id,
|
||||||
|
matchId: matchId,
|
||||||
|
teamId: Value(team.id),
|
||||||
|
),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds multiple [teams] to the database in a batch operation.
|
||||||
|
Future<bool> addTeamsAsList({
|
||||||
|
required List<Team> teams,
|
||||||
|
required String matchId,
|
||||||
|
}) async {
|
||||||
|
if (teams.isEmpty) return false;
|
||||||
|
|
||||||
|
await db.batch(
|
||||||
|
(b) => b.insertAll(
|
||||||
|
teamTable,
|
||||||
|
teams
|
||||||
|
.map(
|
||||||
|
(team) => TeamTableCompanion.insert(
|
||||||
|
id: team.id,
|
||||||
|
name: team.name,
|
||||||
|
createdAt: team.createdAt,
|
||||||
|
color: Value(team.color.name),
|
||||||
|
score: Value(team.score),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
mode: InsertMode.insertOrIgnore,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (final team in teams) {
|
||||||
|
await db.batch((batch) async {
|
||||||
|
for (final player in team.members) {
|
||||||
|
await into(db.playerMatchTable).insert(
|
||||||
|
PlayerMatchTableCompanion.insert(
|
||||||
|
playerId: player.id,
|
||||||
|
matchId: matchId,
|
||||||
|
teamId: Value(team.id),
|
||||||
|
),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read */
|
||||||
|
|
||||||
|
/// Retrieves the total count of teams in the database.
|
||||||
|
Future<int> getTeamCount() async {
|
||||||
|
final count =
|
||||||
|
await (selectOnly(teamTable)..addColumns([teamTable.id.count()]))
|
||||||
|
.map((row) => row.read(teamTable.id.count()))
|
||||||
|
.getSingle();
|
||||||
|
return count ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a team with the given [teamId] exists in the database.
|
||||||
|
/// Returns `true` if the team exists, `false` otherwise.
|
||||||
|
Future<bool> teamExists({required String teamId}) async {
|
||||||
|
final query = select(teamTable)..where((t) => t.id.equals(teamId));
|
||||||
|
final result = await query.getSingleOrNull();
|
||||||
|
return result != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves all teams from the database.
|
||||||
|
Future<List<Team>> getAllTeams() async {
|
||||||
|
final query = select(teamTable);
|
||||||
|
final result = await query.get();
|
||||||
|
return Future.wait(
|
||||||
|
result.map((row) async {
|
||||||
|
final members = await _getTeamMembers(teamId: row.id);
|
||||||
|
return Team(
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
createdAt: row.createdAt,
|
||||||
|
color: AppColor.values.byName(row.color),
|
||||||
|
score: row.score,
|
||||||
|
members: members,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Team>> getTeamsByMatchId({required String matchId}) async {
|
||||||
|
final playerMatchQuery = select(db.playerMatchTable)
|
||||||
|
..where((pm) => pm.matchId.equals(matchId));
|
||||||
|
final playerMatches = await playerMatchQuery.get();
|
||||||
|
|
||||||
|
if (playerMatches.isEmpty) return [];
|
||||||
|
|
||||||
|
final teamIds = playerMatches
|
||||||
|
.map((pm) => pm.teamId)
|
||||||
|
.whereType<String>()
|
||||||
|
.toSet();
|
||||||
|
|
||||||
|
final teams = await Future.wait(
|
||||||
|
teamIds.map((id) => getTeamById(teamId: id)),
|
||||||
|
);
|
||||||
|
return teams;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves a [Team] by its [teamId], including its members.
|
||||||
|
Future<Team> getTeamById({required String teamId}) async {
|
||||||
|
final query = select(teamTable)..where((t) => t.id.equals(teamId));
|
||||||
|
final result = await query.getSingle();
|
||||||
|
final members = await _getTeamMembers(teamId: teamId);
|
||||||
|
return Team(
|
||||||
|
id: result.id,
|
||||||
|
name: result.name,
|
||||||
|
createdAt: result.createdAt,
|
||||||
|
color: AppColor.values.byName(result.color),
|
||||||
|
score: result.score,
|
||||||
|
members: members,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper method to get team members from PlayerMatchTable.
|
||||||
|
Future<List<Player>> _getTeamMembers({required String teamId}) async {
|
||||||
|
// Get all player_match entries with this teamId
|
||||||
|
final playerMatchQuery = select(db.playerMatchTable)
|
||||||
|
..where((pm) => pm.teamId.equals(teamId));
|
||||||
|
final playerMatches = await playerMatchQuery.get();
|
||||||
|
|
||||||
|
if (playerMatches.isEmpty) return [];
|
||||||
|
|
||||||
|
// Get unique player IDs
|
||||||
|
final playerIds = playerMatches.map((pm) => pm.playerId).toSet();
|
||||||
|
|
||||||
|
// Fetch all players
|
||||||
|
final players = await Future.wait(
|
||||||
|
playerIds.map((id) => db.playerDao.getPlayerById(playerId: id)),
|
||||||
|
);
|
||||||
|
return players
|
||||||
|
..sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update */
|
||||||
|
|
||||||
|
/// Updates the name of the team with the given [teamId].
|
||||||
|
Future<bool> updateTeamName({
|
||||||
|
required String teamId,
|
||||||
|
required String name,
|
||||||
|
}) async {
|
||||||
|
final rowsAffected =
|
||||||
|
await (update(teamTable)..where((t) => t.id.equals(teamId))).write(
|
||||||
|
TeamTableCompanion(name: Value(name)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the color of the team with the given [teamId].
|
||||||
|
Future<bool> updateTeamColor({
|
||||||
|
required String teamId,
|
||||||
|
required AppColor color,
|
||||||
|
}) async {
|
||||||
|
final rowsAffected =
|
||||||
|
await (update(teamTable)..where((t) => t.id.equals(teamId))).write(
|
||||||
|
TeamTableCompanion(color: Value(color.name)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the score of the team with the given [teamId].
|
||||||
|
/// Updates the member scores correspondingly
|
||||||
|
Future<bool> updateTeamScore({
|
||||||
|
required String teamId,
|
||||||
|
required String matchId,
|
||||||
|
required int score,
|
||||||
|
}) async {
|
||||||
|
await (update(teamTable)..where((t) => t.id.equals(teamId))).write(
|
||||||
|
const TeamTableCompanion(score: Value(null)),
|
||||||
|
);
|
||||||
|
await _deleteAllScoresForMembersOfTeam(teamId: teamId, matchId: matchId);
|
||||||
|
|
||||||
|
final rowsAffected =
|
||||||
|
await (update(teamTable)..where((t) => t.id.equals(teamId))).write(
|
||||||
|
TeamTableCompanion(score: Value(score)),
|
||||||
|
);
|
||||||
|
|
||||||
|
final members = await _getTeamMembers(teamId: teamId);
|
||||||
|
for (final member in members) {
|
||||||
|
await db.scoreEntryDao.addScore(
|
||||||
|
playerId: member.id,
|
||||||
|
matchId: matchId,
|
||||||
|
entry: ScoreEntry(score: score),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> removeScoreForTeam({
|
||||||
|
required String teamId,
|
||||||
|
required String matchId,
|
||||||
|
}) async {
|
||||||
|
await (update(teamTable)..where((t) => t.id.equals(teamId))).write(
|
||||||
|
const TeamTableCompanion(score: Value(null)),
|
||||||
|
);
|
||||||
|
await _deleteAllScoresForMembersOfTeam(teamId: teamId, matchId: matchId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the scores for all teams in the match with the given [matchId] by setting their scores to null.
|
||||||
|
Future<bool> removeAllTeamScores({required String matchId}) async {
|
||||||
|
// collect all teamIds for the given matchId from playerMatchTable
|
||||||
|
final teamIds =
|
||||||
|
await (selectOnly(playerMatchTable)
|
||||||
|
..addColumns([playerMatchTable.teamId])
|
||||||
|
..where(playerMatchTable.matchId.equals(matchId)))
|
||||||
|
.map((row) => row.read(playerMatchTable.teamId))
|
||||||
|
.get();
|
||||||
|
|
||||||
|
// filter null or duplicates
|
||||||
|
final filteredTeamIds = teamIds.whereType<String>().toSet().toList();
|
||||||
|
|
||||||
|
var rowsAffected = 0;
|
||||||
|
if (filteredTeamIds.isNotEmpty) {
|
||||||
|
rowsAffected =
|
||||||
|
await (update(teamTable)..where((t) => t.id.isIn(filteredTeamIds)))
|
||||||
|
.write(const TeamTableCompanion(score: Value(null)));
|
||||||
|
}
|
||||||
|
await db.scoreEntryDao.deleteAllScoresForMatch(matchId: matchId);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delete */
|
||||||
|
|
||||||
|
/// Deletes all teams from the database.
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> deleteAllTeams() async {
|
||||||
|
final query = delete(teamTable);
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes the team with the given [teamId] from the database.
|
||||||
|
/// Returns `true` if the team was deleted, `false` otherwise.
|
||||||
|
Future<bool> deleteTeam({required String teamId}) async {
|
||||||
|
final query = delete(teamTable)..where((t) => t.id.equals(teamId));
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Score handling */
|
||||||
|
|
||||||
|
/// Sets the team with the given [teamId] as the winner of the match with the given [matchId] by assigning a score of 1.
|
||||||
|
/// Returns `true` if the score was updated successfully, `false` otherwise.
|
||||||
|
Future<bool> setWinnerTeam({
|
||||||
|
required String teamId,
|
||||||
|
required String matchId,
|
||||||
|
}) async {
|
||||||
|
return await updateTeamScore(teamId: teamId, matchId: matchId, score: 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets multiple teams as winners of the match with the given [matchId] by assigning a score of 1 to each team.
|
||||||
|
/// Returns `true` if all scores were updated successfully, `false` otherwise.
|
||||||
|
Future<bool> setWinnerTeams({
|
||||||
|
required List<Team> winners,
|
||||||
|
required String matchId,
|
||||||
|
}) async {
|
||||||
|
// Reset all team scores .
|
||||||
|
await removeAllTeamScores(matchId: matchId);
|
||||||
|
// Reset all score entries
|
||||||
|
for (final team in winners) {
|
||||||
|
await _deleteAllScoresForMembersOfTeam(teamId: team.id, matchId: matchId);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final team in winners) {
|
||||||
|
await updateTeamScore(teamId: team.id, matchId: matchId, score: 1);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the winner status from all Teams with the given [matchId] by setting its score to null.
|
||||||
|
/// Returns `true` if the score was updated successfully, `false` otherwise.
|
||||||
|
Future<bool> removeWinnerTeam({required String matchId}) async {
|
||||||
|
return await removeAllTeamScores(matchId: matchId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the team with the given [teamId] as the loser of the match with the given [matchId] by assigning a score of 0.
|
||||||
|
/// Returns `true` if the score was updated successfully, `false` otherwise.
|
||||||
|
Future<bool> setLoserTeam({
|
||||||
|
required String teamId,
|
||||||
|
required String matchId,
|
||||||
|
}) async {
|
||||||
|
return await updateTeamScore(teamId: teamId, matchId: matchId, score: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the loser from the match with the given [matchId] by setting its score to null.
|
||||||
|
/// Returns `true` if the score was updated successfully, `false` otherwise.
|
||||||
|
Future<bool> removeLoserTeam({required String matchId}) async {
|
||||||
|
return await removeAllTeamScores(matchId: matchId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the placements for the teams in the match with the given [matchId] by assigning scores based on their order in the [teams] list.
|
||||||
|
/// Returns `true` if all scores were updated successfully, `false` otherwise.
|
||||||
|
Future<bool> setTeamPlacements({
|
||||||
|
required String matchId,
|
||||||
|
required List<Team> teams,
|
||||||
|
}) async {
|
||||||
|
List<bool?> success = List.generate(teams.length, (index) => null);
|
||||||
|
for (int i = 0; i < teams.length; i++) {
|
||||||
|
success[i] = await updateTeamScore(
|
||||||
|
matchId: matchId,
|
||||||
|
teamId: teams[i].id,
|
||||||
|
score: teams.length - i,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return success.every((result) => result == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper method to delete all scores for members of a team in a specific match.
|
||||||
|
Future<bool> _deleteAllScoresForMembersOfTeam({
|
||||||
|
required String teamId,
|
||||||
|
required String matchId,
|
||||||
|
}) async {
|
||||||
|
final playerMatchQuery = select(db.playerMatchTable)
|
||||||
|
..where((pm) => pm.teamId.equals(teamId) & pm.matchId.equals(matchId));
|
||||||
|
final playerMatches = await playerMatchQuery.get();
|
||||||
|
|
||||||
|
if (playerMatches.isEmpty) return false;
|
||||||
|
|
||||||
|
for (final pm in playerMatches) {
|
||||||
|
await db.scoreEntryDao.deleteAllScoresForPlayerInMatch(
|
||||||
|
playerId: pm.playerId,
|
||||||
|
matchId: matchId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
lib/data/dao/team_dao.g.dart
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'team_dao.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
mixin _$TeamDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||||
|
$TeamTableTable get teamTable => attachedDatabase.teamTable;
|
||||||
|
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
||||||
|
$GameTableTable get gameTable => attachedDatabase.gameTable;
|
||||||
|
$GroupTableTable get groupTable => attachedDatabase.groupTable;
|
||||||
|
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
||||||
|
$PlayerMatchTableTable get playerMatchTable =>
|
||||||
|
attachedDatabase.playerMatchTable;
|
||||||
|
TeamDaoManager get managers => TeamDaoManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class TeamDaoManager {
|
||||||
|
final _$TeamDaoMixin _db;
|
||||||
|
TeamDaoManager(this._db);
|
||||||
|
$$TeamTableTableTableManager get teamTable =>
|
||||||
|
$$TeamTableTableTableManager(_db.attachedDatabase, _db.teamTable);
|
||||||
|
$$PlayerTableTableTableManager get playerTable =>
|
||||||
|
$$PlayerTableTableTableManager(_db.attachedDatabase, _db.playerTable);
|
||||||
|
$$GameTableTableTableManager get gameTable =>
|
||||||
|
$$GameTableTableTableManager(_db.attachedDatabase, _db.gameTable);
|
||||||
|
$$GroupTableTableTableManager get groupTable =>
|
||||||
|
$$GroupTableTableTableManager(_db.attachedDatabase, _db.groupTable);
|
||||||
|
$$MatchTableTableTableManager get matchTable =>
|
||||||
|
$$MatchTableTableTableManager(_db.attachedDatabase, _db.matchTable);
|
||||||
|
$$PlayerMatchTableTableTableManager get playerMatchTable =>
|
||||||
|
$$PlayerMatchTableTableTableManager(
|
||||||
|
_db.attachedDatabase,
|
||||||
|
_db.playerMatchTable,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,18 +1,22 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:drift_flutter/drift_flutter.dart';
|
import 'package:drift_flutter/drift_flutter.dart';
|
||||||
import 'package:game_tracker/data/dao/group_dao.dart';
|
|
||||||
import 'package:game_tracker/data/dao/group_match_dao.dart';
|
|
||||||
import 'package:game_tracker/data/dao/match_dao.dart';
|
|
||||||
import 'package:game_tracker/data/dao/player_dao.dart';
|
|
||||||
import 'package:game_tracker/data/dao/player_group_dao.dart';
|
|
||||||
import 'package:game_tracker/data/dao/player_match_dao.dart';
|
|
||||||
import 'package:game_tracker/data/db/tables/group_match_table.dart';
|
|
||||||
import 'package:game_tracker/data/db/tables/group_table.dart';
|
|
||||||
import 'package:game_tracker/data/db/tables/match_table.dart';
|
|
||||||
import 'package:game_tracker/data/db/tables/player_group_table.dart';
|
|
||||||
import 'package:game_tracker/data/db/tables/player_match_table.dart';
|
|
||||||
import 'package:game_tracker/data/db/tables/player_table.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:tallee/data/dao/game_dao.dart';
|
||||||
|
import 'package:tallee/data/dao/group_dao.dart';
|
||||||
|
import 'package:tallee/data/dao/match_dao.dart';
|
||||||
|
import 'package:tallee/data/dao/player_dao.dart';
|
||||||
|
import 'package:tallee/data/dao/player_group_dao.dart';
|
||||||
|
import 'package:tallee/data/dao/player_match_dao.dart';
|
||||||
|
import 'package:tallee/data/dao/score_entry_dao.dart';
|
||||||
|
import 'package:tallee/data/dao/team_dao.dart';
|
||||||
|
import 'package:tallee/data/db/tables/game_table.dart';
|
||||||
|
import 'package:tallee/data/db/tables/group_table.dart';
|
||||||
|
import 'package:tallee/data/db/tables/match_table.dart';
|
||||||
|
import 'package:tallee/data/db/tables/player_group_table.dart';
|
||||||
|
import 'package:tallee/data/db/tables/player_match_table.dart';
|
||||||
|
import 'package:tallee/data/db/tables/player_table.dart';
|
||||||
|
import 'package:tallee/data/db/tables/score_entry_table.dart';
|
||||||
|
import 'package:tallee/data/db/tables/team_table.dart';
|
||||||
|
|
||||||
part 'database.g.dart';
|
part 'database.g.dart';
|
||||||
|
|
||||||
@@ -23,7 +27,9 @@ part 'database.g.dart';
|
|||||||
MatchTable,
|
MatchTable,
|
||||||
PlayerGroupTable,
|
PlayerGroupTable,
|
||||||
PlayerMatchTable,
|
PlayerMatchTable,
|
||||||
GroupMatchTable,
|
GameTable,
|
||||||
|
TeamTable,
|
||||||
|
ScoreEntryTable,
|
||||||
],
|
],
|
||||||
daos: [
|
daos: [
|
||||||
PlayerDao,
|
PlayerDao,
|
||||||
@@ -31,7 +37,9 @@ part 'database.g.dart';
|
|||||||
MatchDao,
|
MatchDao,
|
||||||
PlayerGroupDao,
|
PlayerGroupDao,
|
||||||
PlayerMatchDao,
|
PlayerMatchDao,
|
||||||
GroupMatchDao,
|
GameDao,
|
||||||
|
ScoreEntryDao,
|
||||||
|
TeamDao,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
class AppDatabase extends _$AppDatabase {
|
class AppDatabase extends _$AppDatabase {
|
||||||
|
|||||||
14
lib/data/db/tables/game_table.dart
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
|
class GameTable extends Table {
|
||||||
|
TextColumn get id => text()();
|
||||||
|
TextColumn get name => text()();
|
||||||
|
TextColumn get ruleset => text()();
|
||||||
|
TextColumn get description => text()();
|
||||||
|
TextColumn get color => text()();
|
||||||
|
TextColumn get icon => text()();
|
||||||
|
DateTimeColumn get createdAt => dateTime()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column<Object>> get primaryKey => {id};
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:game_tracker/data/db/tables/group_table.dart';
|
|
||||||
import 'package:game_tracker/data/db/tables/match_table.dart';
|
|
||||||
|
|
||||||
class GroupMatchTable extends Table {
|
|
||||||
TextColumn get groupId =>
|
|
||||||
text().references(GroupTable, #id, onDelete: KeyAction.cascade)();
|
|
||||||
TextColumn get matchId =>
|
|
||||||
text().references(MatchTable, #id, onDelete: KeyAction.cascade)();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column<Object>> get primaryKey => {groupId, matchId};
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ import 'package:drift/drift.dart';
|
|||||||
class GroupTable extends Table {
|
class GroupTable extends Table {
|
||||||
TextColumn get id => text()();
|
TextColumn get id => text()();
|
||||||
TextColumn get name => text()();
|
TextColumn get name => text()();
|
||||||
|
TextColumn get description => text()();
|
||||||
DateTimeColumn get createdAt => dateTime()();
|
DateTimeColumn get createdAt => dateTime()();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,10 +1,21 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:tallee/data/db/tables/game_table.dart';
|
||||||
|
import 'package:tallee/data/db/tables/group_table.dart';
|
||||||
|
|
||||||
class MatchTable extends Table {
|
class MatchTable extends Table {
|
||||||
TextColumn get id => text()();
|
TextColumn get id => text()();
|
||||||
|
TextColumn get gameId =>
|
||||||
|
text().references(GameTable, #id, onDelete: KeyAction.cascade)();
|
||||||
|
// Nullable if there is no group associated with the match
|
||||||
|
// onDelete: If a group gets deleted, groupId in the match gets set to null
|
||||||
|
TextColumn get groupId => text()
|
||||||
|
.references(GroupTable, #id, onDelete: KeyAction.setNull)
|
||||||
|
.nullable()();
|
||||||
TextColumn get name => text()();
|
TextColumn get name => text()();
|
||||||
late final winnerId = text().nullable()();
|
BoolColumn get isTeamMatch => boolean().withDefault(const Constant(false))();
|
||||||
|
TextColumn get notes => text()();
|
||||||
DateTimeColumn get createdAt => dateTime()();
|
DateTimeColumn get createdAt => dateTime()();
|
||||||
|
DateTimeColumn get endedAt => dateTime().nullable()();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column<Object>> get primaryKey => {id};
|
Set<Column<Object>> get primaryKey => {id};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:game_tracker/data/db/tables/group_table.dart';
|
import 'package:tallee/data/db/tables/group_table.dart';
|
||||||
import 'package:game_tracker/data/db/tables/player_table.dart';
|
import 'package:tallee/data/db/tables/player_table.dart';
|
||||||
|
|
||||||
class PlayerGroupTable extends Table {
|
class PlayerGroupTable extends Table {
|
||||||
TextColumn get playerId =>
|
TextColumn get playerId =>
|
||||||
|
|||||||