Compare commits
636 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 061eccd87f | |||
| 68055ba720 | |||
| e0505cbaaa | |||
| 8786cae3f8 | |||
| 489543ffca | |||
| 6631ad8e1f | |||
| b4cef51a91 | |||
| 46c59336f4 | |||
| c4086c8072 | |||
| 97acb7e17f | |||
| 329053bfb9 | |||
|
|
d0f8445848 | ||
| 18016888ce | |||
| bbedb8af8c | |||
| f90cc4eb9b | |||
| d2f6a5fe82 | |||
|
|
2cb0a94bbb | ||
| 80ee62fb24 | |||
| ca4375379e | |||
| 049e142845 | |||
| 3a155b6c03 | |||
| 0f35da597f | |||
| 447b5fc346 | |||
| 24e3d5ab80 | |||
| 3c6382318b | |||
| e26239ae85 | |||
| 9c94ba41fb | |||
| b6f8131b2e | |||
| bbbfaaa31d | |||
| b4c9940013 | |||
| ea73ab687b | |||
| dd9ea67d90 | |||
| 43a298723a | |||
| 789c1c3172 | |||
| 639da61802 | |||
| 7977380c16 | |||
| c346c9036f | |||
| ef7384515d | |||
| ecbcab8e31 | |||
| 9353749899 | |||
| 8c0c705dd8 | |||
| 164f738776 | |||
| 6e1dfdb79f | |||
|
|
d3b9c51952 | ||
| 0f937f12f5 | |||
| 88e4718f2e | |||
|
|
0f5ac1f1f4 | ||
|
|
8aeccb0cab | ||
| 446d1bb7ae | |||
| ebb9b77025 | |||
| 75651a6bd3 | |||
| e409e4684d | |||
| ce0a79196c | |||
| f5ebab5d01 | |||
| 58a48215da | |||
| 11618f0cfd | |||
| 002f58f24e | |||
|
|
a717837bd4 | ||
|
|
a0eb6aa1f9 | ||
|
|
357fac2d56 | ||
| d30f4b7add | |||
| 07c3219bec | |||
| da74491d55 | |||
|
|
077d147498 | ||
| 049c39984b | |||
| 4f7eeb9def | |||
| 3cdcc95656 | |||
| dc0d929eaf | |||
| f5430a1940 | |||
| e2b9ae6e0c | |||
| 99768ec9b9 | |||
| 1ce390e528 | |||
| 4d96d1961c | |||
| 18030d5d2e | |||
| 4f73da1218 | |||
| ee0a643636 | |||
| 287e71f2a9 | |||
| d198fb3d42 | |||
| 80df6116ab | |||
| f7c1ff453f | |||
| 4162e34a5f | |||
| 46bdd5c3d7 | |||
| a6056c11e4 | |||
| be4932661d | |||
| 87233a2240 | |||
| 3c4ec9cac9 | |||
| a008b18838 | |||
| c4d5c44a44 | |||
| 50f55ce8c0 | |||
| d26c963ee8 | |||
| 2c9dedb65e | |||
| 7b4874b8b1 | |||
| eda4549c36 | |||
| 4ca57c92f1 | |||
| 03abba0679 | |||
| cd824dcc63 | |||
| 8d8ba41edf | |||
| c09309a47a | |||
| 86a1b0f72c | |||
| 4f6807b28f | |||
| 4e5f221749 | |||
| 95b45c2e54 | |||
| 4402d0398f | |||
| 1972488eb0 | |||
| 73c4bc6cc7 | |||
| 006023e289 | |||
| cd7ef12085 | |||
| 52404422bd | |||
| 690c35415b | |||
| b711635afa | |||
| 795adacb57 | |||
| adcb695d85 | |||
| b60db151cb | |||
| f4cc5d4641 | |||
| b08d1d29ae | |||
| 51829dd833 | |||
| 653560ee31 | |||
| b5a798adcc | |||
| e7d5765ae1 | |||
| 6b11de24b8 | |||
| 5e5c79faff | |||
| 89e6e05e8e | |||
| b2f3bc117d | |||
| 892d0394af | |||
| 726e1ee52d | |||
| f50928fef1 | |||
| e87bb7bebf | |||
| e23f9f9318 | |||
| feeaed6244 | |||
| f68559d028 | |||
| ee94703773 | |||
| f61a765624 | |||
| c9904b18a1 | |||
| 7154a4a7ba | |||
| 175fead68a | |||
| 812f197b75 | |||
| 8a9e018031 | |||
| 285da7df05 | |||
| 9a4039680e | |||
| 186eb36c7e | |||
| 2077c1b10c | |||
| 52e333b31c | |||
| c24d49fa5b | |||
| df5510224b | |||
| 3a38affdff | |||
| 748a4ff866 | |||
| d7179665d9 | |||
| 1ab4df0b81 | |||
| 06da74e193 | |||
| 59a83e81de | |||
| 7730c0f80a | |||
| f8f2a4b05e | |||
| 5ff5995bcf | |||
| 9c1cbb0ed8 | |||
| e975d1d2b0 | |||
| 1bea2f808d | |||
| 49685a75f7 | |||
| 0bfd709f1f | |||
| 362da2fc06 | |||
| 77b697ac83 | |||
| 405fa60d61 | |||
| 045701d848 | |||
| 3b17f47efe | |||
| 7926df8987 | |||
| 92660414c9 | |||
| f226e335c2 | |||
| 89fb7554e0 | |||
| 076c36bc85 | |||
| 08cdb941a4 | |||
| d723f3e3cc | |||
| 15e08834a0 | |||
| db8499e18d | |||
|
|
8e1ab89e6c | ||
| 6046d8720f | |||
| fc0712cc78 | |||
| 3d0e05daf2 | |||
| 839963049d | |||
| ecea09afc6 | |||
| d40904b5cd | |||
| b782e4e62b | |||
| fb2b070e32 | |||
|
|
85653af39e | ||
|
|
cdcc50e93a | ||
|
|
914e0bceda | ||
|
|
7ad35bd2dc | ||
|
|
1163961b90 | ||
|
|
a2c8df16b7 | ||
|
|
a23171359f | ||
| 404f162728 | |||
| bd7368f0cf | |||
| fb7d71c86e | |||
|
|
2c1871b6e2 | ||
| f22a187148 | |||
| 28ccf8b96d | |||
| 377439495c | |||
|
|
c8a1c29cc3 | ||
| 3214d3893b | |||
| 5817f82b77 | |||
| ad63a314c9 | |||
|
|
1636ba14b6 | ||
| 0315920b0d | |||
|
|
4643feb868 | ||
| a3eff2e738 | |||
| 2e13c0cea0 | |||
| 023fdf5ee9 | |||
| 7d3cc51c8f | |||
| fe8fdafdd8 | |||
|
|
024a618473 | ||
| f194d7ca78 | |||
|
|
755fa092c2 | ||
| 8cf12d32d3 | |||
|
|
017ef65b5a | ||
| 934a857cdf | |||
| 6c4fc20870 | |||
|
|
be697f080e | ||
|
|
6235f96def | ||
|
|
4f6a6f71ed | ||
| de37b27fcc | |||
| 0b244938cf | |||
| 9cc2da6b92 | |||
|
|
08c8e54b2d | ||
| 3ee259abfd | |||
|
|
42a4bcd104 | ||
|
|
ec0d2d1ec7 | ||
| 6aac0c6366 | |||
| 1a51f16ee0 | |||
|
|
7340a5616e | ||
| dea1caafd6 | |||
| d35c820967 | |||
|
|
9c47f5ac76 | ||
|
|
aca52efc44 | ||
|
|
6848ab3586 | ||
|
|
b3547b2971 | ||
|
|
c5c5461167 | ||
| 5b88ee242e | |||
| 06a6eeff9c | |||
| 9cafc305fd | |||
|
|
6382c87d67 | ||
|
|
44b7fcbba2 | ||
| 77d106cb06 | |||
| f8cf68eb1a | |||
| 67d3cb2c7f | |||
| 8127b539e8 | |||
| ea1237942d | |||
| 9d6d7d885d | |||
| 86061e2733 | |||
| 923d323808 | |||
| 933bd6b7fd | |||
| 0c63963ddf | |||
| 51a48e07eb | |||
| 3a7621678d | |||
| 0a8b96bc3b | |||
| d3e203b575 | |||
| 02768f940a | |||
| 711c018014 | |||
| 758b44d6d6 | |||
| 5964528524 | |||
| 7c3e34e83d | |||
| 060ecf1ef2 | |||
| f0ce6d8c2d | |||
| 52b97f435a | |||
| 39312c8698 | |||
|
|
917d318566 | ||
|
|
fbcab5d097 | ||
|
|
113c8c61be | ||
|
|
a4a4831615 | ||
|
|
c2f39c40f0 | ||
| 67aadfa66f | |||
|
|
208372c782 | ||
|
|
1f389ca25c | ||
| ceafee2a80 | |||
|
|
7303c22cd3 | ||
|
|
7bddd74a6e | ||
|
|
f3fc93d6e5 | ||
|
|
eefb8de460 | ||
|
|
f623c17071 | ||
|
|
4594139f8e | ||
|
|
a860a1c247 | ||
|
|
a3fcebfa15 | ||
|
|
6c4b3ef120 | ||
|
|
d4958ec914 | ||
| 8e205ebca2 | |||
| cf274f9013 | |||
| 3c4743fa70 | |||
| 168ebe94fc | |||
|
|
94344b146f | ||
|
|
d898732a17 | ||
| 2757bc66da | |||
| b115a4b66c | |||
|
|
c38bb24e94 | ||
|
|
98584f1324 | ||
|
|
3ef6604884 | ||
|
|
044e9ba7b2 | ||
| f93f4f576b | |||
| 1b00366a1e | |||
|
|
572895efa9 | ||
|
|
c244e5742a | ||
|
|
6efa440bbc | ||
| 0e53b16d6c | |||
|
|
884f56c9a7 | ||
| 0e997071c3 | |||
| 68893b1318 | |||
| a221f2a91b | |||
| 1577ed41cd | |||
|
|
1b4069e7e4 | ||
|
|
a6f90f8477 | ||
|
|
aefa00f95d | ||
|
|
c145b3e552 | ||
|
|
c5de2fdd87 | ||
| ef3318a4ff | |||
| 39e30aa8d5 | |||
|
|
de5bbbf3f7 | ||
|
|
fba042d2b9 | ||
|
|
d85530dd2c | ||
|
|
326182aeef | ||
|
|
2172bede74 | ||
|
|
f497251873 | ||
|
|
e4e04940a9 | ||
|
|
b493357f31 | ||
|
|
4eaf98834d | ||
|
|
7cdab36f5d | ||
|
|
6831123aed | ||
|
|
f7b8648778 | ||
|
|
870fa5488a | ||
|
|
3472b4bcd7 | ||
|
|
d166ec1b4d | ||
|
|
f023077715 | ||
|
|
96e6cd6dc7 | ||
|
|
0311887185 | ||
| 27b15893a7 | |||
| 9b59c5bfe0 | |||
| 62094f2f4f | |||
|
|
8ae47ef056 | ||
|
|
d15940a5f2 | ||
| a7f2fc5b92 | |||
| 50c344091f | |||
| c69820241f | |||
|
|
26bfd3973c | ||
|
|
c0ebef7e96 | ||
|
|
cd74e5f06f | ||
|
|
bb62b5d352 | ||
|
|
f26c795d46 | ||
| d6c4be3acf | |||
| b888307ce7 | |||
| 6b15207ad0 | |||
| ce04f16f97 | |||
| 6d7524c859 | |||
| 2f705b87f7 | |||
| 37410b2f7a | |||
| b043577da0 | |||
| 4cfed522eb | |||
| 289a404c0a | |||
| 08e074607e | |||
| 8de2612b05 | |||
| fb36a00fbf | |||
| bb28a8f31d | |||
| 33bf63cb1e | |||
|
|
a50fa82a50 | ||
|
|
b9635cae68 | ||
|
|
5ecf322f0c | ||
|
|
1e7d56499b | ||
|
|
dff379c99d | ||
|
|
79261274f6 | ||
|
|
3a78021b98 | ||
|
|
5b1e0ea115 | ||
|
|
57ad824d67 | ||
|
|
6ece516cc6 | ||
| b1f574b5a4 | |||
| 31398fdcac | |||
| 12aa06f953 | |||
| 6b055c4364 | |||
| 4bd44727a5 | |||
| 6be0099048 | |||
| 9bfd3a48dc | |||
| 92b66d7995 | |||
| d28fced5b3 | |||
| fa01645d31 | |||
| 666c7e0ca6 | |||
| 39e4a3896e | |||
| 67b5da131e | |||
|
|
1010d57b79 | ||
| 3270971c7f | |||
|
|
a8b5ee361e | ||
| e68e39426b | |||
|
|
fea01ba8ec | ||
|
|
b9b74d2447 | ||
| 56111f3ac7 | |||
| da0c819da5 | |||
|
|
872e3a27f1 | ||
| 5cb32114c8 | |||
| 3631201435 | |||
| fe7e81986d | |||
| 6e21e4fd6c | |||
|
|
83eda83671 | ||
|
|
01be4a7620 | ||
|
|
872042de5a | ||
|
|
6a3730ca04 | ||
|
|
8559a46cb9 | ||
|
|
67cb178f46 | ||
| 54ff97448d | |||
| dd1502d2ce | |||
| c82a0efa93 | |||
| 43715e05a5 | |||
| 4dce16f067 | |||
| d1ff46844a | |||
| ab0dbec1af | |||
|
|
4b8e0b2868 | ||
|
|
8fa74240bc | ||
|
|
f98abf19ef | ||
|
|
6ebec31f09 | ||
|
|
f06f98a657 | ||
|
|
b6bee250df | ||
|
|
5f9d7a848c | ||
|
|
271314f990 | ||
|
|
2bc0ffd47a | ||
|
|
6f11f4ebbb | ||
| 5734ceca6e | |||
| fb7c4c66ef | |||
| 75aec048ae | |||
| aa42a53c74 | |||
| 02184d9cfd | |||
| 0695f1d120 | |||
| 0f2b94a277 | |||
| b80ffff296 | |||
| cbdec1882c | |||
| a7ba0fb4b4 | |||
| 4845aeb479 | |||
| 55b61d3908 | |||
| 808b6e3d2b | |||
| 56ffecba92 | |||
| 548d7629d3 | |||
| 1fc675a138 | |||
|
|
aeca5e4341 | ||
|
|
811edf842b | ||
|
|
a35b432b54 | ||
|
|
627ce7a987 | ||
|
|
57ac8ad2ce | ||
|
|
526650bd98 | ||
|
|
21d086a816 | ||
|
|
a6b188e0d6 | ||
|
|
bd46968b90 | ||
| ef1b7ac716 | |||
| b3ff789f1b | |||
| 3baef0e1ab | |||
| 65140d77ac | |||
| 1de6c981dc | |||
| 9ca647f0cc | |||
|
|
332f38db57 | ||
|
|
54ab737e2d | ||
|
|
155c64a729 | ||
|
|
538b4440d8 | ||
|
|
5a3229eb19 | ||
|
|
c232f193d1 | ||
| f17ad44155 | |||
| 4500b213c6 | |||
|
|
4ccfe55174 | ||
| 0173123898 | |||
|
|
9eaf64c7a9 | ||
|
|
aa30efd3b6 | ||
| 852a8a0eb2 | |||
|
|
4e887c1a73 | ||
|
|
108de589bc | ||
| 27871d96f5 | |||
| c776c579ab | |||
| 9096f4fd29 | |||
| 858d13cadc | |||
| 4d7f452a97 | |||
| ee2cab953d | |||
| 0655dfdfea | |||
|
|
c61f71d226 | ||
| de654e487b | |||
| 6d77a98d92 | |||
| 7e3d094217 | |||
|
|
7c0ab0bfae | ||
|
|
8c5348dbb6 | ||
| 3c35e05e47 | |||
|
|
77e93a38b7 | ||
|
|
6f646a7cf0 | ||
|
|
b7f97c45a5 | ||
|
|
5c9d04fc88 | ||
|
|
cbd038d8fb | ||
|
|
6c832e01f3 | ||
|
|
1d00a3b55c | ||
|
|
5823152c56 | ||
|
|
3d403ee35e | ||
|
|
230f2715ca | ||
|
|
6b3979c68f | ||
|
|
003c7ec58a | ||
|
|
5cb625a710 | ||
|
|
5a1ff67db4 | ||
|
|
870920f67e | ||
|
|
d9db1f778e | ||
|
|
bb7fbf9f78 | ||
|
|
53246a78c6 | ||
| 5a691a5ddf | |||
| 7a70d163c7 | |||
| 24a2af0bee | |||
| 67116d2c89 | |||
| 1029e48f42 | |||
| 3088573b0b | |||
| ae653027c4 | |||
| 386deff6b8 | |||
| 2057d5695b | |||
| f646c18035 | |||
| e291a0e999 | |||
| 3760d61e2e | |||
| c96c3b1dab | |||
| 9f465b25bb | |||
| fd3f49255a | |||
| 88dfdb05a8 | |||
| c15313ea5f | |||
| b2ddea8794 | |||
| 6682cdc047 | |||
| beff4c5297 | |||
| 0570758b07 | |||
| a36dee75af | |||
| 48f1a9c1ba | |||
| f3a611ad60 | |||
|
|
4725e11b89 | ||
| d8704c38f1 | |||
|
|
90628b37d9 | ||
| d839081112 | |||
| 9e3e8c835e | |||
| 35640a1a39 | |||
| 9655a77972 | |||
|
|
6a515f0edc | ||
| d3754c8018 | |||
| 6f8c0674c2 | |||
| 18407f8328 | |||
|
|
4f0abd2f26 | ||
| d51b1b0ab7 | |||
| d8e2c8c95d | |||
| 6d4c9a5e65 | |||
| 706cd94d14 | |||
| 8d3430bd75 | |||
|
|
4b9bf4dff4 | ||
|
|
124a10c619 | ||
|
|
eecb695b87 | ||
|
|
38a0242264 | ||
|
|
693829ce42 | ||
|
|
defd851dbd | ||
|
|
8b2d406433 | ||
|
|
65a8475e47 | ||
| bdd5566493 | |||
| 8f635116df | |||
| c84d817810 | |||
| 30b5041ae5 | |||
| 0294f078f9 | |||
|
|
0c75ffbf35 | ||
|
|
47bd755e72 | ||
|
|
be79de7c82 | ||
|
|
e25a02fe1f | ||
|
|
ddc8e98c19 | ||
|
|
4ea76ddab5 | ||
|
|
7bb2d98639 | ||
| b7d85053bc | |||
| dd373d3800 | |||
| 259770ce69 | |||
| ca06f6c8b3 | |||
| 83a0448219 | |||
| 86267ddddf | |||
|
|
3cc1e93c5b | ||
| cfaee96c3e | |||
|
|
cfd5a7c8cd | ||
|
|
a3e5e6701e | ||
|
|
2a871a4d41 | ||
|
|
4bd98ef06f | ||
|
|
82935303ba | ||
|
|
b7681499ae | ||
|
|
877e0cf0de | ||
| 3d6b87a7d2 | |||
| 5ef045165d | |||
| 488f67289d | |||
| 38a71a8b59 | |||
| 054094a61e | |||
| c48039eeb4 | |||
| e580ed3251 | |||
| 695c80ba9d | |||
| bdbcd75042 | |||
| 563f8ee691 | |||
| 352d7beec1 | |||
| d078af520d | |||
| 77c5f53532 | |||
| 06908ffacb | |||
| 9c3f5d4d50 | |||
| 31a11656c4 | |||
| 07cd4f6c0e | |||
|
|
3b8b1e9b97 | ||
| 8f05f22b23 | |||
| 467daa35f0 | |||
| 0f20523b3c | |||
| a9fc3e3227 | |||
| 061275ba30 | |||
| edea9ef315 | |||
| a7162682c4 | |||
| 5791ddc47d | |||
| e572d9f3cf | |||
| c48e3b8a95 | |||
| aa0950df9b | |||
| 5d3efa0af5 | |||
| 3fbb108646 | |||
| 1a0fe7f95d | |||
| f6147aa810 | |||
| 277d1dce8f | |||
| 24041f4242 | |||
|
|
26c437261f | ||
| 1bab36f727 | |||
| a727878b0c | |||
| fdd4af6746 | |||
| 54b3b1d457 | |||
| 9dba49e141 | |||
| df42f68c04 | |||
| 3cb3faa073 | |||
| c5e9baad5e | |||
| 32ce987fbc | |||
| b8fa6b730f | |||
| 8d419b0dd9 | |||
|
|
cce97b7509 | ||
| d3723b8ae5 | |||
| a254d88775 | |||
| b3a30e10d1 | |||
| 8a278cd6e2 | |||
|
|
f5e031484e | ||
|
|
1360fa1592 | ||
|
|
998390c063 | ||
|
|
9f38ca1bcc | ||
| a3ad1dab8c | |||
|
|
177ac844ee | ||
|
|
4f02825b96 | ||
|
|
d31db865cf | ||
| 5c94880733 | |||
| 3c9a3bf1b3 | |||
| 46d7b58238 | |||
| 29153cb6ee | |||
|
|
4736b8bd3f | ||
| 5b7c19f334 |
245
README.md
245
README.md
@@ -1 +1,244 @@
|
|||||||
# group-2-threaded-project-petshop
|
# PetShop
|
||||||
|
|
||||||
|
A pet store management application with a Spring Boot API serving three clients: a Next.js web app, a JavaFX desktop app, and an Android app.
|
||||||
|
|
||||||
|
Handles product sales, pet adoption, appointment booking, real-time chat, AI assistance, payments (Stripe), email notifications (Resend), and file storage (Azure Blob).
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
| Layer | Technology |
|
||||||
|
|-------|------------|
|
||||||
|
| API | Java 25, Spring Boot 4, Spring Security (JWT), Hibernate |
|
||||||
|
| Database | MySQL 8.0, Flyway migrations |
|
||||||
|
| Web | Next.js 16, React 19, Tailwind CSS 4 |
|
||||||
|
| Desktop | JavaFX, Maven |
|
||||||
|
| Android | Kotlin, Hilt, Retrofit, CameraX |
|
||||||
|
| Infra | Docker, Azure Container Apps |
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
main/
|
||||||
|
backend/ Spring Boot REST API
|
||||||
|
web/ Next.js frontend
|
||||||
|
desktop/ JavaFX desktop client
|
||||||
|
android/ Android mobile app
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Java 25
|
||||||
|
- Node.js 18+
|
||||||
|
- Docker
|
||||||
|
- Maven
|
||||||
|
- Android Studio (for mobile)
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### 1. Start the database
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd backend
|
||||||
|
docker compose -f docker-compose.dev.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configure the backend
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd backend
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Fill in `.env` with your keys:
|
||||||
|
|
||||||
|
```
|
||||||
|
JWT_SECRET=<openssl rand -base64 32>
|
||||||
|
STRIPE_SECRET_KEY=sk_test_...
|
||||||
|
OPENROUTER_API_KEY=sk-or-v1-...
|
||||||
|
RESEND_API_KEY=re_...
|
||||||
|
RESEND_FROM=PetShop <no-reply@yourdomain.com>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Run the backend
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd backend
|
||||||
|
mvn spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
The API starts at `http://localhost:8080`. Flyway runs migrations and seeds data automatically on first boot.
|
||||||
|
|
||||||
|
### 4. Run the web frontend
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd web
|
||||||
|
cp .env.example .env.local
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
The web app starts at `http://localhost:3000`.
|
||||||
|
|
||||||
|
### 5. Run the desktop client
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd desktop
|
||||||
|
cp connectionpetstore.properties.example connectionpetstore.properties
|
||||||
|
mvn javafx:run
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Run the Android app
|
||||||
|
|
||||||
|
Open `android/` in Android Studio and run on an emulator or device.
|
||||||
|
|
||||||
|
## Switching Between Azure and Local Backend
|
||||||
|
|
||||||
|
Each client reads the backend URL from a config file. To point a client at the
|
||||||
|
hosted Azure backend versus a local one, flip the commented lines.
|
||||||
|
|
||||||
|
### Web
|
||||||
|
|
||||||
|
Edit `web/.env.local`:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Local
|
||||||
|
BACKEND_URL=http://localhost:8080
|
||||||
|
#BACKEND_URL=https://petshop-backend.nicepond-c7280126.westus2.azurecontainerapps.io
|
||||||
|
|
||||||
|
# Azure
|
||||||
|
#BACKEND_URL=http://localhost:8080
|
||||||
|
BACKEND_URL=https://petshop-backend.nicepond-c7280126.westus2.azurecontainerapps.io
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart the dev server after changing.
|
||||||
|
|
||||||
|
### Desktop
|
||||||
|
|
||||||
|
Edit `desktop/src/main/resources/connectionpetstore.properties`:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Local
|
||||||
|
api.baseUrl=http://localhost:8080
|
||||||
|
#api.baseUrl=https://petshop-backend.nicepond-c7280126.westus2.azurecontainerapps.io
|
||||||
|
|
||||||
|
# Azure
|
||||||
|
#api.baseUrl=http://localhost:8080
|
||||||
|
api.baseUrl=https://petshop-backend.nicepond-c7280126.westus2.azurecontainerapps.io
|
||||||
|
```
|
||||||
|
|
||||||
|
### Android
|
||||||
|
|
||||||
|
Edit `android/local.properties`:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
# Local (emulator — 10.0.2.2 maps to host's localhost)
|
||||||
|
petstore.backend.emulatorUrl=http\://10.0.2.2\:8080/
|
||||||
|
petstore.backend.deviceUrl=http\://192.168.x.x\:8080/
|
||||||
|
|
||||||
|
# Azure
|
||||||
|
petstore.backend.emulatorUrl=https\://petshop-backend.nicepond-c7280126.westus2.azurecontainerapps.io/
|
||||||
|
petstore.backend.deviceUrl=https\://petshop-backend.nicepond-c7280126.westus2.azurecontainerapps.io/
|
||||||
|
```
|
||||||
|
|
||||||
|
Sync Gradle and re-run the app.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
A Postman collection is available at `backend/postman/`. Key endpoint groups:
|
||||||
|
|
||||||
|
- `/api/auth` -- registration, login, password reset
|
||||||
|
- `/api/products` -- catalog and inventory
|
||||||
|
- `/api/pets` -- listings and adoption
|
||||||
|
- `/api/appointments` -- booking
|
||||||
|
- `/api/cart`, `/api/sales`, `/api/refunds` -- transactions
|
||||||
|
- `/api/chat` -- messaging and AI assistant
|
||||||
|
- `/ws` -- WebSocket (STOMP) for real-time updates
|
||||||
|
|
||||||
|
## Docker (full stack)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd backend
|
||||||
|
docker compose up --build -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Starts the API and MySQL together. The web frontend has its own Dockerfile for independent deployment.
|
||||||
|
|
||||||
|
## Running the Web App
|
||||||
|
|
||||||
|
Requires Node.js 18+.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd web
|
||||||
|
cp .env.example .env.local
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open `http://localhost:3000`. The app proxies API calls to the backend at `http://localhost:8080` by default.
|
||||||
|
|
||||||
|
To point at a different backend, edit `BACKEND_URL` and `NEXT_PUBLIC_BACKEND_URL` in `.env.local`.
|
||||||
|
|
||||||
|
For a production build:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the Desktop App (JavaFX)
|
||||||
|
|
||||||
|
Requires IntelliJ IDEA and Java 25+.
|
||||||
|
|
||||||
|
1. Open the `desktop/` directory in IntelliJ.
|
||||||
|
2. Copy `connectionpetstore.properties.example` to `connectionpetstore.properties` and edit it to match your database. The defaults expect the dev Docker database:
|
||||||
|
|
||||||
|
```
|
||||||
|
url=jdbc:mysql://127.0.0.1:3306/Petstoredb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
|
||||||
|
user=petshop
|
||||||
|
password=petshop
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Open **View > Tool Windows > Maven** and click **Reload All Maven Projects**.
|
||||||
|
4. Expand **Plugins > javafx** and double-click **javafx:run**.
|
||||||
|
|
||||||
|
Default accounts seeded on first run:
|
||||||
|
|
||||||
|
| Role | Username | Password |
|
||||||
|
|------|----------|----------|
|
||||||
|
| Admin | `admin` | `admin123` |
|
||||||
|
| Staff | `staff` | `staff123` |
|
||||||
|
|
||||||
|
## Running the Android App
|
||||||
|
|
||||||
|
Requires Android Studio and the Android SDK (min API 24).
|
||||||
|
|
||||||
|
1. Copy `local.properties.template` to `local.properties` and set `sdk.dir` to your Android SDK path.
|
||||||
|
2. Configure the backend URLs in `local.properties`:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
# Emulator — 10.0.2.2 maps to the host machine's localhost
|
||||||
|
petstore.backend.emulatorUrl=http\://10.0.2.2\:8080/
|
||||||
|
|
||||||
|
# Physical device — use the host machine's LAN IP
|
||||||
|
petstore.backend.deviceUrl=http\://192.168.x.x\:8080/
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Open the `android/` directory in Android Studio.
|
||||||
|
4. Sync Gradle, then run on an emulator or connected device.
|
||||||
|
|
||||||
|
## Running the Backend
|
||||||
|
|
||||||
|
Requires IntelliJ IDEA and Java 25+.
|
||||||
|
|
||||||
|
1. Open the `backend/` directory in IntelliJ.
|
||||||
|
2. Copy `.env.example` to `.env` and fill in your API keys.
|
||||||
|
3. Start the database using Docker from IntelliJ's **Services** panel, or from a terminal:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd backend
|
||||||
|
docker compose -f docker-compose.dev.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Run the `BackendApplication` main class from IntelliJ.
|
||||||
|
|
||||||
|
The API starts at `http://localhost:8080`. Flyway runs migrations and seeds data automatically on first boot.
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Application entry point, sets up Hilt dependency injection.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package com.example.petstoremobile;
|
package com.example.petstoremobile;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Screen where the user can request a password reset email.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package com.example.petstoremobile.activities;
|
package com.example.petstoremobile.activities;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -32,6 +38,9 @@ public class ForgotPasswordActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
private ActivityForgotPasswordBinding binding;
|
private ActivityForgotPasswordBinding binding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the content view for forget password page
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
EdgeToEdge.enable(this);
|
EdgeToEdge.enable(this);
|
||||||
@@ -54,6 +63,10 @@ public class ForgotPasswordActivity extends AppCompatActivity {
|
|||||||
binding.btnBackToLogin.setOnClickListener(v -> finish());
|
binding.btnBackToLogin.setOnClickListener(v -> finish());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function to send a reset link to the given email address.
|
||||||
|
* Calls the forgotPassword endpoint. To send the reset link
|
||||||
|
* */
|
||||||
private void sendResetLink(String email) {
|
private void sendResetLink(String email) {
|
||||||
binding.btnSubmit.setEnabled(false);
|
binding.btnSubmit.setEnabled(false);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* Main home screen that shows the dashboard after the user logs in.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package com.example.petstoremobile.activities;
|
package com.example.petstoremobile.activities;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -20,6 +29,7 @@ import androidx.navigation.fragment.NavHostFragment;
|
|||||||
import androidx.navigation.ui.NavigationUI;
|
import androidx.navigation.ui.NavigationUI;
|
||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
import com.example.petstoremobile.databinding.ActivityHomeBinding;
|
import com.example.petstoremobile.databinding.ActivityHomeBinding;
|
||||||
import com.example.petstoremobile.services.ChatNotificationService;
|
import com.example.petstoremobile.services.ChatNotificationService;
|
||||||
|
|
||||||
@@ -30,6 +40,16 @@ public class HomeActivity extends AppCompatActivity {
|
|||||||
private ActivityHomeBinding binding;
|
private ActivityHomeBinding binding;
|
||||||
private NavController navController;
|
private NavController navController;
|
||||||
|
|
||||||
|
private final BroadcastReceiver forceLogoutReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
Intent loginIntent = new Intent(HomeActivity.this, MainActivity.class);
|
||||||
|
loginIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
|
startActivity(loginIntent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Launcher to ask for notification permission
|
// Launcher to ask for notification permission
|
||||||
private final ActivityResultLauncher<String> requestPermissionLauncher =
|
private final ActivityResultLauncher<String> requestPermissionLauncher =
|
||||||
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
|
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
|
||||||
@@ -67,18 +87,30 @@ public class HomeActivity extends AppCompatActivity {
|
|||||||
handleIntent(getIntent());
|
handleIntent(getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the notification service and request for notification permission
|
IntentFilter filter = new IntentFilter(TokenManager.ACTION_FORCE_LOGOUT);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
registerReceiver(forceLogoutReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
|
||||||
|
} else {
|
||||||
|
registerReceiver(forceLogoutReceiver, filter);
|
||||||
|
}
|
||||||
|
|
||||||
startNotificationService();
|
startNotificationService();
|
||||||
requestNotificationPermission();
|
requestNotificationPermission();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
unregisterReceiver(forceLogoutReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles new intents received while the activity is already running (like notifications).
|
* Handles new intents received while the activity is already running (like notifications).
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void onNewIntent(Intent intent) {
|
protected void onNewIntent(Intent intent) {
|
||||||
super.onNewIntent(intent);
|
super.onNewIntent(intent);
|
||||||
setIntent(intent); // Set the new intent so fragments can access updated extras
|
setIntent(intent);
|
||||||
handleIntent(intent);
|
handleIntent(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +135,7 @@ public class HomeActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests POST_NOTIFICATIONS permission from the user if running on Android 13 and above.
|
* Requests for notification permission from the user if running on Android 13 and above.
|
||||||
*/
|
*/
|
||||||
private void requestNotificationPermission() {
|
private void requestNotificationPermission() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Entry point of the app that shows the login and registration screen.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
package com.example.petstoremobile.activities;
|
package com.example.petstoremobile.activities;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -98,7 +104,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the login process using the AuthViewModel and handles the authentication response.
|
* Perform login process using the AuthViewModel and handles the authentication response.
|
||||||
*/
|
*/
|
||||||
private void performLogin(String username, String password) {
|
private void performLogin(String username, String password) {
|
||||||
viewModel.login(username, password).observe(this, resource -> {
|
viewModel.login(username, password).observe(this, resource -> {
|
||||||
@@ -112,6 +118,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
case SUCCESS:
|
case SUCCESS:
|
||||||
if (resource.data != null) {
|
if (resource.data != null) {
|
||||||
String role = resource.data.getRole();
|
String role = resource.data.getRole();
|
||||||
|
//Check if role is staff/admin or customer
|
||||||
if ("CUSTOMER".equalsIgnoreCase(role)) {
|
if ("CUSTOMER".equalsIgnoreCase(role)) {
|
||||||
UIUtils.setViewsEnabled(true, binding.btnLogin);
|
UIUtils.setViewsEnabled(true, binding.btnLogin);
|
||||||
binding.tvLoginStatus.setText("Customers are not allowed to log in");
|
binding.tvLoginStatus.setText("Customers are not allowed to log in");
|
||||||
@@ -132,7 +139,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the logged-in user's profile information to save their ID before navigating to the home screen.
|
* Retrieves the user's profile information to save their ID before navigating to the home screen.
|
||||||
*/
|
*/
|
||||||
private void fetchUserIdAndNavigate() {
|
private void fetchUserIdAndNavigate() {
|
||||||
viewModel.getMe().observe(this, resource -> {
|
viewModel.getMe().observe(this, resource -> {
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Adapter for displaying activity log entries in a list.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -25,10 +32,16 @@ public class ActivityLogAdapter extends RecyclerView.Adapter<ActivityLogAdapter.
|
|||||||
|
|
||||||
private final List<ActivityLogDTO> items;
|
private final List<ActivityLogDTO> items;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for the ActivityLogAdapter.
|
||||||
|
*/
|
||||||
public ActivityLogAdapter(List<ActivityLogDTO> items) {
|
public ActivityLogAdapter(List<ActivityLogDTO> items) {
|
||||||
this.items = items;
|
this.items = items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout for an activity log item and creates a new ViewHolder.
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
@@ -37,6 +50,9 @@ public class ActivityLogAdapter extends RecyclerView.Adapter<ActivityLogAdapter.
|
|||||||
return new ViewHolder(view);
|
return new ViewHolder(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds the data from ActivityLogDTO to the items in the ViewHolder.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||||
ActivityLogDTO log = items.get(position);
|
ActivityLogDTO log = items.get(position);
|
||||||
@@ -71,9 +87,15 @@ public class ActivityLogAdapter extends RecyclerView.Adapter<ActivityLogAdapter.
|
|||||||
holder.tvTimestamp.setText(formatTimestamp(log.getLogTimestamp()));
|
holder.tvTimestamp.setText(formatTimestamp(log.getLogTimestamp()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total number of items in the list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() { return items.size(); }
|
public int getItemCount() { return items.size(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the timestamp string from the API to a readable date/time format.
|
||||||
|
*/
|
||||||
private String formatTimestamp(String raw) {
|
private String formatTimestamp(String raw) {
|
||||||
if (raw == null) return "";
|
if (raw == null) return "";
|
||||||
try {
|
try {
|
||||||
@@ -85,6 +107,9 @@ public class ActivityLogAdapter extends RecyclerView.Adapter<ActivityLogAdapter.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the Role string to be consistent
|
||||||
|
*/
|
||||||
private String formatRole(String role) {
|
private String formatRole(String role) {
|
||||||
if (role == null) return "";
|
if (role == null) return "";
|
||||||
switch (role.toUpperCase(Locale.ROOT)) {
|
switch (role.toUpperCase(Locale.ROOT)) {
|
||||||
@@ -95,6 +120,9 @@ public class ActivityLogAdapter extends RecyclerView.Adapter<ActivityLogAdapter.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first non-null and non-blank string from the provided arguments.
|
||||||
|
*/
|
||||||
private String firstNonBlank(String... values) {
|
private String firstNonBlank(String... values) {
|
||||||
for (String v : values) {
|
for (String v : values) {
|
||||||
if (v != null && !v.isBlank()) return v;
|
if (v != null && !v.isBlank()) return v;
|
||||||
@@ -102,9 +130,15 @@ public class ActivityLogAdapter extends RecyclerView.Adapter<ActivityLogAdapter.
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewHolder class that holds references to the UI components for an activity log item.
|
||||||
|
*/
|
||||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
TextView tvActivity, tvTechnical, tvUser, tvMeta, tvTimestamp;
|
TextView tvActivity, tvTechnical, tvUser, tvMeta, tvTimestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewHolder by finding the views within the item layout.
|
||||||
|
*/
|
||||||
public ViewHolder(@NonNull View itemView) {
|
public ViewHolder(@NonNull View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
tvActivity = itemView.findViewById(R.id.tvLogActivity);
|
tvActivity = itemView.findViewById(R.id.tvLogActivity);
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Adapter for showing adoption records in a list.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
@@ -23,6 +30,9 @@ public class AdoptionAdapter extends RecyclerView.Adapter<AdoptionAdapter.Adopti
|
|||||||
void onSelectionChanged(int count);
|
void onSelectionChanged(int count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for AdoptionAdapter.
|
||||||
|
*/
|
||||||
public AdoptionAdapter(List<AdoptionDTO> adoptionList, OnAdoptionClickListener listener) {
|
public AdoptionAdapter(List<AdoptionDTO> adoptionList, OnAdoptionClickListener listener) {
|
||||||
this.adoptionList = adoptionList;
|
this.adoptionList = adoptionList;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
@@ -39,11 +49,17 @@ public class AdoptionAdapter extends RecyclerView.Adapter<AdoptionAdapter.Adopti
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of IDs for the currently selected adoption items.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<String> getSelectedKeys() {
|
public List<String> getSelectedKeys() {
|
||||||
return selectionHelper.getSelectedKeys();
|
return selectionHelper.getSelectedKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the selection state, deselecting all items.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void clearSelection() {
|
public void clearSelection() {
|
||||||
selectionHelper.clearSelection();
|
selectionHelper.clearSelection();
|
||||||
@@ -58,6 +74,9 @@ public class AdoptionAdapter extends RecyclerView.Adapter<AdoptionAdapter.Adopti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout for an adoption item and creates the ViewHolder.
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public AdoptionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public AdoptionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
@@ -65,6 +84,9 @@ public class AdoptionAdapter extends RecyclerView.Adapter<AdoptionAdapter.Adopti
|
|||||||
return new AdoptionViewHolder(binding);
|
return new AdoptionViewHolder(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds adoption data to the UI components and handles click/long-click logic.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull AdoptionViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull AdoptionViewHolder holder, int position) {
|
||||||
AdoptionDTO a = adoptionList.get(position);
|
AdoptionDTO a = adoptionList.get(position);
|
||||||
@@ -123,6 +145,9 @@ public class AdoptionAdapter extends RecyclerView.Adapter<AdoptionAdapter.Adopti
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total number of adoption items in the list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() { return adoptionList.size(); }
|
public int getItemCount() { return adoptionList.size(); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Adapter for showing appointments in a list.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
@@ -23,6 +30,9 @@ public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.
|
|||||||
void onSelectionChanged(int count);
|
void onSelectionChanged(int count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for AppointmentAdapter.
|
||||||
|
*/
|
||||||
public AppointmentAdapter(List<AppointmentDTO> appointmentList,
|
public AppointmentAdapter(List<AppointmentDTO> appointmentList,
|
||||||
OnAppointmentClickListener appointmentClickListener) {
|
OnAppointmentClickListener appointmentClickListener) {
|
||||||
this.appointmentList = appointmentList;
|
this.appointmentList = appointmentList;
|
||||||
@@ -40,25 +50,40 @@ public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of IDs for the currently selected appointment items.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<String> getSelectedKeys() {
|
public List<String> getSelectedKeys() {
|
||||||
return selectionHelper.getSelectedKeys();
|
return selectionHelper.getSelectedKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the selection state, deselecting all items.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void clearSelection() {
|
public void clearSelection() {
|
||||||
selectionHelper.clearSelection();
|
selectionHelper.clearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewHolder class that holds references to the UI components for an appointment item.
|
||||||
|
*/
|
||||||
public static class AppointmentViewHolder extends RecyclerView.ViewHolder {
|
public static class AppointmentViewHolder extends RecyclerView.ViewHolder {
|
||||||
private final ItemAppointmentBinding binding;
|
private final ItemAppointmentBinding binding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewHolder by finding the views within the item layout.
|
||||||
|
*/
|
||||||
public AppointmentViewHolder(@NonNull ItemAppointmentBinding binding) {
|
public AppointmentViewHolder(@NonNull ItemAppointmentBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout for an appointment item and creates the ViewHolder.
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public AppointmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public AppointmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
@@ -66,6 +91,9 @@ public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.
|
|||||||
return new AppointmentViewHolder(binding);
|
return new AppointmentViewHolder(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds appointment data to the UI components and handles click/long-click logic.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull AppointmentViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull AppointmentViewHolder holder, int position) {
|
||||||
AppointmentDTO a = appointmentList.get(position);
|
AppointmentDTO a = appointmentList.get(position);
|
||||||
@@ -124,6 +152,9 @@ public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total number of appointment items in the list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return appointmentList.size();
|
return appointmentList.size();
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Custom array adapter that displays items with black text.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -12,6 +19,7 @@ import com.example.petstoremobile.R;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
// A class that overrides the arrayAdapter so the text color changes based on theme
|
// A class that overrides the arrayAdapter so the text color changes based on theme
|
||||||
|
// Used to make spinners have black text on white background no matter the theme
|
||||||
public class BlackTextArrayAdapter<T> extends ArrayAdapter<T> {
|
public class BlackTextArrayAdapter<T> extends ArrayAdapter<T> {
|
||||||
public BlackTextArrayAdapter(@NonNull Context context, int resource, @NonNull T[] objects) {
|
public BlackTextArrayAdapter(@NonNull Context context, int resource, @NonNull T[] objects) {
|
||||||
super(context, resource, objects);
|
super(context, resource, objects);
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Adapter for displaying chat conversations in a list.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -20,11 +27,17 @@ public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.ChatViewHolder
|
|||||||
void onChatClick(Chat chat);
|
void onChatClick(Chat chat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for ChatAdapter.
|
||||||
|
*/
|
||||||
public ChatAdapter(List<Chat> chatList, OnChatClickListener listener) {
|
public ChatAdapter(List<Chat> chatList, OnChatClickListener listener) {
|
||||||
this.chatList = chatList;
|
this.chatList = chatList;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout for a chat item and creates the ViewHolder.
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public ChatViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public ChatViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
@@ -32,6 +45,9 @@ public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.ChatViewHolder
|
|||||||
return new ChatViewHolder(binding);
|
return new ChatViewHolder(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds chat data to the UI components for a specific conversation.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull ChatViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull ChatViewHolder holder, int position) {
|
||||||
Chat chat = chatList.get(position);
|
Chat chat = chatList.get(position);
|
||||||
@@ -40,14 +56,23 @@ public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.ChatViewHolder
|
|||||||
holder.itemView.setOnClickListener(v -> listener.onChatClick(chat));
|
holder.itemView.setOnClickListener(v -> listener.onChatClick(chat));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total number of chat items in the list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return chatList.size();
|
return chatList.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewHolder class that holds references to the UI components for a chat item.
|
||||||
|
*/
|
||||||
public static class ChatViewHolder extends RecyclerView.ViewHolder {
|
public static class ChatViewHolder extends RecyclerView.ViewHolder {
|
||||||
final ItemChatBinding binding;
|
final ItemChatBinding binding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewHolder with the chat item's view binding.
|
||||||
|
*/
|
||||||
public ChatViewHolder(@NonNull ItemChatBinding binding) {
|
public ChatViewHolder(@NonNull ItemChatBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Adapter for showing coupons in a list.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -30,11 +37,17 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.ViewHolder
|
|||||||
void onSelectionChanged(int count);
|
void onSelectionChanged(int count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for CouponAdapter.
|
||||||
|
*/
|
||||||
public CouponAdapter(List<CouponDTO> coupons, OnCouponClickListener listener) {
|
public CouponAdapter(List<CouponDTO> coupons, OnCouponClickListener listener) {
|
||||||
this.coupons = coupons;
|
this.coupons = coupons;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout for a coupon item and creates the ViewHolder.
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
@@ -42,6 +55,9 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.ViewHolder
|
|||||||
return new ViewHolder(view);
|
return new ViewHolder(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds coupon data to the UI components and handles interaction logic.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||||
CouponDTO coupon = coupons.get(position);
|
CouponDTO coupon = coupons.get(position);
|
||||||
@@ -95,6 +111,9 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.ViewHolder
|
|||||||
holder.cbSelectCoupon.setOnClickListener(v -> toggleSelection(coupon.getCouponId()));
|
holder.cbSelectCoupon.setOnClickListener(v -> toggleSelection(coupon.getCouponId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the selection state of a specific coupon by its ID.
|
||||||
|
*/
|
||||||
private void toggleSelection(Long id) {
|
private void toggleSelection(Long id) {
|
||||||
if (selectedIds.contains(id)) {
|
if (selectedIds.contains(id)) {
|
||||||
selectedIds.remove(id);
|
selectedIds.remove(id);
|
||||||
@@ -105,6 +124,9 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.ViewHolder
|
|||||||
listener.onSelectionChanged(selectedIds.size());
|
listener.onSelectionChanged(selectedIds.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables bulk selection mode.
|
||||||
|
*/
|
||||||
public void setSelectionMode(boolean selectionMode) {
|
public void setSelectionMode(boolean selectionMode) {
|
||||||
this.selectionMode = selectionMode;
|
this.selectionMode = selectionMode;
|
||||||
if (!selectionMode) selectedIds.clear();
|
if (!selectionMode) selectedIds.clear();
|
||||||
@@ -112,10 +134,16 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.ViewHolder
|
|||||||
listener.onSelectionChanged(selectedIds.size());
|
listener.onSelectionChanged(selectedIds.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the set of IDs for the currently selected coupons.
|
||||||
|
*/
|
||||||
public Set<Long> getSelectedIds() {
|
public Set<Long> getSelectedIds() {
|
||||||
return selectedIds;
|
return selectedIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total number of coupons in the list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return coupons.size();
|
return coupons.size();
|
||||||
@@ -125,6 +153,9 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.ViewHolder
|
|||||||
TextView tvCouponCode, tvCouponDiscount, tvCouponMinOrder, tvCouponExpiry, tvCouponStatus;
|
TextView tvCouponCode, tvCouponDiscount, tvCouponMinOrder, tvCouponExpiry, tvCouponStatus;
|
||||||
CheckBox cbSelectCoupon;
|
CheckBox cbSelectCoupon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewHolder by finding the views within the item layout.
|
||||||
|
*/
|
||||||
public ViewHolder(@NonNull View itemView) {
|
public ViewHolder(@NonNull View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
tvCouponCode = itemView.findViewById(R.id.tvCouponCode);
|
tvCouponCode = itemView.findViewById(R.id.tvCouponCode);
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Adapter for displaying customer entries in a list.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
@@ -24,23 +31,42 @@ public class CustomerAdapter extends RecyclerView.Adapter<CustomerAdapter.Custom
|
|||||||
void onCustomerClick(int position);
|
void onCustomerClick(int position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for CustomerAdapter.
|
||||||
|
*/
|
||||||
public CustomerAdapter(List<CustomerDTO> list, OnCustomerClickListener listener) {
|
public CustomerAdapter(List<CustomerDTO> list, OnCustomerClickListener listener) {
|
||||||
this.list = list;
|
this.list = list;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the base URL for fetching customer profile images.
|
||||||
|
*/
|
||||||
public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; }
|
public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the authentication token for fetching images.
|
||||||
|
*/
|
||||||
public void setToken(String token) { this.token = token; }
|
public void setToken(String token) { this.token = token; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewHolder class for customer items.
|
||||||
|
*/
|
||||||
public static class CustomerViewHolder extends RecyclerView.ViewHolder {
|
public static class CustomerViewHolder extends RecyclerView.ViewHolder {
|
||||||
final ItemCustomerBinding binding;
|
final ItemCustomerBinding binding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewHolder with view binding.
|
||||||
|
*/
|
||||||
public CustomerViewHolder(@NonNull ItemCustomerBinding binding) {
|
public CustomerViewHolder(@NonNull ItemCustomerBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout for a customer item and creates the ViewHolder.
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public CustomerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public CustomerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
@@ -49,6 +75,9 @@ public class CustomerAdapter extends RecyclerView.Adapter<CustomerAdapter.Custom
|
|||||||
return new CustomerViewHolder(binding);
|
return new CustomerViewHolder(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds customer data to the UI components.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull CustomerViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull CustomerViewHolder holder, int position) {
|
||||||
CustomerDTO c = list.get(position);
|
CustomerDTO c = list.get(position);
|
||||||
@@ -75,6 +104,9 @@ public class CustomerAdapter extends RecyclerView.Adapter<CustomerAdapter.Custom
|
|||||||
holder.itemView.setOnClickListener(v -> listener.onCustomerClick(position));
|
holder.itemView.setOnClickListener(v -> listener.onCustomerClick(position));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total number of customers in the list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() { return list.size(); }
|
public int getItemCount() { return list.size(); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Adapter for displaying employee entries in a list.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
@@ -25,28 +32,46 @@ public class EmployeeAdapter extends RecyclerView.Adapter<EmployeeAdapter.Employ
|
|||||||
void onEmployeeClick(int position);
|
void onEmployeeClick(int position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for EmployeeAdapter.
|
||||||
|
*/
|
||||||
public EmployeeAdapter(List<EmployeeDTO> list, OnEmployeeClickListener listener) {
|
public EmployeeAdapter(List<EmployeeDTO> list, OnEmployeeClickListener listener) {
|
||||||
this.list = list;
|
this.list = list;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the base URL for fetching employee profile images.
|
||||||
|
*/
|
||||||
public void setBaseUrl(String baseUrl) {
|
public void setBaseUrl(String baseUrl) {
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the authentication token for fetching images.
|
||||||
|
*/
|
||||||
public void setToken(String token) {
|
public void setToken(String token) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewHolder class for employee items.
|
||||||
|
*/
|
||||||
public static class EmployeeViewHolder extends RecyclerView.ViewHolder {
|
public static class EmployeeViewHolder extends RecyclerView.ViewHolder {
|
||||||
private final ItemEmployeeBinding binding;
|
private final ItemEmployeeBinding binding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewHolder with view binding.
|
||||||
|
*/
|
||||||
public EmployeeViewHolder(@NonNull ItemEmployeeBinding binding) {
|
public EmployeeViewHolder(@NonNull ItemEmployeeBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout for an employee item and creates the ViewHolder.
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public EmployeeViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public EmployeeViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
@@ -55,6 +80,9 @@ public class EmployeeAdapter extends RecyclerView.Adapter<EmployeeAdapter.Employ
|
|||||||
return new EmployeeViewHolder(binding);
|
return new EmployeeViewHolder(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds employee data to the UI components.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull EmployeeViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull EmployeeViewHolder holder, int position) {
|
||||||
EmployeeDTO e = list.get(position);
|
EmployeeDTO e = list.get(position);
|
||||||
@@ -90,6 +118,9 @@ public class EmployeeAdapter extends RecyclerView.Adapter<EmployeeAdapter.Employ
|
|||||||
holder.itemView.setOnClickListener(v -> listener.onEmployeeClick(position));
|
holder.itemView.setOnClickListener(v -> listener.onEmployeeClick(position));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total number of employees in the list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() { return list.size(); }
|
public int getItemCount() { return list.size(); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Adapter for showing inventory items in a list.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
@@ -27,6 +34,9 @@ public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.Inve
|
|||||||
void onSelectionChanged(int selectedCount);
|
void onSelectionChanged(int selectedCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for InventoryAdapter.
|
||||||
|
*/
|
||||||
public InventoryAdapter(List<InventoryDTO> inventoryList, OnInventoryClickListener clickListener) {
|
public InventoryAdapter(List<InventoryDTO> inventoryList, OnInventoryClickListener clickListener) {
|
||||||
this.inventoryList = inventoryList;
|
this.inventoryList = inventoryList;
|
||||||
this.clickListener = clickListener;
|
this.clickListener = clickListener;
|
||||||
@@ -43,25 +53,40 @@ public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.Inve
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of IDs for the currently selected inventory items.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<String> getSelectedKeys() {
|
public List<String> getSelectedKeys() {
|
||||||
return selectionHelper.getSelectedKeys();
|
return selectionHelper.getSelectedKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the selection state, deselecting all items.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void clearSelection() {
|
public void clearSelection() {
|
||||||
selectionHelper.clearSelection();
|
selectionHelper.clearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewHolder class that holds references to the UI components for an inventory item.
|
||||||
|
*/
|
||||||
public static class InventoryViewHolder extends RecyclerView.ViewHolder {
|
public static class InventoryViewHolder extends RecyclerView.ViewHolder {
|
||||||
final ItemInventoryBinding binding;
|
final ItemInventoryBinding binding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewHolder with view binding.
|
||||||
|
*/
|
||||||
public InventoryViewHolder(@NonNull ItemInventoryBinding binding) {
|
public InventoryViewHolder(@NonNull ItemInventoryBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout for an inventory item and creates the ViewHolder.
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public InventoryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public InventoryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
@@ -69,6 +94,9 @@ public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.Inve
|
|||||||
return new InventoryViewHolder(binding);
|
return new InventoryViewHolder(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds inventory data to the UI components.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull InventoryViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull InventoryViewHolder holder, int position) {
|
||||||
InventoryDTO inv = inventoryList.get(position);
|
InventoryDTO inv = inventoryList.get(position);
|
||||||
@@ -84,7 +112,6 @@ public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.Inve
|
|||||||
int qty = inv.getQuantity() != null ? inv.getQuantity() : 0;
|
int qty = inv.getQuantity() != null ? inv.getQuantity() : 0;
|
||||||
binding.tvQuantity.setText("Stock: " + qty);
|
binding.tvQuantity.setText("Stock: " + qty);
|
||||||
|
|
||||||
// Low stock = red, normal = green (like desktop reorder concept)
|
|
||||||
if (qty <= 5) {
|
if (qty <= 5) {
|
||||||
binding.tvQuantity.setTextColor(Color.parseColor("#F44336"));
|
binding.tvQuantity.setTextColor(Color.parseColor("#F44336"));
|
||||||
} else {
|
} else {
|
||||||
@@ -119,6 +146,9 @@ public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.Inve
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total number of inventory items in the list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return inventoryList.size();
|
return inventoryList.size();
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Adapter for displaying chat messages in a conversation.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -37,40 +44,64 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
private String baseUrl;
|
private String baseUrl;
|
||||||
private OnAttachmentClickListener attachmentClickListener;
|
private OnAttachmentClickListener attachmentClickListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for MessageAdapter.
|
||||||
|
*/
|
||||||
public MessageAdapter(List<Message> messages, Long currentUserId) {
|
public MessageAdapter(List<Message> messages, Long currentUserId) {
|
||||||
this.messages = messages;
|
this.messages = messages;
|
||||||
this.currentUserId = currentUserId;
|
this.currentUserId = currentUserId;
|
||||||
setHasStableIds(true);
|
setHasStableIds(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an ID for each message.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public long getItemId(int position) {
|
public long getItemId(int position) {
|
||||||
Message m = messages.get(position);
|
Message m = messages.get(position);
|
||||||
return m.getId() != null ? m.getId() : position;
|
return m.getId() != null ? m.getId() : position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current user's ID and refreshes the list.
|
||||||
|
*/
|
||||||
public void setCurrentUserId(Long id) {
|
public void setCurrentUserId(Long id) {
|
||||||
this.currentUserId = id;
|
this.currentUserId = id;
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the staff ID to identify staff messages in the UI.
|
||||||
|
*/
|
||||||
public void setStaffId(Long id) {
|
public void setStaffId(Long id) {
|
||||||
this.staffId = id;
|
this.staffId = id;
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the authentication token.
|
||||||
|
*/
|
||||||
public void setToken(String token) {
|
public void setToken(String token) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the base API URL.
|
||||||
|
*/
|
||||||
public void setBaseUrl(String baseUrl) {
|
public void setBaseUrl(String baseUrl) {
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a listener for clicks on message attachments.
|
||||||
|
*/
|
||||||
public void setOnAttachmentClickListener(OnAttachmentClickListener listener) {
|
public void setOnAttachmentClickListener(OnAttachmentClickListener listener) {
|
||||||
this.attachmentClickListener = listener;
|
this.attachmentClickListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a message is 'sent' or 'received' based on the sender's ID.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int getItemViewType(int position) {
|
public int getItemViewType(int position) {
|
||||||
Message m = messages.get(position);
|
Message m = messages.get(position);
|
||||||
@@ -80,6 +111,9 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
return TYPE_RECEIVED;
|
return TYPE_RECEIVED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the chat layout for a message.
|
||||||
|
*/
|
||||||
@NonNull @Override
|
@NonNull @Override
|
||||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
LayoutInflater inf = LayoutInflater.from(parent.getContext());
|
LayoutInflater inf = LayoutInflater.from(parent.getContext());
|
||||||
@@ -92,6 +126,9 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds message data to the appropriate ViewHolder.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||||
Message m = messages.get(position);
|
Message m = messages.get(position);
|
||||||
@@ -99,14 +136,26 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
if (holder instanceof ReceivedHolder) ((ReceivedHolder) holder).bind(m, token, baseUrl, attachmentClickListener, staffId);
|
if (holder instanceof ReceivedHolder) ((ReceivedHolder) holder).bind(m, token, baseUrl, attachmentClickListener, staffId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total number of messages.
|
||||||
|
*/
|
||||||
@Override public int getItemCount() { return messages.size(); }
|
@Override public int getItemCount() { return messages.size(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewHolder for messages sent by the user.
|
||||||
|
*/
|
||||||
static class SentHolder extends RecyclerView.ViewHolder {
|
static class SentHolder extends RecyclerView.ViewHolder {
|
||||||
final ItemMessageSentBinding binding;
|
final ItemMessageSentBinding binding;
|
||||||
|
/**
|
||||||
|
* Initializes the SentHolder with view binding.
|
||||||
|
*/
|
||||||
SentHolder(ItemMessageSentBinding binding) {
|
SentHolder(ItemMessageSentBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Binds sent message data to the bubble UI.
|
||||||
|
*/
|
||||||
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener) {
|
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener) {
|
||||||
binding.tvSenderName.setText("You");
|
binding.tvSenderName.setText("You");
|
||||||
binding.tvTimestamp.setText(formatTimestamp(m.getTimestamp()));
|
binding.tvTimestamp.setText(formatTimestamp(m.getTimestamp()));
|
||||||
@@ -128,12 +177,21 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewHolder for messages received from others.
|
||||||
|
*/
|
||||||
static class ReceivedHolder extends RecyclerView.ViewHolder {
|
static class ReceivedHolder extends RecyclerView.ViewHolder {
|
||||||
final ItemMessageReceivedBinding binding;
|
final ItemMessageReceivedBinding binding;
|
||||||
|
/**
|
||||||
|
* Initializes the ReceivedHolder with view binding.
|
||||||
|
*/
|
||||||
ReceivedHolder(ItemMessageReceivedBinding binding) {
|
ReceivedHolder(ItemMessageReceivedBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Binds received message data to the bubble UI.
|
||||||
|
*/
|
||||||
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener, Long staffId) {
|
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener, Long staffId) {
|
||||||
binding.tvSenderName.setText(resolveSenderName(m, staffId));
|
binding.tvSenderName.setText(resolveSenderName(m, staffId));
|
||||||
binding.tvTimestamp.setText(formatTimestamp(m.getTimestamp()));
|
binding.tvTimestamp.setText(formatTimestamp(m.getTimestamp()));
|
||||||
@@ -155,6 +213,9 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the display name of the sender.
|
||||||
|
*/
|
||||||
private static String resolveSenderName(Message m, Long staffId) {
|
private static String resolveSenderName(Message m, Long staffId) {
|
||||||
if ("BOT".equalsIgnoreCase(m.getSenderRole())) {
|
if ("BOT".equalsIgnoreCase(m.getSenderRole())) {
|
||||||
return (m.getSenderDisplayName() != null && !m.getSenderDisplayName().isEmpty())
|
return (m.getSenderDisplayName() != null && !m.getSenderDisplayName().isEmpty())
|
||||||
@@ -166,6 +227,9 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
return "Customer";
|
return "Customer";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the timestamp string into readable format.
|
||||||
|
*/
|
||||||
private static String formatTimestamp(String timestamp) {
|
private static String formatTimestamp(String timestamp) {
|
||||||
if (timestamp == null || timestamp.isEmpty()) return "";
|
if (timestamp == null || timestamp.isEmpty()) return "";
|
||||||
try {
|
try {
|
||||||
@@ -178,8 +242,11 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logic for displaying an attachment in the chat.
|
||||||
|
*/
|
||||||
private static void displayAttachment(Message m, ImageView iv, TextView tvName, String token, String baseUrl) {
|
private static void displayAttachment(Message m, ImageView iv, TextView tvName, String token, String baseUrl) {
|
||||||
// Check if there's an attachment by looking at name or mime type
|
// Check if there's an attachment
|
||||||
if (m.getAttachmentName() != null || m.getAttachmentMimeType() != null) {
|
if (m.getAttachmentName() != null || m.getAttachmentMimeType() != null) {
|
||||||
// Construct the download URL using the message ID
|
// Construct the download URL using the message ID
|
||||||
String url;
|
String url;
|
||||||
@@ -187,7 +254,7 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
String cleanBase = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
|
String cleanBase = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
|
||||||
url = cleanBase + "/api/v1/chat/messages/" + m.getId() + "/attachment";
|
url = cleanBase + "/api/v1/chat/messages/" + m.getId() + "/attachment";
|
||||||
} else {
|
} else {
|
||||||
url = m.getAttachmentUrl(); // Fallback
|
url = m.getAttachmentUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url == null) {
|
if (url == null) {
|
||||||
@@ -208,7 +275,7 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use a signature to prevent Glide from showing stale cached images for the same URL/ID
|
// Use a signature to prevent Glide from showing cached images instead of loading new ones
|
||||||
String signatureKey = (m.getTimestamp() != null ? m.getTimestamp() : "") + m.getId();
|
String signatureKey = (m.getTimestamp() != null ? m.getTimestamp() : "") + m.getId();
|
||||||
|
|
||||||
Glide.with(iv.getContext()).clear(iv);
|
Glide.with(iv.getContext()).clear(iv);
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Adapter for showing pets in a list.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
@@ -31,7 +38,9 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> i
|
|||||||
void onSelectionChanged(int selectedCount);
|
void onSelectionChanged(int selectedCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Constructor
|
/**
|
||||||
|
* Constructor for PetAdapter.
|
||||||
|
*/
|
||||||
public PetAdapter(List<PetDTO> petList, OnPetClickListener petClickListener) {
|
public PetAdapter(List<PetDTO> petList, OnPetClickListener petClickListener) {
|
||||||
this.petList = petList;
|
this.petList = petList;
|
||||||
this.petClickListener = petClickListener;
|
this.petClickListener = petClickListener;
|
||||||
@@ -48,35 +57,54 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> i
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the base URL for fetching pet images from the server.
|
||||||
|
*/
|
||||||
public void setBaseUrl(String baseUrl) {
|
public void setBaseUrl(String baseUrl) {
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the authentication token
|
||||||
|
*/
|
||||||
public void setToken(String token) {
|
public void setToken(String token) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of IDs for the currently selected pet items.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<String> getSelectedKeys() {
|
public List<String> getSelectedKeys() {
|
||||||
return selectionHelper.getSelectedKeys();
|
return selectionHelper.getSelectedKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the selection state, deselecting all items.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void clearSelection() {
|
public void clearSelection() {
|
||||||
selectionHelper.clearSelection();
|
selectionHelper.clearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the controls of each row in recycler view
|
/**
|
||||||
|
* ViewHolder class that holds references to the UI components for a pet item.
|
||||||
|
*/
|
||||||
public static class PetViewHolder extends RecyclerView.ViewHolder {
|
public static class PetViewHolder extends RecyclerView.ViewHolder {
|
||||||
private final ItemPetBinding binding;
|
private final ItemPetBinding binding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewHolder with view binding.
|
||||||
|
*/
|
||||||
public PetViewHolder(@NonNull ItemPetBinding binding) {
|
public PetViewHolder(@NonNull ItemPetBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new row view
|
/**
|
||||||
|
* Inflates the layout for a pet item and creates the ViewHolder.
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public PetViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public PetViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
@@ -84,7 +112,9 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> i
|
|||||||
return new PetViewHolder(binding);
|
return new PetViewHolder(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
//populate the row with pet data
|
/**
|
||||||
|
* Binds pet data to the UI components.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull PetViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull PetViewHolder holder, int position) {
|
||||||
PetDTO pet = petList.get(position);
|
PetDTO pet = petList.get(position);
|
||||||
@@ -103,7 +133,7 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> i
|
|||||||
|
|
||||||
binding.tvPetStatus.setText(pet.getPetStatus());
|
binding.tvPetStatus.setText(pet.getPetStatus());
|
||||||
|
|
||||||
//Set the status color depending on availability. If available, green, If Pending, yellow, otherwise red
|
//Set the status color depending on availability
|
||||||
if (pet.getPetStatus() != null) {
|
if (pet.getPetStatus() != null) {
|
||||||
switch (pet.getPetStatus()) {
|
switch (pet.getPetStatus()) {
|
||||||
case "Available":
|
case "Available":
|
||||||
@@ -157,6 +187,9 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> i
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total number of pet items in the list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return petList.size();
|
return petList.size();
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Adapter for displaying products in a list.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
@@ -22,28 +29,46 @@ public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductV
|
|||||||
void onProductClick(int position);
|
void onProductClick(int position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for ProductAdapter.
|
||||||
|
*/
|
||||||
public ProductAdapter(List<ProductDTO> productList, OnProductClickListener listener) {
|
public ProductAdapter(List<ProductDTO> productList, OnProductClickListener listener) {
|
||||||
this.productList = productList;
|
this.productList = productList;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the base URL for fetching product images.
|
||||||
|
*/
|
||||||
public void setBaseUrl(String baseUrl) {
|
public void setBaseUrl(String baseUrl) {
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the authentication token
|
||||||
|
*/
|
||||||
public void setToken(String token) {
|
public void setToken(String token) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewHolder class for product items.
|
||||||
|
*/
|
||||||
public static class ProductViewHolder extends RecyclerView.ViewHolder {
|
public static class ProductViewHolder extends RecyclerView.ViewHolder {
|
||||||
final ItemProductBinding binding;
|
final ItemProductBinding binding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewHolder with view binding.
|
||||||
|
*/
|
||||||
public ProductViewHolder(@NonNull ItemProductBinding binding) {
|
public ProductViewHolder(@NonNull ItemProductBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout for a product item and creates the ViewHolder.
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public ProductViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public ProductViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
@@ -51,6 +76,9 @@ public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductV
|
|||||||
return new ProductViewHolder(binding);
|
return new ProductViewHolder(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds product data to the UI components.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull ProductViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull ProductViewHolder holder, int position) {
|
||||||
ProductDTO p = productList.get(position);
|
ProductDTO p = productList.get(position);
|
||||||
@@ -72,6 +100,9 @@ public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductV
|
|||||||
holder.itemView.setOnClickListener(v -> listener.onProductClick(position));
|
holder.itemView.setOnClickListener(v -> listener.onProductClick(position));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total number of product items in the list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() { return productList.size(); }
|
public int getItemCount() { return productList.size(); }
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Adapter for showing product-supplier links in a list.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -25,6 +32,9 @@ public class ProductSupplierAdapter extends RecyclerView.Adapter<ProductSupplier
|
|||||||
void onSelectionChanged(int count);
|
void onSelectionChanged(int count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for ProductSupplierAdapter.
|
||||||
|
*/
|
||||||
public ProductSupplierAdapter(List<ProductSupplierDTO> list, OnProductSupplierClickListener listener) {
|
public ProductSupplierAdapter(List<ProductSupplierDTO> list, OnProductSupplierClickListener listener) {
|
||||||
this.list = list;
|
this.list = list;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
@@ -41,25 +51,40 @@ public class ProductSupplierAdapter extends RecyclerView.Adapter<ProductSupplier
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of selected keys for bulk deletion.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<String> getSelectedKeys() {
|
public List<String> getSelectedKeys() {
|
||||||
return selectionHelper.getSelectedKeys();
|
return selectionHelper.getSelectedKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all selected items and exits selection mode.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void clearSelection() {
|
public void clearSelection() {
|
||||||
selectionHelper.clearSelection();
|
selectionHelper.clearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewHolder for Product-Supplier relationship items.
|
||||||
|
*/
|
||||||
public static class PSViewHolder extends RecyclerView.ViewHolder {
|
public static class PSViewHolder extends RecyclerView.ViewHolder {
|
||||||
final ItemProductSupplierBinding binding;
|
final ItemProductSupplierBinding binding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewHolder with view binding.
|
||||||
|
*/
|
||||||
public PSViewHolder(@NonNull ItemProductSupplierBinding binding) {
|
public PSViewHolder(@NonNull ItemProductSupplierBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout for a Product-Supplier item.
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public PSViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public PSViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
@@ -67,6 +92,9 @@ public class ProductSupplierAdapter extends RecyclerView.Adapter<ProductSupplier
|
|||||||
return new PSViewHolder(binding);
|
return new PSViewHolder(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds product-supplier data to the UI components.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull PSViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull PSViewHolder holder, int position) {
|
||||||
ProductSupplierDTO ps = list.get(position);
|
ProductSupplierDTO ps = list.get(position);
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Adapter for displaying purchase orders in a list.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -17,20 +24,32 @@ public class PurchaseOrderAdapter extends RecyclerView.Adapter<PurchaseOrderAdap
|
|||||||
void onPurchaseOrderClick(int position);
|
void onPurchaseOrderClick(int position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for PurchaseOrderAdapter.
|
||||||
|
*/
|
||||||
public PurchaseOrderAdapter(List<PurchaseOrderDTO> list, OnPurchaseOrderClickListener listener) {
|
public PurchaseOrderAdapter(List<PurchaseOrderDTO> list, OnPurchaseOrderClickListener listener) {
|
||||||
this.list = list;
|
this.list = list;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewHolder for Purchase Order items.
|
||||||
|
*/
|
||||||
public static class POViewHolder extends RecyclerView.ViewHolder {
|
public static class POViewHolder extends RecyclerView.ViewHolder {
|
||||||
final ItemPurchaseOrderBinding binding;
|
final ItemPurchaseOrderBinding binding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewHolder with view binding.
|
||||||
|
*/
|
||||||
public POViewHolder(@NonNull ItemPurchaseOrderBinding binding) {
|
public POViewHolder(@NonNull ItemPurchaseOrderBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout for a Purchase Order item.
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public POViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public POViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
@@ -38,6 +57,9 @@ public class PurchaseOrderAdapter extends RecyclerView.Adapter<PurchaseOrderAdap
|
|||||||
return new POViewHolder(binding);
|
return new POViewHolder(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds purchase order data to the UI components.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull POViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull POViewHolder holder, int position) {
|
||||||
PurchaseOrderDTO po = list.get(position);
|
PurchaseOrderDTO po = list.get(position);
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Adapter for showing sales in a list.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -20,20 +27,32 @@ public class SaleAdapter extends RecyclerView.Adapter<SaleAdapter.SaleViewHolder
|
|||||||
void onSaleClick(int position);
|
void onSaleClick(int position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for SaleAdapter.
|
||||||
|
*/
|
||||||
public SaleAdapter(List<SaleDTO> saleList, OnSaleClickListener listener) {
|
public SaleAdapter(List<SaleDTO> saleList, OnSaleClickListener listener) {
|
||||||
this.saleList = saleList;
|
this.saleList = saleList;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewHolder for Sale record items.
|
||||||
|
*/
|
||||||
public static class SaleViewHolder extends RecyclerView.ViewHolder {
|
public static class SaleViewHolder extends RecyclerView.ViewHolder {
|
||||||
final ItemSaleBinding binding;
|
final ItemSaleBinding binding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewHolder with view binding.
|
||||||
|
*/
|
||||||
public SaleViewHolder(@NonNull ItemSaleBinding binding) {
|
public SaleViewHolder(@NonNull ItemSaleBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout for a Sale record item.
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public SaleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public SaleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
@@ -41,6 +60,9 @@ public class SaleAdapter extends RecyclerView.Adapter<SaleAdapter.SaleViewHolder
|
|||||||
return new SaleViewHolder(binding);
|
return new SaleViewHolder(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds sale transaction data to the UI components.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull SaleViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull SaleViewHolder holder, int position) {
|
||||||
SaleDTO s = saleList.get(position);
|
SaleDTO s = saleList.get(position);
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Adapter for displaying services in a list.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -31,6 +38,9 @@ public class ServiceAdapter extends RecyclerView.Adapter<ServiceAdapter.ServiceV
|
|||||||
void onSelectionChanged(int count);
|
void onSelectionChanged(int count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for ServiceAdapter.
|
||||||
|
*/
|
||||||
public ServiceAdapter(List<ServiceDTO> serviceList, OnServiceClickListener clickListener) {
|
public ServiceAdapter(List<ServiceDTO> serviceList, OnServiceClickListener clickListener) {
|
||||||
this.serviceList = serviceList;
|
this.serviceList = serviceList;
|
||||||
this.clickListener = clickListener;
|
this.clickListener = clickListener;
|
||||||
@@ -47,29 +57,40 @@ public class ServiceAdapter extends RecyclerView.Adapter<ServiceAdapter.ServiceV
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of selected keys for bulk deletion.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<String> getSelectedKeys() {
|
public List<String> getSelectedKeys() {
|
||||||
return selectionHelper.getSelectedKeys();
|
return selectionHelper.getSelectedKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all selected items and exits selection mode.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void clearSelection() {
|
public void clearSelection() {
|
||||||
selectionHelper.clearSelection();
|
selectionHelper.clearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ViewHolder class for service items.
|
* ViewHolder for Service items.
|
||||||
*/
|
*/
|
||||||
public static class ServiceViewHolder extends RecyclerView.ViewHolder {
|
public static class ServiceViewHolder extends RecyclerView.ViewHolder {
|
||||||
final ItemServiceBinding binding;
|
final ItemServiceBinding binding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewHolder with view binding.
|
||||||
|
*/
|
||||||
public ServiceViewHolder(@NonNull ItemServiceBinding binding) {
|
public ServiceViewHolder(@NonNull ItemServiceBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new row view
|
/**
|
||||||
|
* Inflates the layout for a Service item.
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public ServiceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public ServiceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
@@ -77,7 +98,9 @@ public class ServiceAdapter extends RecyclerView.Adapter<ServiceAdapter.ServiceV
|
|||||||
return new ServiceViewHolder(binding);
|
return new ServiceViewHolder(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
//populate the row with service data
|
/**
|
||||||
|
* Binds service data to the UI components.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull ServiceViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull ServiceViewHolder holder, int position) {
|
||||||
ServiceDTO service = serviceList.get(position);
|
ServiceDTO service = serviceList.get(position);
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Adapter for showing suppliers in a list.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -26,7 +33,9 @@ public class SupplierAdapter extends RecyclerView.Adapter<SupplierAdapter.Suppli
|
|||||||
void onSelectionChanged(int count);
|
void onSelectionChanged(int count);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Constructor
|
/**
|
||||||
|
* Constructor for SupplierAdapter.
|
||||||
|
*/
|
||||||
public SupplierAdapter(List<SupplierDTO> supplierList, OnSupplierClickListener supplierClickListener) {
|
public SupplierAdapter(List<SupplierDTO> supplierList, OnSupplierClickListener supplierClickListener) {
|
||||||
this.supplierList = supplierList;
|
this.supplierList = supplierList;
|
||||||
this.supplierClickListener = supplierClickListener;
|
this.supplierClickListener = supplierClickListener;
|
||||||
@@ -43,27 +52,40 @@ public class SupplierAdapter extends RecyclerView.Adapter<SupplierAdapter.Suppli
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of selected keys for bulk deletion.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<String> getSelectedKeys() {
|
public List<String> getSelectedKeys() {
|
||||||
return selectionHelper.getSelectedKeys();
|
return selectionHelper.getSelectedKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all selected items and exits selection mode.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void clearSelection() {
|
public void clearSelection() {
|
||||||
selectionHelper.clearSelection();
|
selectionHelper.clearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the controls of each row in recycler view
|
/**
|
||||||
|
* ViewHolder for Supplier items.
|
||||||
|
*/
|
||||||
public static class SupplierViewHolder extends RecyclerView.ViewHolder {
|
public static class SupplierViewHolder extends RecyclerView.ViewHolder {
|
||||||
final ItemSupplierBinding binding;
|
final ItemSupplierBinding binding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewHolder with view binding.
|
||||||
|
*/
|
||||||
public SupplierViewHolder(@NonNull ItemSupplierBinding binding) {
|
public SupplierViewHolder(@NonNull ItemSupplierBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new row view
|
/**
|
||||||
|
* Inflates the layout for a Supplier item.
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public SupplierViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public SupplierViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
@@ -71,7 +93,9 @@ public class SupplierAdapter extends RecyclerView.Adapter<SupplierAdapter.Suppli
|
|||||||
return new SupplierViewHolder(binding);
|
return new SupplierViewHolder(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
//populate the row with supplier data
|
/**
|
||||||
|
* Binds supplier data to the UI components.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull SupplierViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull SupplierViewHolder holder, int position) {
|
||||||
SupplierDTO supplier = supplierList.get(position);
|
SupplierDTO supplier = supplierList.get(position);
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Custom array adapter that displays items with white text.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -12,9 +19,8 @@ import androidx.core.content.ContextCompat;
|
|||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
// A class that overrides the arrayAdapter so the text color changes based on theme
|
||||||
* A class that overrides the arrayAdapter so the text color is white and background is transparent.
|
// Used to make spinners have white text on dark background no matter the theme
|
||||||
*/
|
|
||||||
public class WhiteTextArrayAdapter<T> extends ArrayAdapter<T> {
|
public class WhiteTextArrayAdapter<T> extends ArrayAdapter<T> {
|
||||||
public WhiteTextArrayAdapter(@NonNull Context context, int resource, @NonNull T[] objects) {
|
public WhiteTextArrayAdapter(@NonNull Context context, int resource, @NonNull T[] objects) {
|
||||||
super(context, resource, objects);
|
super(context, resource, objects);
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for activity log endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.ActivityLogDTO;
|
import com.example.petstoremobile.dtos.ActivityLogDTO;
|
||||||
@@ -8,8 +15,10 @@ import retrofit2.Call;
|
|||||||
import retrofit2.http.GET;
|
import retrofit2.http.GET;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
// api calls to get activity logs
|
||||||
public interface ActivityLogApi {
|
public interface ActivityLogApi {
|
||||||
|
|
||||||
|
// Get activity logs with filters
|
||||||
@GET("api/v1/activity-logs")
|
@GET("api/v1/activity-logs")
|
||||||
Call<List<ActivityLogDTO>> getActivityLogs(
|
Call<List<ActivityLogDTO>> getActivityLogs(
|
||||||
@Query("limit") int limit,
|
@Query("limit") int limit,
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for adoption endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.AdoptionDTO;
|
import com.example.petstoremobile.dtos.AdoptionDTO;
|
||||||
@@ -14,8 +21,10 @@ import retrofit2.http.PUT;
|
|||||||
import retrofit2.http.Path;
|
import retrofit2.http.Path;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
// api calls for adoptions
|
||||||
public interface AdoptionApi {
|
public interface AdoptionApi {
|
||||||
|
|
||||||
|
// Get all adoptions with filters
|
||||||
@GET("api/v1/adoptions")
|
@GET("api/v1/adoptions")
|
||||||
Call<PageResponse<AdoptionDTO>> getAllAdoptions(
|
Call<PageResponse<AdoptionDTO>> getAllAdoptions(
|
||||||
@Query("page") int page,
|
@Query("page") int page,
|
||||||
@@ -27,18 +36,23 @@ public interface AdoptionApi {
|
|||||||
@Query("employeeId") Long employeeId,
|
@Query("employeeId") Long employeeId,
|
||||||
@Query("sort") String sort);
|
@Query("sort") String sort);
|
||||||
|
|
||||||
|
// Get adoption by id
|
||||||
@GET("api/v1/adoptions/{id}")
|
@GET("api/v1/adoptions/{id}")
|
||||||
Call<AdoptionDTO> getAdoptionById(@Path("id") Long id);
|
Call<AdoptionDTO> getAdoptionById(@Path("id") Long id);
|
||||||
|
|
||||||
|
// Create adoption
|
||||||
@POST("api/v1/adoptions")
|
@POST("api/v1/adoptions")
|
||||||
Call<AdoptionDTO> createAdoption(@Body AdoptionDTO adoption);
|
Call<AdoptionDTO> createAdoption(@Body AdoptionDTO adoption);
|
||||||
|
|
||||||
|
// Update adoption
|
||||||
@PUT("api/v1/adoptions/{id}")
|
@PUT("api/v1/adoptions/{id}")
|
||||||
Call<AdoptionDTO> updateAdoption(@Path("id") Long id, @Body AdoptionDTO adoption);
|
Call<AdoptionDTO> updateAdoption(@Path("id") Long id, @Body AdoptionDTO adoption);
|
||||||
|
|
||||||
|
// Delete adoption
|
||||||
@DELETE("api/v1/adoptions/{id}")
|
@DELETE("api/v1/adoptions/{id}")
|
||||||
Call<Void> deleteAdoption(@Path("id") Long id);
|
Call<Void> deleteAdoption(@Path("id") Long id);
|
||||||
|
|
||||||
|
// Bulk delete adoptions
|
||||||
@HTTP(method = "DELETE", path = "api/v1/adoptions", hasBody = true)
|
@HTTP(method = "DELETE", path = "api/v1/adoptions", hasBody = true)
|
||||||
Call<Void> bulkDeleteAdoptions(@Body BulkDeleteRequest request);
|
Call<Void> bulkDeleteAdoptions(@Body BulkDeleteRequest request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for appointment endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.AppointmentDTO;
|
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||||
@@ -14,8 +21,10 @@ import retrofit2.http.PUT;
|
|||||||
import retrofit2.http.Path;
|
import retrofit2.http.Path;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
// api calls for appointments
|
||||||
public interface AppointmentApi {
|
public interface AppointmentApi {
|
||||||
|
|
||||||
|
// Get all appointments with filters
|
||||||
@GET("api/v1/appointments")
|
@GET("api/v1/appointments")
|
||||||
Call<PageResponse<AppointmentDTO>> getAllAppointments(
|
Call<PageResponse<AppointmentDTO>> getAllAppointments(
|
||||||
@Query("page") int page,
|
@Query("page") int page,
|
||||||
@@ -27,18 +36,23 @@ public interface AppointmentApi {
|
|||||||
@Query("employeeId") Long employeeId,
|
@Query("employeeId") Long employeeId,
|
||||||
@Query("sort") String sort);
|
@Query("sort") String sort);
|
||||||
|
|
||||||
|
// Get appointment by id
|
||||||
@GET("api/v1/appointments/{id}")
|
@GET("api/v1/appointments/{id}")
|
||||||
Call<AppointmentDTO> getAppointmentById(@Path("id") Long id);
|
Call<AppointmentDTO> getAppointmentById(@Path("id") Long id);
|
||||||
|
|
||||||
|
// Create appointment
|
||||||
@POST("api/v1/appointments")
|
@POST("api/v1/appointments")
|
||||||
Call<AppointmentDTO> createAppointment(@Body AppointmentDTO appointment);
|
Call<AppointmentDTO> createAppointment(@Body AppointmentDTO appointment);
|
||||||
|
|
||||||
|
// Update appointment
|
||||||
@PUT("api/v1/appointments/{id}")
|
@PUT("api/v1/appointments/{id}")
|
||||||
Call<AppointmentDTO> updateAppointment(@Path("id") Long id, @Body AppointmentDTO appointment);
|
Call<AppointmentDTO> updateAppointment(@Path("id") Long id, @Body AppointmentDTO appointment);
|
||||||
|
|
||||||
|
// Delete appointment
|
||||||
@DELETE("api/v1/appointments/{id}")
|
@DELETE("api/v1/appointments/{id}")
|
||||||
Call<Void> deleteAppointment(@Path("id") Long id);
|
Call<Void> deleteAppointment(@Path("id") Long id);
|
||||||
|
|
||||||
|
// Bulk delete appointments
|
||||||
@HTTP(method = "DELETE", path = "api/v1/appointments", hasBody = true)
|
@HTTP(method = "DELETE", path = "api/v1/appointments", hasBody = true)
|
||||||
Call<Void> bulkDeleteAppointments(@Body BulkDeleteRequest request);
|
Call<Void> bulkDeleteAppointments(@Body BulkDeleteRequest request);
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for category endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
import com.example.petstoremobile.dtos.CategoryDTO;
|
||||||
@@ -6,8 +13,10 @@ import retrofit2.Call;
|
|||||||
import retrofit2.http.GET;
|
import retrofit2.http.GET;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
// api calls for categories
|
||||||
public interface CategoryApi {
|
public interface CategoryApi {
|
||||||
|
|
||||||
|
// Get all categories with pagination
|
||||||
@GET("api/v1/categories")
|
@GET("api/v1/categories")
|
||||||
Call<PageResponse<CategoryDTO>> getAllCategories(
|
Call<PageResponse<CategoryDTO>> getAllCategories(
|
||||||
@Query("page") int page,
|
@Query("page") int page,
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for chat and conversation endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.ConversationDTO;
|
import com.example.petstoremobile.dtos.ConversationDTO;
|
||||||
@@ -12,15 +19,18 @@ import retrofit2.http.GET;
|
|||||||
import retrofit2.http.PUT;
|
import retrofit2.http.PUT;
|
||||||
import retrofit2.http.Path;
|
import retrofit2.http.Path;
|
||||||
|
|
||||||
//api calls to get conversations
|
// api calls for chat conversations
|
||||||
public interface ChatApi {
|
public interface ChatApi {
|
||||||
|
|
||||||
|
// Get all conversations
|
||||||
@GET("api/v1/chat/conversations")
|
@GET("api/v1/chat/conversations")
|
||||||
Call<List<ConversationDTO>> getAllConversations();
|
Call<List<ConversationDTO>> getAllConversations();
|
||||||
|
|
||||||
|
// Get conversation by id
|
||||||
@GET("api/v1/chat/conversations/{conversationId}")
|
@GET("api/v1/chat/conversations/{conversationId}")
|
||||||
Call<ConversationDTO> getConversationById(@Path("conversationId") Long conversationId);
|
Call<ConversationDTO> getConversationById(@Path("conversationId") Long conversationId);
|
||||||
|
|
||||||
|
// Update conversation status
|
||||||
@PUT("api/v1/chat/conversations/{conversationId}")
|
@PUT("api/v1/chat/conversations/{conversationId}")
|
||||||
Call<ConversationDTO> updateConversationStatus(@Path("conversationId") Long conversationId, @Body UpdateConversationStatusRequest request);
|
Call<ConversationDTO> updateConversationStatus(@Path("conversationId") Long conversationId, @Body UpdateConversationStatusRequest request);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for coupon endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.CouponDTO;
|
import com.example.petstoremobile.dtos.CouponDTO;
|
||||||
@@ -6,9 +13,12 @@ import com.example.petstoremobile.dtos.PageResponse;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
|
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||||
|
|
||||||
import retrofit2.http.Body;
|
import retrofit2.http.Body;
|
||||||
import retrofit2.http.DELETE;
|
import retrofit2.http.DELETE;
|
||||||
import retrofit2.http.GET;
|
import retrofit2.http.GET;
|
||||||
|
import retrofit2.http.HTTP;
|
||||||
import retrofit2.http.POST;
|
import retrofit2.http.POST;
|
||||||
import retrofit2.http.PUT;
|
import retrofit2.http.PUT;
|
||||||
import retrofit2.http.Path;
|
import retrofit2.http.Path;
|
||||||
@@ -39,6 +49,6 @@ public interface CouponApi {
|
|||||||
@DELETE("api/v1/coupons/{id}")
|
@DELETE("api/v1/coupons/{id}")
|
||||||
Call<Void> deleteCoupon(@Path("id") Long id);
|
Call<Void> deleteCoupon(@Path("id") Long id);
|
||||||
|
|
||||||
@DELETE("api/v1/coupons")
|
@HTTP(method = "DELETE", path = "api/v1/coupons", hasBody = true)
|
||||||
Call<Void> bulkDeleteCoupons(@Query("ids") List<Long> ids);
|
Call<Void> bulkDeleteCoupons(@Body BulkDeleteRequest request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for customer endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.CustomerDTO;
|
import com.example.petstoremobile.dtos.CustomerDTO;
|
||||||
@@ -15,23 +22,30 @@ import retrofit2.http.PUT;
|
|||||||
import retrofit2.http.Path;
|
import retrofit2.http.Path;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
// api calls for customers
|
||||||
public interface CustomerApi {
|
public interface CustomerApi {
|
||||||
|
|
||||||
|
// Get all customers with pagination
|
||||||
@GET("api/v1/customers")
|
@GET("api/v1/customers")
|
||||||
Call<PageResponse<CustomerDTO>> getAllCustomers(@Query("page") int page, @Query("size") int size);
|
Call<PageResponse<CustomerDTO>> getAllCustomers(@Query("page") int page, @Query("size") int size);
|
||||||
|
|
||||||
|
// Get customer by id
|
||||||
@GET("api/v1/customers/{customerId}")
|
@GET("api/v1/customers/{customerId}")
|
||||||
Call<CustomerDTO> getCustomerById(@Path("customerId") Long customerId);
|
Call<CustomerDTO> getCustomerById(@Path("customerId") Long customerId);
|
||||||
|
|
||||||
|
// Update customer
|
||||||
@PUT("api/v1/customers/{customerId}")
|
@PUT("api/v1/customers/{customerId}")
|
||||||
Call<CustomerDTO> updateCustomer(@Path("customerId") Long customerId, @Body CustomerDTO customer);
|
Call<CustomerDTO> updateCustomer(@Path("customerId") Long customerId, @Body CustomerDTO customer);
|
||||||
|
|
||||||
|
// Delete customer
|
||||||
@DELETE("api/v1/customers/{customerId}")
|
@DELETE("api/v1/customers/{customerId}")
|
||||||
Call<Void> deleteCustomer(@Path("customerId") Long customerId);
|
Call<Void> deleteCustomer(@Path("customerId") Long customerId);
|
||||||
|
|
||||||
|
// Register customer
|
||||||
@POST("api/v1/auth/register")
|
@POST("api/v1/auth/register")
|
||||||
Call<CustomerDTO> registerCustomer(@Body CustomerDTO customer);
|
Call<CustomerDTO> registerCustomer(@Body CustomerDTO customer);
|
||||||
|
|
||||||
|
// Get customer dropdowns
|
||||||
@GET("api/v1/dropdowns/customers")
|
@GET("api/v1/dropdowns/customers")
|
||||||
Call<List<DropdownDTO>> getCustomerDropdowns();
|
Call<List<DropdownDTO>> getCustomerDropdowns();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for employee endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.EmployeeDTO;
|
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||||
@@ -5,28 +12,28 @@ import com.example.petstoremobile.dtos.PageResponse;
|
|||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.http.*;
|
import retrofit2.http.*;
|
||||||
|
|
||||||
|
// api calls for employees
|
||||||
public interface EmployeeApi {
|
public interface EmployeeApi {
|
||||||
|
|
||||||
|
// Get all employees with pagination
|
||||||
@GET("api/v1/employees")
|
@GET("api/v1/employees")
|
||||||
Call<PageResponse<EmployeeDTO>> getAllEmployees(
|
Call<PageResponse<EmployeeDTO>> getAllEmployees(
|
||||||
@Query("page") int page,
|
@Query("page") int page,
|
||||||
@Query("size") int size);
|
@Query("size") int size);
|
||||||
|
|
||||||
|
// Get employee by id
|
||||||
@GET("api/v1/employees/{id}")
|
@GET("api/v1/employees/{id}")
|
||||||
Call<EmployeeDTO> getEmployeeById(@Path("id") Long id);
|
Call<EmployeeDTO> getEmployeeById(@Path("id") Long id);
|
||||||
|
|
||||||
|
// Create employee
|
||||||
@POST("api/v1/employees")
|
@POST("api/v1/employees")
|
||||||
Call<EmployeeDTO> createEmployee(@Body EmployeeDTO employee);
|
Call<EmployeeDTO> createEmployee(@Body EmployeeDTO employee);
|
||||||
|
|
||||||
|
// Update employee
|
||||||
@PUT("api/v1/employees/{id}")
|
@PUT("api/v1/employees/{id}")
|
||||||
Call<EmployeeDTO> updateEmployee(@Path("id") Long id, @Body EmployeeDTO employee);
|
Call<EmployeeDTO> updateEmployee(@Path("id") Long id, @Body EmployeeDTO employee);
|
||||||
|
|
||||||
|
// Delete employee
|
||||||
@DELETE("api/v1/employees/{id}")
|
@DELETE("api/v1/employees/{id}")
|
||||||
Call<Void> deleteEmployee(@Path("id") Long id);
|
Call<Void> deleteEmployee(@Path("id") Long id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for inventory endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||||
@@ -14,8 +21,10 @@ import retrofit2.http.PUT;
|
|||||||
import retrofit2.http.Path;
|
import retrofit2.http.Path;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
// api calls for inventory
|
||||||
public interface InventoryApi {
|
public interface InventoryApi {
|
||||||
|
|
||||||
|
// Get all inventory with filters
|
||||||
@GET("api/v1/inventory")
|
@GET("api/v1/inventory")
|
||||||
Call<PageResponse<InventoryDTO>> getAllInventory(
|
Call<PageResponse<InventoryDTO>> getAllInventory(
|
||||||
@Query("page") int page,
|
@Query("page") int page,
|
||||||
@@ -24,23 +33,23 @@ public interface InventoryApi {
|
|||||||
@Query("storeId") Long storeId,
|
@Query("storeId") Long storeId,
|
||||||
@Query("sort") String sort);
|
@Query("sort") String sort);
|
||||||
|
|
||||||
// GET /api/v1/inventory/{id}
|
// Get inventory by id
|
||||||
@GET("api/v1/inventory/{id}")
|
@GET("api/v1/inventory/{id}")
|
||||||
Call<InventoryDTO> getInventoryById(@Path("id") Long id);
|
Call<InventoryDTO> getInventoryById(@Path("id") Long id);
|
||||||
|
|
||||||
// POST /api/v1/inventory
|
// Create inventory
|
||||||
@POST("api/v1/inventory")
|
@POST("api/v1/inventory")
|
||||||
Call<InventoryDTO> createInventory(@Body InventoryDTO request);
|
Call<InventoryDTO> createInventory(@Body InventoryDTO request);
|
||||||
|
|
||||||
// PUT /api/v1/inventory/{id}
|
// Update inventory
|
||||||
@PUT("api/v1/inventory/{id}")
|
@PUT("api/v1/inventory/{id}")
|
||||||
Call<InventoryDTO> updateInventory(@Path("id") Long id, @Body InventoryDTO request);
|
Call<InventoryDTO> updateInventory(@Path("id") Long id, @Body InventoryDTO request);
|
||||||
|
|
||||||
// DELETE /api/v1/inventory/{id}
|
// Delete inventory
|
||||||
@DELETE("api/v1/inventory/{id}")
|
@DELETE("api/v1/inventory/{id}")
|
||||||
Call<Void> deleteInventory(@Path("id") Long id);
|
Call<Void> deleteInventory(@Path("id") Long id);
|
||||||
|
|
||||||
// DELETE /api/v1/inventory (bulk delete)
|
// Bulk delete inventory
|
||||||
@HTTP(method = "DELETE", path = "api/v1/inventory", hasBody = true)
|
@HTTP(method = "DELETE", path = "api/v1/inventory", hasBody = true)
|
||||||
Call<Void> bulkDeleteInventory(@Body BulkDeleteRequest request);
|
Call<Void> bulkDeleteInventory(@Body BulkDeleteRequest request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for message endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.MessageDTO;
|
import com.example.petstoremobile.dtos.MessageDTO;
|
||||||
@@ -15,15 +22,18 @@ import retrofit2.http.Part;
|
|||||||
import retrofit2.http.Path;
|
import retrofit2.http.Path;
|
||||||
import retrofit2.http.Streaming;
|
import retrofit2.http.Streaming;
|
||||||
|
|
||||||
//api calls to get and send messages
|
// api calls for messages
|
||||||
public interface MessageApi {
|
public interface MessageApi {
|
||||||
|
|
||||||
|
// Get messages for a conversation
|
||||||
@GET("api/v1/chat/conversations/{id}/messages")
|
@GET("api/v1/chat/conversations/{id}/messages")
|
||||||
Call<List<MessageDTO>> getMessages(@Path("id") Long conversationId);
|
Call<List<MessageDTO>> getMessages(@Path("id") Long conversationId);
|
||||||
|
|
||||||
|
// Send a message
|
||||||
@POST("api/v1/chat/conversations/{id}/messages")
|
@POST("api/v1/chat/conversations/{id}/messages")
|
||||||
Call<MessageDTO> sendMessage(@Path("id") Long conversationId, @Body SendMessageRequest request);
|
Call<MessageDTO> sendMessage(@Path("id") Long conversationId, @Body SendMessageRequest request);
|
||||||
|
|
||||||
|
// Send a message with attachment
|
||||||
@Multipart
|
@Multipart
|
||||||
@POST("api/v1/chat/conversations/{id}/attachments")
|
@POST("api/v1/chat/conversations/{id}/attachments")
|
||||||
Call<MessageDTO> sendMessageWithAttachment(
|
Call<MessageDTO> sendMessageWithAttachment(
|
||||||
@@ -32,6 +42,7 @@ public interface MessageApi {
|
|||||||
@Part MultipartBody.Part file
|
@Part MultipartBody.Part file
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Download attachment
|
||||||
@GET("api/v1/chat/messages/{id}/attachment")
|
@GET("api/v1/chat/messages/{id}/attachment")
|
||||||
@Streaming
|
@Streaming
|
||||||
Call<ResponseBody> downloadAttachment(@Path("id") Long messageId);
|
Call<ResponseBody> downloadAttachment(@Path("id") Long messageId);
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for pet endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||||
@@ -20,7 +27,7 @@ import retrofit2.http.Part;
|
|||||||
import retrofit2.http.Path;
|
import retrofit2.http.Path;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
//api calls to CRUD pets
|
// api calls to CRUD pets
|
||||||
public interface PetApi {
|
public interface PetApi {
|
||||||
// endpoint for downloading the pet's image file
|
// endpoint for downloading the pet's image file
|
||||||
String PET_IMAGE_PATH = "api/v1/pets/%d/image";
|
String PET_IMAGE_PATH = "api/v1/pets/%d/image";
|
||||||
@@ -38,18 +45,23 @@ public interface PetApi {
|
|||||||
@Query("sort") String sort
|
@Query("sort") String sort
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Get pets by customer id
|
||||||
@GET("api/v1/dropdowns/customers/{customerId}/pets")
|
@GET("api/v1/dropdowns/customers/{customerId}/pets")
|
||||||
Call<List<DropdownDTO>> getCustomerPets(@Path("customerId") Long customerId);
|
Call<List<DropdownDTO>> getCustomerPets(@Path("customerId") Long customerId);
|
||||||
|
|
||||||
|
// Get adoption pets
|
||||||
@GET("api/v1/dropdowns/adoption-pets")
|
@GET("api/v1/dropdowns/adoption-pets")
|
||||||
Call<List<DropdownDTO>> getAdoptionPets(@Query("storeId") Long storeId);
|
Call<List<DropdownDTO>> getAdoptionPets(@Query("storeId") Long storeId);
|
||||||
|
|
||||||
|
// Get pet dropdowns
|
||||||
@GET("api/v1/dropdowns/pets")
|
@GET("api/v1/dropdowns/pets")
|
||||||
Call<List<DropdownDTO>> getPetDropdowns();
|
Call<List<DropdownDTO>> getPetDropdowns();
|
||||||
|
|
||||||
|
// Get pet species dropdowns
|
||||||
@GET("api/v1/dropdowns/pet-species")
|
@GET("api/v1/dropdowns/pet-species")
|
||||||
Call<List<DropdownDTO>> getPetSpeciesDropdowns();
|
Call<List<DropdownDTO>> getPetSpeciesDropdowns();
|
||||||
|
|
||||||
|
// Get pet breeds dropdowns
|
||||||
@GET("api/v1/dropdowns/pet-breeds")
|
@GET("api/v1/dropdowns/pet-breeds")
|
||||||
Call<List<DropdownDTO>> getPetBreedsDropdowns(@Query("species") String species);
|
Call<List<DropdownDTO>> getPetBreedsDropdowns(@Query("species") String species);
|
||||||
|
|
||||||
@@ -81,5 +93,4 @@ public interface PetApi {
|
|||||||
// Delete pet image
|
// Delete pet image
|
||||||
@DELETE("api/v1/pets/{id}/image")
|
@DELETE("api/v1/pets/{id}/image")
|
||||||
Call<Void> deletePetImage(@Path("id") Long id);
|
Call<Void> deletePetImage(@Path("id") Long id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for product endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
@@ -9,9 +16,12 @@ import retrofit2.http.*;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
// api calls for products
|
||||||
public interface ProductApi {
|
public interface ProductApi {
|
||||||
|
// endpoint for downloading the product's image file
|
||||||
String PRODUCT_IMAGE_PATH = "api/v1/products/%d/image";
|
String PRODUCT_IMAGE_PATH = "api/v1/products/%d/image";
|
||||||
|
|
||||||
|
// Get all products with filters
|
||||||
@GET("api/v1/products")
|
@GET("api/v1/products")
|
||||||
Call<PageResponse<ProductDTO>> getAllProducts(
|
Call<PageResponse<ProductDTO>> getAllProducts(
|
||||||
@Query("q") String query,
|
@Query("q") String query,
|
||||||
@@ -20,28 +30,36 @@ public interface ProductApi {
|
|||||||
@Query("size") int size,
|
@Query("size") int size,
|
||||||
@Query("sort") String sort);
|
@Query("sort") String sort);
|
||||||
|
|
||||||
|
// Get product by id
|
||||||
@GET("api/v1/products/{id}")
|
@GET("api/v1/products/{id}")
|
||||||
Call<ProductDTO> getProductById(@Path("id") Long id);
|
Call<ProductDTO> getProductById(@Path("id") Long id);
|
||||||
|
|
||||||
|
// Create product
|
||||||
@POST("api/v1/products")
|
@POST("api/v1/products")
|
||||||
Call<ProductDTO> createProduct(@Body ProductDTO product);
|
Call<ProductDTO> createProduct(@Body ProductDTO product);
|
||||||
|
|
||||||
|
// Update product
|
||||||
@PUT("api/v1/products/{id}")
|
@PUT("api/v1/products/{id}")
|
||||||
Call<ProductDTO> updateProduct(@Path("id") Long id, @Body ProductDTO product);
|
Call<ProductDTO> updateProduct(@Path("id") Long id, @Body ProductDTO product);
|
||||||
|
|
||||||
|
// Delete product
|
||||||
@DELETE("api/v1/products/{id}")
|
@DELETE("api/v1/products/{id}")
|
||||||
Call<Void> deleteProduct(@Path("id") Long id);
|
Call<Void> deleteProduct(@Path("id") Long id);
|
||||||
|
|
||||||
|
// Upload product image
|
||||||
@Multipart
|
@Multipart
|
||||||
@POST("api/v1/products/{id}/image")
|
@POST("api/v1/products/{id}/image")
|
||||||
Call<Void> uploadProductImage(@Path("id") Long id, @Part MultipartBody.Part image);
|
Call<Void> uploadProductImage(@Path("id") Long id, @Part MultipartBody.Part image);
|
||||||
|
|
||||||
|
// Delete product image
|
||||||
@DELETE("api/v1/products/{id}/image")
|
@DELETE("api/v1/products/{id}/image")
|
||||||
Call<Void> deleteProductImage(@Path("id") Long id);
|
Call<Void> deleteProductImage(@Path("id") Long id);
|
||||||
|
|
||||||
|
// Get product dropdowns
|
||||||
@GET("api/v1/dropdowns/products")
|
@GET("api/v1/dropdowns/products")
|
||||||
Call<List<DropdownDTO>> getProductDropdowns();
|
Call<List<DropdownDTO>> getProductDropdowns();
|
||||||
|
|
||||||
|
// Get category dropdowns
|
||||||
@GET("api/v1/dropdowns/categories")
|
@GET("api/v1/dropdowns/categories")
|
||||||
Call<List<DropdownDTO>> getCategoryDropdowns();
|
Call<List<DropdownDTO>> getCategoryDropdowns();
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for product-supplier endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||||
@@ -6,8 +13,10 @@ import com.example.petstoremobile.dtos.ProductSupplierDTO;
|
|||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.http.*;
|
import retrofit2.http.*;
|
||||||
|
|
||||||
|
// api calls for product-supplier relationships
|
||||||
public interface ProductSupplierApi {
|
public interface ProductSupplierApi {
|
||||||
|
|
||||||
|
// Get all product-suppliers with filters
|
||||||
@GET("api/v1/product-suppliers")
|
@GET("api/v1/product-suppliers")
|
||||||
Call<PageResponse<ProductSupplierDTO>> getAllProductSuppliers(
|
Call<PageResponse<ProductSupplierDTO>> getAllProductSuppliers(
|
||||||
@Query("page") int page,
|
@Query("page") int page,
|
||||||
@@ -17,24 +26,30 @@ public interface ProductSupplierApi {
|
|||||||
@Query("supplierId") Long supplierId,
|
@Query("supplierId") Long supplierId,
|
||||||
@Query("sort") String sort);
|
@Query("sort") String sort);
|
||||||
|
|
||||||
|
// Get product-supplier by composite id
|
||||||
@GET("api/v1/product-suppliers/{productId}/{supplierId}")
|
@GET("api/v1/product-suppliers/{productId}/{supplierId}")
|
||||||
Call<ProductSupplierDTO> getProductSupplierById(
|
Call<ProductSupplierDTO> getProductSupplierById(
|
||||||
@Path("productId") Long productId,
|
@Path("productId") Long productId,
|
||||||
@Path("supplierId") Long supplierId);
|
@Path("supplierId") Long supplierId);
|
||||||
|
|
||||||
|
// Create product-supplier
|
||||||
@POST("api/v1/product-suppliers")
|
@POST("api/v1/product-suppliers")
|
||||||
Call<ProductSupplierDTO> createProductSupplier(@Body ProductSupplierDTO dto);
|
Call<ProductSupplierDTO> createProductSupplier(@Body ProductSupplierDTO dto);
|
||||||
|
|
||||||
|
// Update product-supplier
|
||||||
@PUT("api/v1/product-suppliers/{productId}/{supplierId}")
|
@PUT("api/v1/product-suppliers/{productId}/{supplierId}")
|
||||||
Call<ProductSupplierDTO> updateProductSupplier(
|
Call<ProductSupplierDTO> updateProductSupplier(
|
||||||
@Path("productId") Long productId,
|
@Path("productId") Long productId,
|
||||||
@Path("supplierId") Long supplierId,
|
@Path("supplierId") Long supplierId,
|
||||||
@Body ProductSupplierDTO dto);
|
@Body ProductSupplierDTO dto);
|
||||||
|
|
||||||
|
// Delete product-supplier
|
||||||
@DELETE("api/v1/product-suppliers/{productId}/{supplierId}")
|
@DELETE("api/v1/product-suppliers/{productId}/{supplierId}")
|
||||||
Call<Void> deleteProductSupplier(
|
Call<Void> deleteProductSupplier(
|
||||||
@Path("productId") Long productId,
|
@Path("productId") Long productId,
|
||||||
@Path("supplierId") Long supplierId);
|
@Path("supplierId") Long supplierId);
|
||||||
|
|
||||||
|
// Bulk delete product-suppliers
|
||||||
@HTTP(method = "DELETE", path = "api/v1/product-suppliers", hasBody = true)
|
@HTTP(method = "DELETE", path = "api/v1/product-suppliers", hasBody = true)
|
||||||
Call<Void> bulkDeleteProductSuppliers(@Body BulkDeleteRequest request);
|
Call<Void> bulkDeleteProductSuppliers(@Body BulkDeleteRequest request);
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for purchase order endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
@@ -7,8 +14,10 @@ import retrofit2.http.GET;
|
|||||||
import retrofit2.http.Path;
|
import retrofit2.http.Path;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
// api calls for purchase orders
|
||||||
public interface PurchaseOrderApi {
|
public interface PurchaseOrderApi {
|
||||||
|
|
||||||
|
// Get all purchase orders with filters
|
||||||
@GET("api/v1/purchase-orders")
|
@GET("api/v1/purchase-orders")
|
||||||
Call<PageResponse<PurchaseOrderDTO>> getAllPurchaseOrders(
|
Call<PageResponse<PurchaseOrderDTO>> getAllPurchaseOrders(
|
||||||
@Query("page") int page,
|
@Query("page") int page,
|
||||||
@@ -17,6 +26,7 @@ public interface PurchaseOrderApi {
|
|||||||
@Query("storeId") Long storeId,
|
@Query("storeId") Long storeId,
|
||||||
@Query("sort") String sort);
|
@Query("sort") String sort);
|
||||||
|
|
||||||
|
// Get purchase order by id
|
||||||
@GET("api/v1/purchase-orders/{id}")
|
@GET("api/v1/purchase-orders/{id}")
|
||||||
Call<PurchaseOrderDTO> getPurchaseOrderById(@Path("id") Long id);
|
Call<PurchaseOrderDTO> getPurchaseOrderById(@Path("id") Long id);
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for refund endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.RefundDTO;
|
import com.example.petstoremobile.dtos.RefundDTO;
|
||||||
@@ -6,20 +13,26 @@ import retrofit2.http.*;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
// api calls for refunds
|
||||||
public interface RefundApi {
|
public interface RefundApi {
|
||||||
|
|
||||||
|
// Get all refunds
|
||||||
@GET("api/v1/refunds")
|
@GET("api/v1/refunds")
|
||||||
Call<List<RefundDTO>> getAllRefunds();
|
Call<List<RefundDTO>> getAllRefunds();
|
||||||
|
|
||||||
|
// Get refund by id
|
||||||
@GET("api/v1/refunds/{id}")
|
@GET("api/v1/refunds/{id}")
|
||||||
Call<RefundDTO> getRefundById(@Path("id") Long id);
|
Call<RefundDTO> getRefundById(@Path("id") Long id);
|
||||||
|
|
||||||
|
// Create refund
|
||||||
@POST("api/v1/refunds")
|
@POST("api/v1/refunds")
|
||||||
Call<RefundDTO> createRefund(@Body RefundDTO refund);
|
Call<RefundDTO> createRefund(@Body RefundDTO refund);
|
||||||
|
|
||||||
|
// Update refund
|
||||||
@PUT("api/v1/refunds/{id}")
|
@PUT("api/v1/refunds/{id}")
|
||||||
Call<RefundDTO> updateRefund(@Path("id") Long id, @Body RefundDTO refund);
|
Call<RefundDTO> updateRefund(@Path("id") Long id, @Body RefundDTO refund);
|
||||||
|
|
||||||
|
// Delete refund
|
||||||
@DELETE("api/v1/refunds/{id}")
|
@DELETE("api/v1/refunds/{id}")
|
||||||
Call<Void> deleteRefund(@Path("id") Long id);
|
Call<Void> deleteRefund(@Path("id") Long id);
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for sale endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
@@ -10,8 +17,10 @@ import retrofit2.http.POST;
|
|||||||
import retrofit2.http.Path;
|
import retrofit2.http.Path;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
// api calls for sales
|
||||||
public interface SaleApi {
|
public interface SaleApi {
|
||||||
|
|
||||||
|
// Get all sales with filters
|
||||||
@GET("api/v1/sales")
|
@GET("api/v1/sales")
|
||||||
Call<PageResponse<SaleDTO>> getAllSales(
|
Call<PageResponse<SaleDTO>> getAllSales(
|
||||||
@Query("page") int page,
|
@Query("page") int page,
|
||||||
@@ -23,9 +32,11 @@ public interface SaleApi {
|
|||||||
@Query("customerId") Long customerId,
|
@Query("customerId") Long customerId,
|
||||||
@Query("sort") String sort);
|
@Query("sort") String sort);
|
||||||
|
|
||||||
|
// Get sale by id
|
||||||
@GET("api/v1/sales/{id}")
|
@GET("api/v1/sales/{id}")
|
||||||
Call<SaleDTO> getSaleById(@Path("id") Long id);
|
Call<SaleDTO> getSaleById(@Path("id") Long id);
|
||||||
|
|
||||||
|
// Create sale
|
||||||
@POST("api/v1/sales")
|
@POST("api/v1/sales")
|
||||||
Call<SaleDTO> createSale(@Body SaleDTO sale);
|
Call<SaleDTO> createSale(@Body SaleDTO sale);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for service endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||||
@@ -14,7 +21,7 @@ import retrofit2.http.PUT;
|
|||||||
import retrofit2.http.Path;
|
import retrofit2.http.Path;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
//api calls to CRUD services
|
// api calls to CRUD services
|
||||||
public interface ServiceApi {
|
public interface ServiceApi {
|
||||||
// Get all services
|
// Get all services
|
||||||
@GET("api/v1/services")
|
@GET("api/v1/services")
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for store endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
@@ -11,16 +18,20 @@ import retrofit2.http.GET;
|
|||||||
import retrofit2.http.Path;
|
import retrofit2.http.Path;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
// api calls for stores
|
||||||
public interface StoreApi {
|
public interface StoreApi {
|
||||||
|
|
||||||
|
// Get all stores with pagination
|
||||||
@GET("api/v1/stores")
|
@GET("api/v1/stores")
|
||||||
Call<PageResponse<StoreDTO>> getAllStores(
|
Call<PageResponse<StoreDTO>> getAllStores(
|
||||||
@Query("page") int page,
|
@Query("page") int page,
|
||||||
@Query("size") int size);
|
@Query("size") int size);
|
||||||
|
|
||||||
|
// Get store dropdowns
|
||||||
@GET("api/v1/dropdowns/stores")
|
@GET("api/v1/dropdowns/stores")
|
||||||
Call<List<DropdownDTO>> getStoreDropdowns();
|
Call<List<DropdownDTO>> getStoreDropdowns();
|
||||||
|
|
||||||
|
// Get employees of a specific store
|
||||||
@GET("api/v1/dropdowns/stores/{storeId}/employees")
|
@GET("api/v1/dropdowns/stores/{storeId}/employees")
|
||||||
Call<List<DropdownDTO>> getStoreEmployees(@Path("storeId") Long storeId);
|
Call<List<DropdownDTO>> getStoreEmployees(@Path("storeId") Long storeId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for supplier endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for user endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
@@ -7,9 +14,12 @@ import retrofit2.Call;
|
|||||||
import retrofit2.http.GET;
|
import retrofit2.http.GET;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
// api calls for users
|
||||||
public interface UserApi {
|
public interface UserApi {
|
||||||
|
// endpoint for downloading the user's avatar file
|
||||||
String AVATAR_PATH = "api/v1/users/%d/avatar/file";
|
String AVATAR_PATH = "api/v1/users/%d/avatar/file";
|
||||||
|
|
||||||
|
// Get all users with filters
|
||||||
@GET("api/v1/users")
|
@GET("api/v1/users")
|
||||||
Call<PageResponse<UserDTO>> getUsers(@Query("role") String role, @Query("page") int page, @Query("size") int size);
|
Call<PageResponse<UserDTO>> getUsers(@Query("role") String role, @Query("page") int page, @Query("size") int size);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Retrofit interface for login and registration endpoints.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api.auth;
|
package com.example.petstoremobile.api.auth;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.AuthDTO;
|
import com.example.petstoremobile.dtos.AuthDTO;
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Interceptor that attaches the auth token to outgoing requests.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api.auth;
|
package com.example.petstoremobile.api.auth;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -23,19 +30,26 @@ public class AuthInterceptor implements Interceptor {
|
|||||||
String token = tokenManager.getToken();
|
String token = tokenManager.getToken();
|
||||||
String url = chain.request().url().toString();
|
String url = chain.request().url().toString();
|
||||||
|
|
||||||
if (url.contains("auth/login") || url.contains("auth/register")) {
|
boolean isAuthEndpoint = url.contains("auth/login") || url.contains("auth/register");
|
||||||
|
|
||||||
|
if (isAuthEndpoint) {
|
||||||
return chain.proceed(chain.request());
|
return chain.proceed(chain.request());
|
||||||
}
|
}
|
||||||
|
|
||||||
//If we have a token then add it to the request
|
Response response;
|
||||||
if (token != null) {
|
if (token != null) {
|
||||||
Request request = chain.request().newBuilder()
|
Request request = chain.request().newBuilder()
|
||||||
.addHeader("Authorization", "Bearer " + token)
|
.addHeader("Authorization", "Bearer " + token)
|
||||||
.build();
|
.build();
|
||||||
return chain.proceed(request);
|
response = chain.proceed(request);
|
||||||
|
} else {
|
||||||
|
response = chain.proceed(chain.request());
|
||||||
}
|
}
|
||||||
|
|
||||||
//If no token then just pass the request
|
if (response.code() == 401) {
|
||||||
return chain.proceed(chain.request());
|
tokenManager.forceLogout();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* Handles saving and retrieving the authentication token.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.api.auth;
|
package com.example.petstoremobile.api.auth;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -8,8 +16,11 @@ import javax.inject.Singleton;
|
|||||||
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext;
|
import dagger.hilt.android.qualifiers.ApplicationContext;
|
||||||
|
|
||||||
|
//Used to save and retrieve login data
|
||||||
@Singleton
|
@Singleton
|
||||||
public class TokenManager {
|
public class TokenManager {
|
||||||
|
public static final String ACTION_FORCE_LOGOUT = "com.example.petstoremobile.ACTION_FORCE_LOGOUT";
|
||||||
|
|
||||||
private static final String TOKEN_KEY = "token";
|
private static final String TOKEN_KEY = "token";
|
||||||
private static final String USERNAME_KEY = "username";
|
private static final String USERNAME_KEY = "username";
|
||||||
private static final String ROLE_KEY = "role";
|
private static final String ROLE_KEY = "role";
|
||||||
@@ -17,13 +28,22 @@ public class TokenManager {
|
|||||||
private static final String USER_ID_KEY = "user_id";
|
private static final String USER_ID_KEY = "user_id";
|
||||||
private static final String PRIMARY_STORE_ID_KEY = "primary_store_id";
|
private static final String PRIMARY_STORE_ID_KEY = "primary_store_id";
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
private SharedPreferences prefs;
|
private SharedPreferences prefs;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public TokenManager(@ApplicationContext Context context) {
|
public TokenManager(@ApplicationContext Context context) {
|
||||||
|
this.context = context;
|
||||||
prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void forceLogout() {
|
||||||
|
clearLoginData();
|
||||||
|
Intent intent = new Intent(ACTION_FORCE_LOGOUT);
|
||||||
|
intent.setPackage(context.getPackageName());
|
||||||
|
context.sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
|
||||||
//save login data after login
|
//save login data after login
|
||||||
public void saveLoginData(String token, String username, String role) {
|
public void saveLoginData(String token, String username, String role) {
|
||||||
prefs.edit()
|
prefs.edit()
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Sets up the Retrofit client and dependency injection for network calls.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.di;
|
package com.example.petstoremobile.di;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for activity log entries.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for activity logs.
|
||||||
|
*/
|
||||||
public class ActivityLogDTO {
|
public class ActivityLogDTO {
|
||||||
private Long logId;
|
private Long logId;
|
||||||
private String activity;
|
private String activity;
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for adoption records.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for pet adoptions.
|
||||||
|
*/
|
||||||
public class AdoptionDTO {
|
public class AdoptionDTO {
|
||||||
|
|
||||||
private Long adoptionId;
|
private Long adoptionId;
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for appointments.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for appointments.
|
||||||
|
*/
|
||||||
public class AppointmentDTO {
|
public class AppointmentDTO {
|
||||||
|
|
||||||
private Long appointmentId;
|
private Long appointmentId;
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for authentication requests and responses.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
//Used to send login data to the backend
|
/**
|
||||||
|
* Data Transfer Object for authentication credentials.
|
||||||
|
*/
|
||||||
public class AuthDTO {
|
public class AuthDTO {
|
||||||
public static class LoginRequest {
|
public static class LoginRequest {
|
||||||
private String username;
|
private String username;
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Response object returned after uploading an avatar image.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response containing the URL of a newly uploaded avatar.
|
||||||
|
*/
|
||||||
public class AvatarUploadResponse {
|
public class AvatarUploadResponse {
|
||||||
private String avatarUrl;
|
private String avatarUrl;
|
||||||
private String message;
|
private String message;
|
||||||
|
|||||||
@@ -1,22 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Request object for deleting multiple items at once.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request body for deleting multiple records at once.
|
||||||
|
*/
|
||||||
public class BulkDeleteRequest {
|
public class BulkDeleteRequest {
|
||||||
private List<String> ids;
|
private List<Long> ids;
|
||||||
|
|
||||||
public BulkDeleteRequest() {
|
public BulkDeleteRequest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public BulkDeleteRequest(List<String> ids) {
|
public BulkDeleteRequest(List<Long> ids) {
|
||||||
this.ids = ids;
|
this.ids = ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getIds() {
|
public List<Long> getIds() {
|
||||||
return ids;
|
return ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIds(List<String> ids) {
|
public void setIds(List<Long> ids) {
|
||||||
this.ids = ids;
|
this.ids = ids;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for categories.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for product categories.
|
||||||
|
*/
|
||||||
public class CategoryDTO {
|
public class CategoryDTO {
|
||||||
private Long categoryId;
|
private Long categoryId;
|
||||||
private String categoryName;
|
private String categoryName;
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for chat conversations.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for chat conversations.
|
||||||
|
*/
|
||||||
public class ConversationDTO {
|
public class ConversationDTO {
|
||||||
private Long id;
|
private Long id;
|
||||||
private Long customerId;
|
private Long customerId;
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for coupons.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for coupons.
|
||||||
|
*/
|
||||||
public class CouponDTO {
|
public class CouponDTO {
|
||||||
private Long couponId;
|
private Long couponId;
|
||||||
private String couponCode;
|
private String couponCode;
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for customers.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for customers.
|
||||||
|
*/
|
||||||
public class CustomerDTO {
|
public class CustomerDTO {
|
||||||
@SerializedName("id")
|
@SerializedName("id")
|
||||||
private Long customerId;
|
private Long customerId;
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object used to populate dropdown menus.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for simple dropdown selection lists.
|
||||||
|
*/
|
||||||
public class DropdownDTO {
|
public class DropdownDTO {
|
||||||
private Long id;
|
private Long id;
|
||||||
private String label;
|
private String label;
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for employees.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for employees.
|
||||||
|
*/
|
||||||
public class EmployeeDTO {
|
public class EmployeeDTO {
|
||||||
|
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Represents an error response from the server.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
//Used to get messages of any errors from the backend
|
/**
|
||||||
|
* Used to get messages of any errors from the backend.
|
||||||
|
*/
|
||||||
public class ErrorResponse {
|
public class ErrorResponse {
|
||||||
private String message;
|
private String message;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for inventory items.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for inventory stock.
|
||||||
|
*/
|
||||||
public class InventoryDTO {
|
public class InventoryDTO {
|
||||||
// Response fields (from backend InventoryResponse)
|
// Response fields (from backend InventoryResponse)
|
||||||
private Long inventoryId;
|
private Long inventoryId;
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for chat messages.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for chat messages.
|
||||||
|
*/
|
||||||
public class MessageDTO {
|
public class MessageDTO {
|
||||||
|
|
||||||
@SerializedName("id")
|
@SerializedName("id")
|
||||||
|
|||||||
@@ -1,8 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Wrapper for paginated responses from the server.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
//Used to get data from the API
|
/**
|
||||||
|
* Generic response wrapper for paginated API results.
|
||||||
|
*/
|
||||||
public class PageResponse<T> {
|
public class PageResponse<T> {
|
||||||
private List<T> content;
|
private List<T> content;
|
||||||
private int totalPages;
|
private int totalPages;
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for pets.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object representing a pet.
|
||||||
|
*/
|
||||||
public class PetDTO {
|
public class PetDTO {
|
||||||
private Long petId;
|
private Long petId;
|
||||||
private String petName;
|
private String petName;
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for products.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for products.
|
||||||
|
*/
|
||||||
public class ProductDTO {
|
public class ProductDTO {
|
||||||
private Long prodId;
|
private Long prodId;
|
||||||
private String prodName;
|
private String prodName;
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for product-supplier relationships.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for mapping products to suppliers.
|
||||||
|
*/
|
||||||
public class ProductSupplierDTO {
|
public class ProductSupplierDTO {
|
||||||
private Long productId;
|
private Long productId;
|
||||||
private String productName;
|
private String productName;
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for purchase orders.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for purchase orders.
|
||||||
|
*/
|
||||||
public class PurchaseOrderDTO {
|
public class PurchaseOrderDTO {
|
||||||
private Long purchaseOrderId;
|
private Long purchaseOrderId;
|
||||||
private Long supId;
|
private Long supId;
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for refunds.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for refund processing.
|
||||||
|
*/
|
||||||
public class RefundDTO {
|
public class RefundDTO {
|
||||||
// Response fields
|
// Response fields
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for sales.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for sales transactions.
|
||||||
|
*/
|
||||||
public class SaleDTO {
|
public class SaleDTO {
|
||||||
// Response fields
|
// Response fields
|
||||||
private Long saleId;
|
private Long saleId;
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Request object for sending a new chat message.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request body for sending a new chat message.
|
||||||
|
*/
|
||||||
public class SendMessageRequest {
|
public class SendMessageRequest {
|
||||||
|
|
||||||
private String content;
|
private String content;
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for services.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for services.
|
||||||
|
*/
|
||||||
public class ServiceDTO {
|
public class ServiceDTO {
|
||||||
private Long serviceId;
|
private Long serviceId;
|
||||||
private String serviceName;
|
private String serviceName;
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for store information.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for store information.
|
||||||
|
*/
|
||||||
public class StoreDTO {
|
public class StoreDTO {
|
||||||
private Long storeId;
|
private Long storeId;
|
||||||
private String storeName;
|
private String storeName;
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for suppliers.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for suppliers.
|
||||||
|
*/
|
||||||
public class SupplierDTO {
|
public class SupplierDTO {
|
||||||
private Long supId;
|
private Long supId;
|
||||||
private String supCompany;
|
private String supCompany;
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Request object for changing a conversation's status.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request body for updating chat conversation status.
|
||||||
|
*/
|
||||||
public class UpdateConversationStatusRequest {
|
public class UpdateConversationStatusRequest {
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Data transfer object for user account information.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.dtos;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Object for user account details.
|
||||||
|
*/
|
||||||
public class UserDTO {
|
public class UserDTO {
|
||||||
private Long id;
|
private Long id;
|
||||||
private String username;
|
private String username;
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Fragment for the real-time chat screen.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments;
|
package com.example.petstoremobile.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@@ -65,6 +72,9 @@ import okhttp3.MultipartBody;
|
|||||||
import okhttp3.RequestBody;
|
import okhttp3.RequestBody;
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for handling customer support chat.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickListener, StompChatManager.MessageListener,
|
public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickListener, StompChatManager.MessageListener,
|
||||||
StompChatManager.ConversationListener, StompChatManager.ConnectionListener {
|
StompChatManager.ConversationListener, StompChatManager.ConnectionListener {
|
||||||
@@ -90,6 +100,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
private StompChatManager stompChatManager;
|
private StompChatManager stompChatManager;
|
||||||
private ActivityResultLauncher<Intent> attachmentLauncher;
|
private ActivityResultLauncher<Intent> attachmentLauncher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the view model and attachment launcher.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -105,6 +118,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout and sets up UI event listeners.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
ViewGroup container, Bundle savedInstanceState) {
|
ViewGroup container, Bundle savedInstanceState) {
|
||||||
@@ -137,6 +153,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the logic to open and close the chat drawer.
|
||||||
|
*/
|
||||||
private void setupDrawerToggles() {
|
private void setupDrawerToggles() {
|
||||||
binding.headerActiveChats.setOnClickListener(v -> {
|
binding.headerActiveChats.setOnClickListener(v -> {
|
||||||
if (binding.rvActiveChats.getVisibility() == View.VISIBLE) {
|
if (binding.rvActiveChats.getVisibility() == View.VISIBLE) {
|
||||||
@@ -159,6 +178,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the adapters and layout managers for chat lists and message history.
|
||||||
|
*/
|
||||||
private void setupRecyclerViews() {
|
private void setupRecyclerViews() {
|
||||||
activeChatAdapter = new ChatAdapter(activeChatList, this);
|
activeChatAdapter = new ChatAdapter(activeChatList, this);
|
||||||
binding.rvActiveChats.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.rvActiveChats.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
@@ -187,6 +209,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
setConversationActive(false, null);
|
setConversationActive(false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a full-screen image preview for message attachments.
|
||||||
|
*/
|
||||||
private void showFullScreenImage(Message message) {
|
private void showFullScreenImage(Message message) {
|
||||||
if (baseUrl == null || message.getId() == null) return;
|
if (baseUrl == null || message.getId() == null) return;
|
||||||
|
|
||||||
@@ -208,6 +233,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiates the download process for a message attachment.
|
||||||
|
*/
|
||||||
private void downloadFile(Message message) {
|
private void downloadFile(Message message) {
|
||||||
if (message.getId() == null) return;
|
if (message.getId() == null) return;
|
||||||
|
|
||||||
@@ -227,6 +255,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the downloaded file body to the device's downloads folder.
|
||||||
|
*/
|
||||||
private void saveFileToDownloads(ResponseBody body, String fileName, String mimeType) {
|
private void saveFileToDownloads(ResponseBody body, String fileName, String mimeType) {
|
||||||
android.os.Handler mainHandler = new android.os.Handler(android.os.Looper.getMainLooper());
|
android.os.Handler mainHandler = new android.os.Handler(android.os.Looper.getMainLooper());
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
@@ -270,6 +301,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes LiveData from the ViewModel to update chat lists and messages.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getActiveChats().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getActiveChats().observe(getViewLifecycleOwner(), list -> {
|
||||||
activeChatList.clear();
|
activeChatList.clear();
|
||||||
@@ -300,12 +334,18 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), this::setLoading);
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), this::setLoading);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the visibility of the progress bar.
|
||||||
|
*/
|
||||||
private void setLoading(boolean loading) {
|
private void setLoading(boolean loading) {
|
||||||
if (binding != null && binding.progressBar != null) {
|
if (binding != null && binding.progressBar != null) {
|
||||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the chat header and input state if the active conversation changes.
|
||||||
|
*/
|
||||||
private void updateTitleAndStateIfActive(List<Chat> list) {
|
private void updateTitleAndStateIfActive(List<Chat> list) {
|
||||||
if (activeConversationId != null) {
|
if (activeConversationId != null) {
|
||||||
for (Chat chat : list) {
|
for (Chat chat : list) {
|
||||||
@@ -318,6 +358,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads initial chat data and establishes WebSocket connection.
|
||||||
|
*/
|
||||||
private void loadInitialData() {
|
private void loadInitialData() {
|
||||||
String token = tokenManager.getToken();
|
String token = tokenManager.getToken();
|
||||||
Long currentUserId = tokenManager.getUserId();
|
Long currentUserId = tokenManager.getUserId();
|
||||||
@@ -353,6 +396,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles clicks on a chat from the drawer to switch the active conversation.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onChatClick(Chat chat) {
|
public void onChatClick(Chat chat) {
|
||||||
activeConversationId = Long.parseLong(chat.getChatId());
|
activeConversationId = Long.parseLong(chat.getChatId());
|
||||||
@@ -368,6 +414,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
viewModel.loadMessageHistory(activeConversationId);
|
viewModel.loadMessageHistory(activeConversationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the active chat conversation.
|
||||||
|
*/
|
||||||
private void closeChat() {
|
private void closeChat() {
|
||||||
if (activeConversationId == null) return;
|
if (activeConversationId == null) return;
|
||||||
|
|
||||||
@@ -392,6 +441,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a text message to the active conversation.
|
||||||
|
*/
|
||||||
private void sendMessage() {
|
private void sendMessage() {
|
||||||
if (activeConversationId == null) return;
|
if (activeConversationId == null) return;
|
||||||
String text = binding.etMessage.getText().toString().trim();
|
String text = binding.etMessage.getText().toString().trim();
|
||||||
@@ -408,12 +460,18 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a file picker to select an attachment.
|
||||||
|
*/
|
||||||
private void selectAttachment() {
|
private void selectAttachment() {
|
||||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
intent.setType("*/*");
|
intent.setType("*/*");
|
||||||
attachmentLauncher.launch(intent);
|
attachmentLauncher.launch(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a preview of the selected attachment.
|
||||||
|
*/
|
||||||
private void showAttachmentPreview(Uri uri) {
|
private void showAttachmentPreview(Uri uri) {
|
||||||
pendingAttachmentUri = uri;
|
pendingAttachmentUri = uri;
|
||||||
binding.layoutAttachmentPreview.setVisibility(View.VISIBLE);
|
binding.layoutAttachmentPreview.setVisibility(View.VISIBLE);
|
||||||
@@ -427,11 +485,17 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the currently selected attachment from the preview.
|
||||||
|
*/
|
||||||
private void removeAttachment() {
|
private void removeAttachment() {
|
||||||
pendingAttachmentUri = null;
|
pendingAttachmentUri = null;
|
||||||
binding.layoutAttachmentPreview.setVisibility(View.GONE);
|
binding.layoutAttachmentPreview.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message with a file attachment.
|
||||||
|
*/
|
||||||
private void sendWithAttachment(Uri uri) {
|
private void sendWithAttachment(Uri uri) {
|
||||||
if (activeConversationId == null) return;
|
if (activeConversationId == null) return;
|
||||||
|
|
||||||
@@ -468,6 +532,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for when a new message is received through the WebSocket.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onMessageReceived(MessageDTO dto) {
|
public void onMessageReceived(MessageDTO dto) {
|
||||||
requireActivity().runOnUiThread(() -> {
|
requireActivity().runOnUiThread(() -> {
|
||||||
@@ -478,6 +545,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for when a conversation's status or last message is updated.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onConversationUpdated(ConversationDTO dto) {
|
public void onConversationUpdated(ConversationDTO dto) {
|
||||||
requireActivity().runOnUiThread(() -> {
|
requireActivity().runOnUiThread(() -> {
|
||||||
@@ -489,6 +559,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for when the WebSocket connection is successfully opened.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onSocketOpened() {
|
public void onSocketOpened() {
|
||||||
if (!isAdded()) return;
|
if (!isAdded()) return;
|
||||||
@@ -498,12 +571,18 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for when the WebSocket connection is closed.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onSocketClosed() {
|
public void onSocketClosed() {
|
||||||
if (!isAdded()) return;
|
if (!isAdded()) return;
|
||||||
requireActivity().runOnUiThread(viewModel::loadConversations);
|
requireActivity().runOnUiThread(viewModel::loadConversations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for when a WebSocket error occurs.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onSocketError() {
|
public void onSocketError() {
|
||||||
if (!isAdded()) return;
|
if (!isAdded()) return;
|
||||||
@@ -513,6 +592,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scrolls the message list to the most recent message.
|
||||||
|
*/
|
||||||
private void scrollToBottom() {
|
private void scrollToBottom() {
|
||||||
if (!messageList.isEmpty()) {
|
if (!messageList.isEmpty()) {
|
||||||
binding.rvMessages.post(() ->
|
binding.rvMessages.post(() ->
|
||||||
@@ -520,6 +602,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the UI state based on whether a conversation is active and its status.
|
||||||
|
*/
|
||||||
private void setConversationActive(boolean active, String status) {
|
private void setConversationActive(boolean active, String status) {
|
||||||
boolean isClosed = "CLOSED".equalsIgnoreCase(status);
|
boolean isClosed = "CLOSED".equalsIgnoreCase(status);
|
||||||
UIUtils.setViewsEnabled(active && !isClosed, binding.btnSend, binding.etMessage, binding.btnAttach);
|
UIUtils.setViewsEnabled(active && !isClosed, binding.btnSend, binding.etMessage, binding.btnAttach);
|
||||||
@@ -541,6 +626,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up resources and disconnects the WebSocket when the view is destroyed.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Base fragment that provides common list and search functionality.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments;
|
package com.example.petstoremobile.fragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -23,7 +30,9 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
//The Fragment for the displaying the list of entities to be viewed
|
/**
|
||||||
|
* Fragment that serves as a container for various list-based screens, providing a navigation drawer.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class ListFragment extends Fragment {
|
public class ListFragment extends Fragment {
|
||||||
|
|
||||||
@@ -97,6 +106,9 @@ public class ListFragment extends Fragment {
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding when the view is destroyed.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Fragment for viewing and editing the user's profile.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments;
|
package com.example.petstoremobile.fragments;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@@ -173,12 +180,18 @@ public class ProfileFragment extends Fragment {
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the visibility of the progress bar.
|
||||||
|
*/
|
||||||
private void setLoading(boolean loading) {
|
private void setLoading(boolean loading) {
|
||||||
if (binding != null && binding.progressBar != null) {
|
if (binding != null && binding.progressBar != null) {
|
||||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding when the view is destroyed.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Fragment for browsing the activity log.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.app.DatePickerDialog;
|
import android.app.DatePickerDialog;
|
||||||
@@ -34,6 +41,9 @@ import java.util.List;
|
|||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for viewing application activity logs with various filtering options.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class ActivityLogFragment extends Fragment {
|
public class ActivityLogFragment extends Fragment {
|
||||||
private FragmentActivityLogBinding binding;
|
private FragmentActivityLogBinding binding;
|
||||||
@@ -46,6 +56,9 @@ public class ActivityLogFragment extends Fragment {
|
|||||||
|
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout, checks for admin access, and initializes ViewModel and UI components.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -69,12 +82,18 @@ public class ActivityLogFragment extends Fragment {
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers initial data loading after the view is created.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
viewModel.loadInitialData();
|
viewModel.loadInitialData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the RecyclerView and its scroll listener for pagination.
|
||||||
|
*/
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new ActivityLogAdapter(logList);
|
adapter = new ActivityLogAdapter(logList);
|
||||||
binding.recyclerViewActivityLog.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewActivityLog.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
@@ -97,6 +116,9 @@ public class ActivityLogFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up filters for logs, including search, role, store, and date range.
|
||||||
|
*/
|
||||||
private void setupFilters() {
|
private void setupFilters() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter,
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter,
|
||||||
binding.etSearchLog, binding.spinnerRoleFilter, binding.spinnerStoreFilter);
|
binding.etSearchLog, binding.spinnerRoleFilter, binding.spinnerStoreFilter);
|
||||||
@@ -123,6 +145,9 @@ public class ActivityLogFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a date picker dialog and updates the selected start or end date.
|
||||||
|
*/
|
||||||
private void showDatePicker(boolean isStart) {
|
private void showDatePicker(boolean isStart) {
|
||||||
Calendar cal = Calendar.getInstance();
|
Calendar cal = Calendar.getInstance();
|
||||||
new DatePickerDialog(requireContext(), (view, year, month, day) -> {
|
new DatePickerDialog(requireContext(), (view, year, month, day) -> {
|
||||||
@@ -141,12 +166,18 @@ public class ActivityLogFragment extends Fragment {
|
|||||||
}, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)).show();
|
}, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles store selection from the filter spinner.
|
||||||
|
*/
|
||||||
private void onStoreSelected() {
|
private void onStoreSelected() {
|
||||||
int pos = binding.spinnerStoreFilter.getSelectedItemPosition();
|
int pos = binding.spinnerStoreFilter.getSelectedItemPosition();
|
||||||
Long storeId = (pos > 0 && !storeList.isEmpty()) ? storeList.get(pos - 1).getId() : null;
|
Long storeId = (pos > 0 && !storeList.isEmpty()) ? storeList.get(pos - 1).getId() : null;
|
||||||
viewModel.setStoreFilter(storeId);
|
viewModel.setStoreFilter(storeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes the ViewModel for log list updates, store options, and loading status.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getLogs().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getLogs().observe(getViewLifecycleOwner(), list -> {
|
||||||
logList.clear();
|
logList.clear();
|
||||||
@@ -164,6 +195,9 @@ public class ActivityLogFragment extends Fragment {
|
|||||||
binding.swipeRefreshActivityLog.setRefreshing(loading));
|
binding.swipeRefreshActivityLog.setRefreshing(loading));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding reference.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Fragment for browsing adoption records.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
@@ -44,6 +51,9 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and managing a list of adoptions with a calendar view and filtering.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdoptionClickListener {
|
public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdoptionClickListener {
|
||||||
|
|
||||||
@@ -58,12 +68,18 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
|
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewModel.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(AdoptionListViewModel.class);
|
viewModel = new ViewModelProvider(this).get(AdoptionListViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout and sets up UI components, calendar, and observers.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -88,6 +104,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes the ViewModel for adoption list, stores, and loading status.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getAdoptions().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getAdoptions().observe(getViewLifecycleOwner(), list -> {
|
||||||
adoptionList.clear();
|
adoptionList.clear();
|
||||||
@@ -106,6 +125,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the bulk delete handler for multiple adoption record deletion.
|
||||||
|
*/
|
||||||
private void setupBulkDelete() {
|
private void setupBulkDelete() {
|
||||||
bulkDeleteHandler = new BulkDeleteHandler(
|
bulkDeleteHandler = new BulkDeleteHandler(
|
||||||
this,
|
this,
|
||||||
@@ -119,6 +141,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads adoption data and stores when the fragment resumes.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
@@ -126,6 +151,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
if (!isStaff()) viewModel.loadStores();
|
if (!isStaff()) viewModel.loadStores();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles between month and week display modes for the calendar.
|
||||||
|
*/
|
||||||
private void toggleCalendarMode() {
|
private void toggleCalendarMode() {
|
||||||
isMonthMode = !isMonthMode;
|
isMonthMode = !isMonthMode;
|
||||||
binding.calendarViewAdoption.state().edit()
|
binding.calendarViewAdoption.state().edit()
|
||||||
@@ -133,6 +161,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the filter visibility toggle, considering user roles.
|
||||||
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
if (isStaff()) {
|
if (isStaff()) {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilterAdoption, binding.layoutFilterAdoption,
|
UIUtils.setupFilterToggle(binding.btnToggleFilterAdoption, binding.layoutFilterAdoption,
|
||||||
@@ -144,10 +175,16 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the currently logged-in user has the STAFF role.
|
||||||
|
*/
|
||||||
private boolean isStaff() {
|
private boolean isStaff() {
|
||||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the calendar view for date-based filtering.
|
||||||
|
*/
|
||||||
private void setupCalendar() {
|
private void setupCalendar() {
|
||||||
binding.calendarViewAdoption.setOnDateChangedListener((widget, date, selected) -> {
|
binding.calendarViewAdoption.setOnDateChangedListener((widget, date, selected) -> {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
@@ -164,6 +201,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates calendar decorators to highlight dates with adoptions.
|
||||||
|
*/
|
||||||
private void updateCalendarDecorators() {
|
private void updateCalendarDecorators() {
|
||||||
HashSet<CalendarDay> datesWithAdoptions = new HashSet<>();
|
HashSet<CalendarDay> datesWithAdoptions = new HashSet<>();
|
||||||
for (AdoptionDTO adoption : adoptionList) {
|
for (AdoptionDTO adoption : adoptionList) {
|
||||||
@@ -184,6 +224,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
binding.calendarViewAdoption.addDecorator(new EventDecorator(Color.RED, datesWithAdoptions));
|
binding.calendarViewAdoption.addDecorator(new EventDecorator(Color.RED, datesWithAdoptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the RecyclerView and its scroll listener for pagination.
|
||||||
|
*/
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new AdoptionAdapter(adoptionList, this);
|
adapter = new AdoptionAdapter(adoptionList, this);
|
||||||
binding.recyclerViewAdoptions.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewAdoptions.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
@@ -206,23 +249,38 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the search input listener.
|
||||||
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchAdoption, () -> loadAdoptions(true));
|
UIUtils.attachSearch(binding.etSearchAdoption, () -> loadAdoptions(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the status filter spinner.
|
||||||
|
*/
|
||||||
private void setupStatusFilter() {
|
private void setupStatusFilter() {
|
||||||
String[] statuses = {"All Statuses", "Completed", "Pending", "Missed", "Cancelled"};
|
String[] statuses = {"All Statuses", "Completed", "Pending", "Missed", "Cancelled"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusAdoption, statuses, () -> loadAdoptions(true));
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusAdoption, statuses, () -> loadAdoptions(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the store filter spinner.
|
||||||
|
*/
|
||||||
private void setupStoreFilter() {
|
private void setupStoreFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStoreAdoption, () -> loadAdoptions(true));
|
SpinnerUtils.setupFilterSpinner(binding.spinnerStoreAdoption, () -> loadAdoptions(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the swipe-to-refresh layout.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshAdoption.setOnRefreshListener(() -> loadAdoptions(true));
|
binding.swipeRefreshAdoption.setOnRefreshListener(() -> loadAdoptions(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads adoption data based on current filters, search query, and selected date.
|
||||||
|
*/
|
||||||
private void loadAdoptions(boolean reset) {
|
private void loadAdoptions(boolean reset) {
|
||||||
String query = binding.etSearchAdoption.getText().toString().trim();
|
String query = binding.etSearchAdoption.getText().toString().trim();
|
||||||
String status = binding.spinnerStatusAdoption.getSelectedItem() != null ? binding.spinnerStatusAdoption.getSelectedItem().toString() : "All Statuses";
|
String status = binding.spinnerStatusAdoption.getSelectedItem() != null ? binding.spinnerStatusAdoption.getSelectedItem().toString() : "All Statuses";
|
||||||
@@ -250,6 +308,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
viewModel.loadAdoptions(reset, query, status, storeId, selectedDateString, null);
|
viewModel.loadAdoptions(reset, query, status, storeId, selectedDateString, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the adoption detail screen.
|
||||||
|
*/
|
||||||
private void openDetail(int position) {
|
private void openDetail(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
@@ -259,9 +320,15 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_adoption_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_adoption_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles adoption item clicks by opening details.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onAdoptionClick(int position) { openDetail(position); }
|
public void onAdoptionClick(int position) { openDetail(position); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forwards selection changes to the bulk delete handler.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onSelectionChanged(int selectedCount) {
|
public void onSelectionChanged(int selectedCount) {
|
||||||
if (bulkDeleteHandler != null) {
|
if (bulkDeleteHandler != null) {
|
||||||
@@ -269,6 +336,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding reference.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Fragment for displaying store analytics and charts.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
@@ -18,6 +25,9 @@ import java.math.BigDecimal;
|
|||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying business analytics, including revenue, transactions, and product performance.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class AnalyticsFragment extends Fragment {
|
public class AnalyticsFragment extends Fragment {
|
||||||
|
|
||||||
@@ -31,6 +41,9 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
private static final String[] TOP_N_OPTIONS = {"5", "10", "15", "20"};
|
private static final String[] TOP_N_OPTIONS = {"5", "10", "15", "20"};
|
||||||
private static final int[] TOP_N_VALUES = { 5, 10, 15, 20 };
|
private static final int[] TOP_N_VALUES = { 5, 10, 15, 20 };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout, initializes ViewModel, and sets up UI components and filters.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -51,6 +64,9 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
private static final int COLOR_SELECTED = 0xFF4ECDC4;
|
private static final int COLOR_SELECTED = 0xFF4ECDC4;
|
||||||
private static final int COLOR_UNSELECTED = 0xFFCBD5E1;
|
private static final int COLOR_UNSELECTED = 0xFFCBD5E1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the view mode toggle buttons (My Analytics vs Store Analytics).
|
||||||
|
*/
|
||||||
private void setupViewModeToggle() {
|
private void setupViewModeToggle() {
|
||||||
updateViewModeButtonStyles(viewModel.getViewMode());
|
updateViewModeButtonStyles(viewModel.getViewMode());
|
||||||
|
|
||||||
@@ -67,6 +83,9 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the styles of the view mode buttons based on the selected mode.
|
||||||
|
*/
|
||||||
private void updateViewModeButtonStyles(String mode) {
|
private void updateViewModeButtonStyles(String mode) {
|
||||||
binding.btnMyAnalytics.setBackgroundTintList(
|
binding.btnMyAnalytics.setBackgroundTintList(
|
||||||
android.content.res.ColorStateList.valueOf(mode.equals("mine") ? COLOR_SELECTED : COLOR_UNSELECTED));
|
android.content.res.ColorStateList.valueOf(mode.equals("mine") ? COLOR_SELECTED : COLOR_UNSELECTED));
|
||||||
@@ -74,6 +93,9 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
android.content.res.ColorStateList.valueOf(mode.equals("store") ? COLOR_SELECTED : COLOR_UNSELECTED));
|
android.content.res.ColorStateList.valueOf(mode.equals("store") ? COLOR_SELECTED : COLOR_UNSELECTED));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the visibility of the store filter based on the user's role and selected view mode.
|
||||||
|
*/
|
||||||
private void updateStoreFilterVisibility(String mode) {
|
private void updateStoreFilterVisibility(String mode) {
|
||||||
boolean isAdmin = "ADMIN".equalsIgnoreCase(tokenManager.getRole());
|
boolean isAdmin = "ADMIN".equalsIgnoreCase(tokenManager.getRole());
|
||||||
int vis = (isAdmin && mode.equals("store")) ? View.VISIBLE : View.GONE;
|
int vis = (isAdmin && mode.equals("store")) ? View.VISIBLE : View.GONE;
|
||||||
@@ -81,8 +103,10 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
binding.spinnerFilterStore.setVisibility(vis);
|
binding.spinnerFilterStore.setVisibility(vis);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter Panel
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the filter panel, including date pickers, presets, and action buttons.
|
||||||
|
*/
|
||||||
private void setupFilterPanel() {
|
private void setupFilterPanel() {
|
||||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerTopN, TOP_N_OPTIONS);
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerTopN, TOP_N_OPTIONS);
|
||||||
|
|
||||||
@@ -111,12 +135,18 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
binding.btnFilterReset.setOnClickListener(v -> resetFilters());
|
binding.btnFilterReset.setOnClickListener(v -> resetFilters());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the visibility of the filter content section.
|
||||||
|
*/
|
||||||
private void toggleFilters() {
|
private void toggleFilters() {
|
||||||
filtersExpanded = !filtersExpanded;
|
filtersExpanded = !filtersExpanded;
|
||||||
binding.llFilterContent.setVisibility(filtersExpanded ? View.VISIBLE : View.GONE);
|
binding.llFilterContent.setVisibility(filtersExpanded ? View.VISIBLE : View.GONE);
|
||||||
binding.tvFilterToggleIcon.setText(filtersExpanded ? "▲" : "▼");
|
binding.tvFilterToggleIcon.setText(filtersExpanded ? "▲" : "▼");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a date range preset to the filter fields.
|
||||||
|
*/
|
||||||
private void applyPreset(int startOffset, int endOffset) {
|
private void applyPreset(int startOffset, int endOffset) {
|
||||||
binding.etFilterStartDate.setText(getDateString(startOffset));
|
binding.etFilterStartDate.setText(getDateString(startOffset));
|
||||||
binding.etFilterEndDate.setText(getDateString(endOffset));
|
binding.etFilterEndDate.setText(getDateString(endOffset));
|
||||||
@@ -124,6 +154,9 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
applyFiltersFromUI();
|
applyFiltersFromUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads filter values from the UI and applies them to the ViewModel.
|
||||||
|
*/
|
||||||
private void applyFiltersFromUI() {
|
private void applyFiltersFromUI() {
|
||||||
AnalyticsViewModel.FilterState filter = new AnalyticsViewModel.FilterState();
|
AnalyticsViewModel.FilterState filter = new AnalyticsViewModel.FilterState();
|
||||||
filter.startDate = binding.etFilterStartDate.getText().toString().trim();
|
filter.startDate = binding.etFilterStartDate.getText().toString().trim();
|
||||||
@@ -142,6 +175,9 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
viewModel.applyFilter(filter);
|
viewModel.applyFilter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets all filters to their default values.
|
||||||
|
*/
|
||||||
private void resetFilters() {
|
private void resetFilters() {
|
||||||
binding.etFilterStartDate.setText("");
|
binding.etFilterStartDate.setText("");
|
||||||
binding.etFilterEndDate.setText("");
|
binding.etFilterEndDate.setText("");
|
||||||
@@ -152,6 +188,9 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
viewModel.resetFilter();
|
viewModel.resetFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the text summary of the currently selected date range.
|
||||||
|
*/
|
||||||
private void updateFilterSummary() {
|
private void updateFilterSummary() {
|
||||||
String start = binding.etFilterStartDate.getText().toString().trim();
|
String start = binding.etFilterStartDate.getText().toString().trim();
|
||||||
String end = binding.etFilterEndDate.getText().toString().trim();
|
String end = binding.etFilterEndDate.getText().toString().trim();
|
||||||
@@ -166,10 +205,16 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a date string into a shorter version for display.
|
||||||
|
*/
|
||||||
private String shortDate(String date) {
|
private String shortDate(String date) {
|
||||||
return (date != null && date.length() >= 10) ? date.substring(5) : date;
|
return (date != null && date.length() >= 10) ? date.substring(5) : date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a formatted date string for a given day.
|
||||||
|
*/
|
||||||
private String getDateString(int offsetDays) {
|
private String getDateString(int offsetDays) {
|
||||||
Calendar c = Calendar.getInstance();
|
Calendar c = Calendar.getInstance();
|
||||||
c.add(Calendar.DAY_OF_YEAR, offsetDays);
|
c.add(Calendar.DAY_OF_YEAR, offsetDays);
|
||||||
@@ -177,8 +222,10 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, c.get(Calendar.DAY_OF_MONTH));
|
c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, c.get(Calendar.DAY_OF_MONTH));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ViewModel Observation
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes the ViewModel for analytics data, loading status, errors, and filter options.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getAnalyticsData().observe(getViewLifecycleOwner(), this::computeAndDisplay);
|
viewModel.getAnalyticsData().observe(getViewLifecycleOwner(), this::computeAndDisplay);
|
||||||
|
|
||||||
@@ -216,14 +263,19 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding reference.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes and displays analytics data in summary cards and bar charts.
|
||||||
|
*/
|
||||||
private void computeAndDisplay(AnalyticsViewModel.AnalyticsData data) {
|
private void computeAndDisplay(AnalyticsViewModel.AnalyticsData data) {
|
||||||
if (data == null) return;
|
if (data == null) return;
|
||||||
|
|
||||||
@@ -312,8 +364,10 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chart Helpers
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamically adds a bar chart row to a given layout container.
|
||||||
|
*/
|
||||||
private void addBarRow(LinearLayout parent, String label, String value, float ratio, String color) {
|
private void addBarRow(LinearLayout parent, String label, String value, float ratio, String color) {
|
||||||
if (getContext() == null) return;
|
if (getContext() == null) return;
|
||||||
LinearLayout row = new LinearLayout(getContext());
|
LinearLayout row = new LinearLayout(getContext());
|
||||||
@@ -359,6 +413,9 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
parent.addView(row);
|
parent.addView(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an empty message row to a given layout container.
|
||||||
|
*/
|
||||||
private void addEmptyRow(LinearLayout parent, String message) {
|
private void addEmptyRow(LinearLayout parent, String message) {
|
||||||
if (getContext() == null) return;
|
if (getContext() == null) return;
|
||||||
TextView tv = new TextView(getContext());
|
TextView tv = new TextView(getContext());
|
||||||
@@ -368,6 +425,9 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
parent.addView(tv);
|
parent.addView(tv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays an error message and updates UI to reflect the error state.
|
||||||
|
*/
|
||||||
private void showError(String msg) {
|
private void showError(String msg) {
|
||||||
if (getContext() == null || binding == null) return;
|
if (getContext() == null || binding == null) return;
|
||||||
binding.tvTotalRevenue.setText("Error");
|
binding.tvTotalRevenue.setText("Error");
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Fragment for browsing appointments.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
@@ -45,6 +52,9 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and managing a list of appointments with calendar integration and filtering.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class AppointmentFragment extends Fragment implements AppointmentAdapter.OnAppointmentClickListener {
|
public class AppointmentFragment extends Fragment implements AppointmentAdapter.OnAppointmentClickListener {
|
||||||
|
|
||||||
@@ -63,6 +73,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
private Long currentUserId = null;
|
private Long currentUserId = null;
|
||||||
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes ViewModels for appointment and authentication data.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -70,6 +83,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
authViewModel = new ViewModelProvider(this).get(AuthViewModel.class);
|
authViewModel = new ViewModelProvider(this).get(AuthViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout and sets up UI components, calendar, and observers.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -97,6 +113,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes the ViewModel for appointment list, stores, and loading status.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getAppointments().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getAppointments().observe(getViewLifecycleOwner(), list -> {
|
||||||
appointmentList.clear();
|
appointmentList.clear();
|
||||||
@@ -115,6 +134,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the bulk delete handler for multiple appointment deletion.
|
||||||
|
*/
|
||||||
private void setupBulkDelete() {
|
private void setupBulkDelete() {
|
||||||
bulkDeleteHandler = new BulkDeleteHandler(
|
bulkDeleteHandler = new BulkDeleteHandler(
|
||||||
this,
|
this,
|
||||||
@@ -128,6 +150,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads appointment data and stores when the fragment resumes.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
@@ -135,6 +160,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
if (!isStaff()) viewModel.loadStores();
|
if (!isStaff()) viewModel.loadStores();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles between month and week display modes for the calendar.
|
||||||
|
*/
|
||||||
private void toggleCalendarMode() {
|
private void toggleCalendarMode() {
|
||||||
isMonthMode = !isMonthMode;
|
isMonthMode = !isMonthMode;
|
||||||
binding.calendarView.state().edit()
|
binding.calendarView.state().edit()
|
||||||
@@ -142,12 +170,18 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the "My Appointments" filter button.
|
||||||
|
*/
|
||||||
private void setupMyAppointmentFilter() {
|
private void setupMyAppointmentFilter() {
|
||||||
binding.btnMyAppointments.setOnClickListener(v -> {
|
binding.btnMyAppointments.setOnClickListener(v -> {
|
||||||
loadAppointmentData(true);
|
loadAppointmentData(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads information about the currently logged-in user.
|
||||||
|
*/
|
||||||
private void loadCurrentUserInfo() {
|
private void loadCurrentUserInfo() {
|
||||||
authViewModel.getMe().observe(getViewLifecycleOwner(), resource -> {
|
authViewModel.getMe().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
@@ -156,6 +190,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the filter visibility toggle.
|
||||||
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
if (isStaff()) {
|
if (isStaff()) {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchAppointment, binding.spinnerStatus);
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchAppointment, binding.spinnerStatus);
|
||||||
@@ -166,6 +203,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the calendar view for date-based filtering.
|
||||||
|
*/
|
||||||
private void setupCalendar() {
|
private void setupCalendar() {
|
||||||
binding.calendarView.setOnDateChangedListener((widget, date, selected) -> {
|
binding.calendarView.setOnDateChangedListener((widget, date, selected) -> {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
@@ -182,6 +222,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates calendar decorators to highlight dates with appointments.
|
||||||
|
*/
|
||||||
private void updateCalendarDecorators() {
|
private void updateCalendarDecorators() {
|
||||||
HashSet<CalendarDay> datesWithAppointments = new HashSet<>();
|
HashSet<CalendarDay> datesWithAppointments = new HashSet<>();
|
||||||
for (AppointmentDTO appointment : appointmentList) {
|
for (AppointmentDTO appointment : appointmentList) {
|
||||||
@@ -200,23 +243,38 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
binding.calendarView.addDecorator(new EventDecorator(Color.RED, datesWithAppointments));
|
binding.calendarView.addDecorator(new EventDecorator(Color.RED, datesWithAppointments));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the search input listener.
|
||||||
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchAppointment, () -> loadAppointmentData(true));
|
UIUtils.attachSearch(binding.etSearchAppointment, () -> loadAppointmentData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the status filter spinner.
|
||||||
|
*/
|
||||||
private void setupStatusFilter() {
|
private void setupStatusFilter() {
|
||||||
String[] statuses = {"All Statuses", "Booked", "Completed", "Cancelled", "Missed"};
|
String[] statuses = {"All Statuses", "Booked", "Completed", "Cancelled", "Missed"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatus, statuses, () -> loadAppointmentData(true));
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatus, statuses, () -> loadAppointmentData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the store filter spinner.
|
||||||
|
*/
|
||||||
private void setupStoreFilter() {
|
private void setupStoreFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadAppointmentData(true));
|
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadAppointmentData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the swipe-to-refresh layout.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshAppointment.setOnRefreshListener(() -> loadAppointmentData(true));
|
binding.swipeRefreshAppointment.setOnRefreshListener(() -> loadAppointmentData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the appointment detail screen.
|
||||||
|
*/
|
||||||
private void openAppointmentDetails(int position) {
|
private void openAppointmentDetails(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
@@ -226,11 +284,17 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_appointment_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_appointment_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles appointment item clicks by opening details.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onAppointmentClick(int position) {
|
public void onAppointmentClick(int position) {
|
||||||
openAppointmentDetails(position);
|
openAppointmentDetails(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forwards selection changes to the bulk delete handler.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onSelectionChanged(int count) {
|
public void onSelectionChanged(int count) {
|
||||||
if (bulkDeleteHandler != null) {
|
if (bulkDeleteHandler != null) {
|
||||||
@@ -238,10 +302,16 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the currently logged-in user has the STAFF role.
|
||||||
|
*/
|
||||||
private boolean isStaff() {
|
private boolean isStaff() {
|
||||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads appointment data based on current filters, search query, and selected date.
|
||||||
|
*/
|
||||||
private void loadAppointmentData(boolean reset) {
|
private void loadAppointmentData(boolean reset) {
|
||||||
String query = binding.etSearchAppointment.getText().toString().trim();
|
String query = binding.etSearchAppointment.getText().toString().trim();
|
||||||
String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses";
|
String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses";
|
||||||
@@ -274,6 +344,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
viewModel.loadAppointments(reset, query, status, storeId, selectedDateString, employeeId);
|
viewModel.loadAppointments(reset, query, status, storeId, selectedDateString, employeeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the RecyclerView and its scroll listener for pagination.
|
||||||
|
*/
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new AppointmentAdapter(appointmentList, this);
|
adapter = new AppointmentAdapter(appointmentList, this);
|
||||||
binding.recyclerViewAppointments.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewAppointments.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
@@ -296,6 +369,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding reference.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Fragment for browsing coupons.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -27,6 +34,9 @@ import java.util.List;
|
|||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and managing a list of coupons.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class CouponFragment extends Fragment implements CouponAdapter.OnCouponClickListener {
|
public class CouponFragment extends Fragment implements CouponAdapter.OnCouponClickListener {
|
||||||
private FragmentCouponBinding binding;
|
private FragmentCouponBinding binding;
|
||||||
@@ -34,6 +44,9 @@ public class CouponFragment extends Fragment implements CouponAdapter.OnCouponCl
|
|||||||
private CouponAdapter adapter;
|
private CouponAdapter adapter;
|
||||||
private final List<CouponDTO> couponList = new ArrayList<>();
|
private final List<CouponDTO> couponList = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout, initializes ViewModel, and sets up UI components.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -59,6 +72,9 @@ public class CouponFragment extends Fragment implements CouponAdapter.OnCouponCl
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes the ViewModel for coupon list updates and loading status.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getCoupons().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getCoupons().observe(getViewLifecycleOwner(), list -> {
|
||||||
couponList.clear();
|
couponList.clear();
|
||||||
@@ -71,6 +87,9 @@ public class CouponFragment extends Fragment implements CouponAdapter.OnCouponCl
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the RecyclerView and its scroll listener for pagination.
|
||||||
|
*/
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new CouponAdapter(couponList, this);
|
adapter = new CouponAdapter(couponList, this);
|
||||||
binding.recyclerViewCoupon.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewCoupon.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
@@ -93,24 +112,39 @@ public class CouponFragment extends Fragment implements CouponAdapter.OnCouponCl
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the status filter spinner.
|
||||||
|
*/
|
||||||
private void setupStatusFilter() {
|
private void setupStatusFilter() {
|
||||||
String[] statuses = {"All Statuses", "Active", "Inactive"};
|
String[] statuses = {"All Statuses", "Active", "Inactive"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusCoupon, statuses, () -> applyFilters(true));
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusCoupon, statuses, () -> applyFilters(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the discount type filter spinner.
|
||||||
|
*/
|
||||||
private void setupTypeFilter() {
|
private void setupTypeFilter() {
|
||||||
String[] types = {"All Types", "FIXED", "PERCENT"};
|
String[] types = {"All Types", "FIXED", "PERCENT"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerTypeCoupon, types, () -> applyFilters(true));
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerTypeCoupon, types, () -> applyFilters(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the search input listener.
|
||||||
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchCoupon, () -> applyFilters(true));
|
UIUtils.attachSearch(binding.etSearchCoupon, () -> applyFilters(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the swipe-to-refresh layout.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshCoupon.setOnRefreshListener(() -> applyFilters(true));
|
binding.swipeRefreshCoupon.setOnRefreshListener(() -> applyFilters(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies filters and loads the coupon list.
|
||||||
|
*/
|
||||||
private void applyFilters(boolean reset) {
|
private void applyFilters(boolean reset) {
|
||||||
String statusStr = binding.spinnerStatusCoupon.getSelectedItem() != null ?
|
String statusStr = binding.spinnerStatusCoupon.getSelectedItem() != null ?
|
||||||
binding.spinnerStatusCoupon.getSelectedItem().toString() : "All Statuses";
|
binding.spinnerStatusCoupon.getSelectedItem().toString() : "All Statuses";
|
||||||
@@ -122,25 +156,39 @@ public class CouponFragment extends Fragment implements CouponAdapter.OnCouponCl
|
|||||||
binding.spinnerTypeCoupon.getSelectedItem().toString() : "All Types";
|
binding.spinnerTypeCoupon.getSelectedItem().toString() : "All Types";
|
||||||
String discountType = typeStr.equals("All Types") ? null : typeStr;
|
String discountType = typeStr.equals("All Types") ? null : typeStr;
|
||||||
|
|
||||||
viewModel.loadCoupons(reset, active, discountType, null);
|
String search = binding.etSearchCoupon.getText() != null ? binding.etSearchCoupon.getText().toString().trim() : null;
|
||||||
|
if (search != null && search.isEmpty()) search = null;
|
||||||
|
viewModel.loadCoupons(reset, active, discountType, search);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the coupon detail screen.
|
||||||
|
*/
|
||||||
private void openDetail(long id) {
|
private void openDetail(long id) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putLong("couponId", id);
|
args.putLong("couponId", id);
|
||||||
Navigation.findNavController(requireView()).navigate(R.id.couponDetailFragment, args);
|
Navigation.findNavController(requireView()).navigate(R.id.couponDetailFragment, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles coupon item clicks by opening details.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCouponClick(CouponDTO coupon) {
|
public void onCouponClick(CouponDTO coupon) {
|
||||||
openDetail(coupon.getCouponId());
|
openDetail(coupon.getCouponId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows or hides the bulk delete button based on selection count.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onSelectionChanged(int count) {
|
public void onSelectionChanged(int count) {
|
||||||
binding.btnBulkDeleteCoupons.setVisibility(count > 0 ? View.VISIBLE : View.GONE);
|
binding.btnBulkDeleteCoupons.setVisibility(count > 0 ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a confirmation dialog for deleting multiple coupons.
|
||||||
|
*/
|
||||||
private void confirmBulkDelete() {
|
private void confirmBulkDelete() {
|
||||||
new AlertDialog.Builder(requireContext())
|
new AlertDialog.Builder(requireContext())
|
||||||
.setTitle("Confirm Bulk Delete")
|
.setTitle("Confirm Bulk Delete")
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Fragment for browsing customers.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -22,6 +29,9 @@ import java.util.*;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and managing a list of customers.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class CustomerFragment extends Fragment implements CustomerAdapter.OnCustomerClickListener {
|
public class CustomerFragment extends Fragment implements CustomerAdapter.OnCustomerClickListener {
|
||||||
|
|
||||||
@@ -33,6 +43,9 @@ public class CustomerFragment extends Fragment implements CustomerAdapter.OnCust
|
|||||||
@Inject @Named("baseUrl") String baseUrl;
|
@Inject @Named("baseUrl") String baseUrl;
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout, initializes ViewModel, and sets up UI components and filters.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -45,17 +58,21 @@ public class CustomerFragment extends Fragment implements CustomerAdapter.OnCust
|
|||||||
setupSwipeRefresh();
|
setupSwipeRefresh();
|
||||||
observeViewModel();
|
observeViewModel();
|
||||||
|
|
||||||
|
viewModel.resetFilters();
|
||||||
viewModel.loadCustomers(true);
|
viewModel.loadCustomers(true);
|
||||||
|
|
||||||
binding.fabAddCustomer.setOnClickListener(v -> openDetail(-1));
|
binding.fabAddCustomer.setOnClickListener(v -> openDetail(-1));
|
||||||
|
|
||||||
UIUtils.setupHamburgerMenu(binding.btnHamburgerCustomer, this);
|
UIUtils.setupHamburgerMenu(binding.btnHamburgerCustomer, this);
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilterCustomer, binding.layoutFilterCustomer,
|
UIUtils.setupFilterToggle(binding.btnToggleFilterCustomer, binding.layoutFilterCustomer,
|
||||||
binding.etSearchCustomer, binding.spinnerStatusCustomer);
|
binding.etSearchCustomer, this::applyFilters, binding.spinnerStatusCustomer);
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes the ViewModel for customer list updates and loading status.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getFilteredCustomers().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getFilteredCustomers().observe(getViewLifecycleOwner(), list -> {
|
||||||
customerList.clear();
|
customerList.clear();
|
||||||
@@ -68,6 +85,9 @@ public class CustomerFragment extends Fragment implements CustomerAdapter.OnCust
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the RecyclerView and its scroll listener for pagination.
|
||||||
|
*/
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new CustomerAdapter(customerList, this);
|
adapter = new CustomerAdapter(customerList, this);
|
||||||
adapter.setBaseUrl(baseUrl);
|
adapter.setBaseUrl(baseUrl);
|
||||||
@@ -92,15 +112,24 @@ public class CustomerFragment extends Fragment implements CustomerAdapter.OnCust
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the status filter spinner.
|
||||||
|
*/
|
||||||
private void setupStatusFilter() {
|
private void setupStatusFilter() {
|
||||||
String[] statuses = {"All Statuses", "Active", "Inactive"};
|
String[] statuses = {"All Statuses", "Active", "Inactive"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusCustomer, statuses, this::applyFilters);
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusCustomer, statuses, this::applyFilters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the search input listener.
|
||||||
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchCustomer, this::applyFilters);
|
UIUtils.attachSearch(binding.etSearchCustomer, this::applyFilters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies filters and triggers data reloading or filtering in ViewModel.
|
||||||
|
*/
|
||||||
private void applyFilters() {
|
private void applyFilters() {
|
||||||
String query = binding.etSearchCustomer.getText().toString().trim();
|
String query = binding.etSearchCustomer.getText().toString().trim();
|
||||||
String status = binding.spinnerStatusCustomer.getSelectedItem() != null ?
|
String status = binding.spinnerStatusCustomer.getSelectedItem() != null ?
|
||||||
@@ -108,10 +137,16 @@ public class CustomerFragment extends Fragment implements CustomerAdapter.OnCust
|
|||||||
viewModel.filter(query, status);
|
viewModel.filter(query, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the swipe-to-refresh layout.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshCustomer.setOnRefreshListener(() -> viewModel.loadCustomers(true));
|
binding.swipeRefreshCustomer.setOnRefreshListener(() -> viewModel.loadCustomers(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the customer detail screen.
|
||||||
|
*/
|
||||||
private void openDetail(int position) {
|
private void openDetail(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
@@ -129,11 +164,17 @@ public class CustomerFragment extends Fragment implements CustomerAdapter.OnCust
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_customer_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_customer_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles customer item clicks by opening details.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCustomerClick(int position) {
|
public void onCustomerClick(int position) {
|
||||||
openDetail(position);
|
openDetail(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding reference.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Fragment for browsing inventory items.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -32,6 +39,9 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and managing inventory items.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class InventoryFragment extends Fragment implements InventoryAdapter.OnInventoryClickListener {
|
public class InventoryFragment extends Fragment implements InventoryAdapter.OnInventoryClickListener {
|
||||||
|
|
||||||
@@ -43,12 +53,18 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
|
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewModel.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(InventoryListViewModel.class);
|
viewModel = new ViewModelProvider(this).get(InventoryListViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout and sets up UI components, filters, and observers.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -71,6 +87,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes the ViewModel for inventory list updates, store list, and loading status.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getInventory().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getInventory().observe(getViewLifecycleOwner(), list -> {
|
||||||
inventoryList.clear();
|
inventoryList.clear();
|
||||||
@@ -88,6 +107,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the bulk delete handler for multiple inventory item deletion.
|
||||||
|
*/
|
||||||
private void setupBulkDelete() {
|
private void setupBulkDelete() {
|
||||||
bulkDeleteHandler = new BulkDeleteHandler(
|
bulkDeleteHandler = new BulkDeleteHandler(
|
||||||
this,
|
this,
|
||||||
@@ -101,18 +123,27 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads store data if necessary when the fragment resumes.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
if (!isStaff()) viewModel.loadStores();
|
if (!isStaff()) viewModel.loadStores();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding reference.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the filter visibility toggle, considering user roles.
|
||||||
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
if (isStaff()) {
|
if (isStaff()) {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchInventory);
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchInventory);
|
||||||
@@ -122,18 +153,30 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the currently logged-in user has the STAFF role.
|
||||||
|
*/
|
||||||
private boolean isStaff() {
|
private boolean isStaff() {
|
||||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the search input listener.
|
||||||
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchInventory, () -> loadInventory(true));
|
UIUtils.attachSearch(binding.etSearchInventory, () -> loadInventory(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the store filter spinner.
|
||||||
|
*/
|
||||||
private void setupStoreFilter() {
|
private void setupStoreFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadInventory(true));
|
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadInventory(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the RecyclerView and its scroll listener for pagination.
|
||||||
|
*/
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new InventoryAdapter(inventoryList, this);
|
adapter = new InventoryAdapter(inventoryList, this);
|
||||||
binding.recyclerViewInventory.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewInventory.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
@@ -156,10 +199,16 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the swipe-to-refresh layout.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshInventory.setOnRefreshListener(() -> loadInventory(true));
|
binding.swipeRefreshInventory.setOnRefreshListener(() -> loadInventory(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads inventory data based on current search query and store filter.
|
||||||
|
*/
|
||||||
private void loadInventory(boolean reset) {
|
private void loadInventory(boolean reset) {
|
||||||
String query = binding.etSearchInventory != null ? binding.etSearchInventory.getText().toString().trim() : "";
|
String query = binding.etSearchInventory != null ? binding.etSearchInventory.getText().toString().trim() : "";
|
||||||
if (query.isEmpty()) query = null;
|
if (query.isEmpty()) query = null;
|
||||||
@@ -178,6 +227,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
viewModel.loadInventory(reset, query, storeId);
|
viewModel.loadInventory(reset, query, storeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the inventory detail screen.
|
||||||
|
*/
|
||||||
private void openDetail(InventoryDTO inv) {
|
private void openDetail(InventoryDTO inv) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (inv != null) {
|
if (inv != null) {
|
||||||
@@ -186,6 +238,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_inventory_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_inventory_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles inventory item clicks by opening details.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onInventoryClick(int position) {
|
public void onInventoryClick(int position) {
|
||||||
if (position >= 0 && position < inventoryList.size()) {
|
if (position >= 0 && position < inventoryList.size()) {
|
||||||
@@ -193,6 +248,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forwards selection changes to the bulk delete handler.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onSelectionChanged(int selectedCount) {
|
public void onSelectionChanged(int selectedCount) {
|
||||||
if (bulkDeleteHandler != null) {
|
if (bulkDeleteHandler != null) {
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Fragment for browsing pets.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -33,6 +40,9 @@ import javax.inject.Named;
|
|||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and managing a list of pets.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class PetFragment extends Fragment implements PetAdapter.OnPetClickListener {
|
public class PetFragment extends Fragment implements PetAdapter.OnPetClickListener {
|
||||||
private FragmentPetBinding binding;
|
private FragmentPetBinding binding;
|
||||||
@@ -44,12 +54,18 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
@Inject @Named("baseUrl") String baseUrl;
|
@Inject @Named("baseUrl") String baseUrl;
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the view model.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(PetListViewModel.class);
|
viewModel = new ViewModelProvider(this).get(PetListViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout and initializes UI components and filters.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -72,6 +88,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes LiveData from the ViewModel to update the list and filter options.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getPets().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getPets().observe(getViewLifecycleOwner(), list -> {
|
||||||
petList.clear();
|
petList.clear();
|
||||||
@@ -94,6 +113,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the handler for bulk deletion of pets.
|
||||||
|
*/
|
||||||
private void setupBulkDelete() {
|
private void setupBulkDelete() {
|
||||||
bulkDeleteHandler = new BulkDeleteHandler(
|
bulkDeleteHandler = new BulkDeleteHandler(
|
||||||
this,
|
this,
|
||||||
@@ -107,6 +129,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes pet data and filters when the fragment is resumed.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
@@ -115,6 +140,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
if (!isStaff()) viewModel.loadStores();
|
if (!isStaff()) viewModel.loadStores();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the visibility of filters based on the user's role.
|
||||||
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
if (isStaff()) {
|
if (isStaff()) {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPet,
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPet,
|
||||||
@@ -126,32 +154,53 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current user has the 'STAFF' role.
|
||||||
|
*/
|
||||||
private boolean isStaff() {
|
private boolean isStaff() {
|
||||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches search functionality to the search input field.
|
||||||
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchPet, () -> loadPetData(true));
|
UIUtils.attachSearch(binding.etSearchPet, () -> loadPetData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the status filter spinner.
|
||||||
|
*/
|
||||||
private void setupStatusFilter() {
|
private void setupStatusFilter() {
|
||||||
String[] statuses = {"All Statuses", "Available", "Adopted", "Owned", "Pending"};
|
String[] statuses = {"All Statuses", "Available", "Adopted", "Owned", "Pending"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatus, statuses, () -> loadPetData(true));
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatus, statuses, () -> loadPetData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the species filter spinner.
|
||||||
|
*/
|
||||||
private void setupSpeciesFilter() {
|
private void setupSpeciesFilter() {
|
||||||
String[] initial = {"All Species"};
|
String[] initial = {"All Species"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerSpecies, initial, () -> loadPetData(true));
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerSpecies, initial, () -> loadPetData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the store filter spinner.
|
||||||
|
*/
|
||||||
private void setupStoreFilter() {
|
private void setupStoreFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadPetData(true));
|
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadPetData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the swipe-to-refresh layout.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshPet.setOnRefreshListener(() -> loadPetData(true));
|
binding.swipeRefreshPet.setOnRefreshListener(() -> loadPetData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers loading of pet data from the backend with current filters.
|
||||||
|
*/
|
||||||
private void loadPetData(boolean reset) {
|
private void loadPetData(boolean reset) {
|
||||||
String query = binding.etSearchPet.getText().toString().trim();
|
String query = binding.etSearchPet.getText().toString().trim();
|
||||||
String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses";
|
String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses";
|
||||||
@@ -171,6 +220,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
viewModel.loadPets(reset, query, status, species, storeId);
|
viewModel.loadPets(reset, query, status, species, storeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the RecyclerView and its scroll listener for pagination.
|
||||||
|
*/
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new PetAdapter(petList, this);
|
adapter = new PetAdapter(petList, this);
|
||||||
adapter.setBaseUrl(baseUrl);
|
adapter.setBaseUrl(baseUrl);
|
||||||
@@ -195,6 +247,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the profile view of a specific pet.
|
||||||
|
*/
|
||||||
private void openPetProfile(int position) {
|
private void openPetProfile(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
PetDTO pet = petList.get(position);
|
PetDTO pet = petList.get(position);
|
||||||
@@ -202,15 +257,24 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_pet_profile, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_pet_profile, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the screen for adding a new pet.
|
||||||
|
*/
|
||||||
private void openPetDetails() {
|
private void openPetDetails() {
|
||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_pet_detail);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_pet_detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles clicks on individual pets in the list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onPetClick(int position) {
|
public void onPetClick(int position) {
|
||||||
openPetProfile(position);
|
openPetProfile(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the bulk delete handler when item selection changes.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onSelectionChanged(int selectedCount) {
|
public void onSelectionChanged(int selectedCount) {
|
||||||
if (bulkDeleteHandler != null) {
|
if (bulkDeleteHandler != null) {
|
||||||
@@ -218,6 +282,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding when the view is destroyed.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Fragment for browsing products.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -31,6 +38,9 @@ import javax.inject.Named;
|
|||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and managing a list of products.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class ProductFragment extends Fragment implements ProductAdapter.OnProductClickListener {
|
public class ProductFragment extends Fragment implements ProductAdapter.OnProductClickListener {
|
||||||
|
|
||||||
@@ -41,12 +51,18 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
|
|
||||||
@Inject @Named("baseUrl") String baseUrl;
|
@Inject @Named("baseUrl") String baseUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewModel.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(ProductListViewModel.class);
|
viewModel = new ViewModelProvider(this).get(ProductListViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout and sets up UI components.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -66,6 +82,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes the ViewModel for product list, categories, and loading status.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getProducts().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getProducts().observe(getViewLifecycleOwner(), list -> {
|
||||||
productList.clear();
|
productList.clear();
|
||||||
@@ -83,6 +102,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads product data and categories when the fragment resumes.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
@@ -90,23 +112,38 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
viewModel.loadCategories();
|
viewModel.loadCategories();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the filter visibility toggle.
|
||||||
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter,
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter,
|
||||||
binding.etSearchProduct, binding.spinnerCategory);
|
binding.etSearchProduct, binding.spinnerCategory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the search input listener.
|
||||||
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchProduct, () -> loadProductData(true));
|
UIUtils.attachSearch(binding.etSearchProduct, () -> loadProductData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the category filter spinner.
|
||||||
|
*/
|
||||||
private void setupCategoryFilter() {
|
private void setupCategoryFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerCategory, () -> loadProductData(true));
|
SpinnerUtils.setupFilterSpinner(binding.spinnerCategory, () -> loadProductData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the swipe-to-refresh layout.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshProduct.setOnRefreshListener(() -> loadProductData(true));
|
binding.swipeRefreshProduct.setOnRefreshListener(() -> loadProductData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads product data based on current filters and search query.
|
||||||
|
*/
|
||||||
private void loadProductData(boolean reset) {
|
private void loadProductData(boolean reset) {
|
||||||
String query = binding.etSearchProduct.getText().toString().trim();
|
String query = binding.etSearchProduct.getText().toString().trim();
|
||||||
if (query.isEmpty()) query = null;
|
if (query.isEmpty()) query = null;
|
||||||
@@ -120,6 +157,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
viewModel.loadProducts(reset, query, categoryId);
|
viewModel.loadProducts(reset, query, categoryId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the RecyclerView and its scroll listener for pagination.
|
||||||
|
*/
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new ProductAdapter(productList, this);
|
adapter = new ProductAdapter(productList, this);
|
||||||
adapter.setBaseUrl(baseUrl);
|
adapter.setBaseUrl(baseUrl);
|
||||||
@@ -143,6 +183,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the product detail screen.
|
||||||
|
*/
|
||||||
private void openProductDetails(int position) {
|
private void openProductDetails(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
@@ -152,11 +195,17 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_product_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_product_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles product item clicks by opening details.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onProductClick(int position) {
|
public void onProductClick(int position) {
|
||||||
openProductDetails(position);
|
openProductDetails(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding reference.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Fragment for browsing product-supplier links.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -29,6 +36,9 @@ import java.util.List;
|
|||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and managing the relationships between products and suppliers.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class ProductSupplierFragment extends Fragment
|
public class ProductSupplierFragment extends Fragment
|
||||||
implements ProductSupplierAdapter.OnProductSupplierClickListener {
|
implements ProductSupplierAdapter.OnProductSupplierClickListener {
|
||||||
@@ -40,12 +50,18 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
private ProductSupplierListViewModel viewModel;
|
private ProductSupplierListViewModel viewModel;
|
||||||
private BulkDeleteHandler bulkDeleteHandler;
|
private BulkDeleteHandler bulkDeleteHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewModel.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(ProductSupplierListViewModel.class);
|
viewModel = new ViewModelProvider(this).get(ProductSupplierListViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout and sets up UI components, filters, and observers.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -67,6 +83,9 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes the ViewModel for product-supplier list, products, suppliers, and loading status.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getProductSuppliers().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getProductSuppliers().observe(getViewLifecycleOwner(), list -> {
|
||||||
psList.clear();
|
psList.clear();
|
||||||
@@ -89,6 +108,9 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the bulk delete handler for multiple product-supplier relationship deletion.
|
||||||
|
*/
|
||||||
private void setupBulkDelete() {
|
private void setupBulkDelete() {
|
||||||
bulkDeleteHandler = new BulkDeleteHandler(
|
bulkDeleteHandler = new BulkDeleteHandler(
|
||||||
this,
|
this,
|
||||||
@@ -102,6 +124,9 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads data and filter options when the fragment resumes.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
@@ -109,17 +134,26 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
viewModel.loadFilterData();
|
viewModel.loadFilterData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding reference.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the filter visibility toggle.
|
||||||
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPS,
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPS,
|
||||||
binding.spinnerProduct, binding.spinnerSupplier);
|
binding.spinnerProduct, binding.spinnerSupplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the RecyclerView and its scroll listener for pagination.
|
||||||
|
*/
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new ProductSupplierAdapter(psList, this);
|
adapter = new ProductSupplierAdapter(psList, this);
|
||||||
binding.recyclerViewPS.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewPS.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
@@ -142,22 +176,37 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the search input listener.
|
||||||
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchPS, () -> loadData(true));
|
UIUtils.attachSearch(binding.etSearchPS, () -> loadData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the product filter spinner.
|
||||||
|
*/
|
||||||
private void setupProductFilter() {
|
private void setupProductFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerProduct, () -> loadData(true));
|
SpinnerUtils.setupFilterSpinner(binding.spinnerProduct, () -> loadData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the supplier filter spinner.
|
||||||
|
*/
|
||||||
private void setupSupplierFilter() {
|
private void setupSupplierFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerSupplier, () -> loadData(true));
|
SpinnerUtils.setupFilterSpinner(binding.spinnerSupplier, () -> loadData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the swipe-to-refresh layout.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshPS.setOnRefreshListener(() -> loadData(true));
|
binding.swipeRefreshPS.setOnRefreshListener(() -> loadData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads product-supplier data based on current search query and filters.
|
||||||
|
*/
|
||||||
private void loadData(boolean reset) {
|
private void loadData(boolean reset) {
|
||||||
String query = binding.etSearchPS.getText().toString().trim();
|
String query = binding.etSearchPS.getText().toString().trim();
|
||||||
if (query.isEmpty()) query = null;
|
if (query.isEmpty()) query = null;
|
||||||
@@ -177,6 +226,9 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
viewModel.loadProductSuppliers(reset, query, productId, supplierId);
|
viewModel.loadProductSuppliers(reset, query, productId, supplierId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the product-supplier detail screen.
|
||||||
|
*/
|
||||||
private void openDetail(int position) {
|
private void openDetail(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
@@ -187,9 +239,15 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_product_supplier_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_product_supplier_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles product-supplier item clicks by opening details.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onProductSupplierClick(int position) { openDetail(position); }
|
public void onProductSupplierClick(int position) { openDetail(position); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forwards selection changes to the bulk delete handler.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onSelectionChanged(int count) {
|
public void onSelectionChanged(int count) {
|
||||||
if (bulkDeleteHandler != null) {
|
if (bulkDeleteHandler != null) {
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Fragment for browsing purchase orders.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -30,6 +37,9 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and managing a list of purchase orders.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class PurchaseOrderFragment extends Fragment
|
public class PurchaseOrderFragment extends Fragment
|
||||||
implements PurchaseOrderAdapter.OnPurchaseOrderClickListener {
|
implements PurchaseOrderAdapter.OnPurchaseOrderClickListener {
|
||||||
@@ -41,12 +51,18 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
|
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewModel.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(PurchaseOrderListViewModel.class);
|
viewModel = new ViewModelProvider(this).get(PurchaseOrderListViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout and sets up UI components, filters, and observers.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -64,6 +80,9 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes the ViewModel for purchase order list, stores, and loading status.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getPurchaseOrders().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getPurchaseOrders().observe(getViewLifecycleOwner(), list -> {
|
||||||
poList.clear();
|
poList.clear();
|
||||||
@@ -81,6 +100,9 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads data and stores if necessary when the fragment resumes.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
@@ -88,6 +110,9 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
if (!isStaff()) viewModel.loadStores();
|
if (!isStaff()) viewModel.loadStores();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the filter visibility toggle
|
||||||
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
if (isStaff()) {
|
if (isStaff()) {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPO);
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPO);
|
||||||
@@ -97,18 +122,30 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the currently logged-in user has the STAFF role.
|
||||||
|
*/
|
||||||
private boolean isStaff() {
|
private boolean isStaff() {
|
||||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the search input listener.
|
||||||
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchPO, () -> loadData(true));
|
UIUtils.attachSearch(binding.etSearchPO, () -> loadData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the store filter spinner.
|
||||||
|
*/
|
||||||
private void setupStoreFilter() {
|
private void setupStoreFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadData(true));
|
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the RecyclerView and its scroll listener for pagination.
|
||||||
|
*/
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new PurchaseOrderAdapter(poList, this);
|
adapter = new PurchaseOrderAdapter(poList, this);
|
||||||
binding.recyclerViewPO.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewPO.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
@@ -131,10 +168,16 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the swipe-to-refresh layout.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshPO.setOnRefreshListener(() -> loadData(true));
|
binding.swipeRefreshPO.setOnRefreshListener(() -> loadData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads purchase order data based on current search query and store filter.
|
||||||
|
*/
|
||||||
private void loadData(boolean reset) {
|
private void loadData(boolean reset) {
|
||||||
String query = binding.etSearchPO != null ? binding.etSearchPO.getText().toString().trim() : "";
|
String query = binding.etSearchPO != null ? binding.etSearchPO.getText().toString().trim() : "";
|
||||||
if (query.isEmpty()) query = null;
|
if (query.isEmpty()) query = null;
|
||||||
@@ -153,6 +196,9 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
viewModel.loadPurchaseOrders(reset, query, storeId);
|
viewModel.loadPurchaseOrders(reset, query, storeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the purchase order detail screen.
|
||||||
|
*/
|
||||||
private void openDetail(int position) {
|
private void openDetail(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
PurchaseOrderDTO po = poList.get(position);
|
PurchaseOrderDTO po = poList.get(position);
|
||||||
@@ -160,11 +206,17 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_purchase_order_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_purchase_order_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles purchase order item clicks by opening details.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onPurchaseOrderClick(int position) {
|
public void onPurchaseOrderClick(int position) {
|
||||||
openDetail(position);
|
openDetail(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding reference.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Fragment for browsing sales.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -31,6 +38,9 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and managing a list of sales.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickListener {
|
public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickListener {
|
||||||
|
|
||||||
@@ -41,6 +51,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
|
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout for this fragment.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -48,6 +61,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes UI components and observers after the view is created.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
@@ -79,6 +95,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_refund));
|
NavHostFragment.findNavController(this).navigate(R.id.nav_refund));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes LiveData from the ViewModel to update the list and filter options.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getSales().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getSales().observe(getViewLifecycleOwner(), list -> {
|
||||||
saleList.clear();
|
saleList.clear();
|
||||||
@@ -101,6 +120,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes lookup data when the fragment is resumed.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
@@ -108,6 +130,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
viewModel.loadCustomers();
|
viewModel.loadCustomers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the visibility of filters.
|
||||||
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
if (isStaff()) {
|
if (isStaff()) {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSale,
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSale,
|
||||||
@@ -119,32 +144,53 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current user has the 'STAFF' role.
|
||||||
|
*/
|
||||||
private boolean isStaff() {
|
private boolean isStaff() {
|
||||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current user has the 'ADMIN' role.
|
||||||
|
*/
|
||||||
private boolean isAdmin() {
|
private boolean isAdmin() {
|
||||||
return "ADMIN".equalsIgnoreCase(tokenManager.getRole());
|
return "ADMIN".equalsIgnoreCase(tokenManager.getRole());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the store filter spinner.
|
||||||
|
*/
|
||||||
private void setupStoreFilter() {
|
private void setupStoreFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadSales(true));
|
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadSales(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the payment method filter spinner.
|
||||||
|
*/
|
||||||
private void setupPaymentMethodFilter() {
|
private void setupPaymentMethodFilter() {
|
||||||
String[] paymentMethods = {"All Payments", "Cash", "Card"};
|
String[] paymentMethods = {"All Payments", "Cash", "Card"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerPaymentMethod, paymentMethods, () -> loadSales(true));
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerPaymentMethod, paymentMethods, () -> loadSales(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the refund status filter spinner.
|
||||||
|
*/
|
||||||
private void setupRefundStatusFilter() {
|
private void setupRefundStatusFilter() {
|
||||||
String[] refundStatuses = {"All Status", "Sale", "Refund"};
|
String[] refundStatuses = {"All Status", "Sale", "Refund"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerRefundStatus, refundStatuses, () -> loadSales(true));
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerRefundStatus, refundStatuses, () -> loadSales(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the customer filter spinner.
|
||||||
|
*/
|
||||||
private void setupCustomerFilter() {
|
private void setupCustomerFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerCustomer, () -> loadSales(true));
|
SpinnerUtils.setupFilterSpinner(binding.spinnerCustomer, () -> loadSales(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the RecyclerView and its scroll listener for pagination.
|
||||||
|
*/
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new SaleAdapter(saleList, this);
|
adapter = new SaleAdapter(saleList, this);
|
||||||
binding.recyclerViewSales.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewSales.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
@@ -168,14 +214,23 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches search functionality to the search input field.
|
||||||
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchSale, () -> loadSales(true));
|
UIUtils.attachSearch(binding.etSearchSale, () -> loadSales(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the swipe-to-refresh layout.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshSale.setOnRefreshListener(() -> loadSales(true));
|
binding.swipeRefreshSale.setOnRefreshListener(() -> loadSales(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers loading of sale data from the backend with current filters.
|
||||||
|
*/
|
||||||
private void loadSales(boolean reset) {
|
private void loadSales(boolean reset) {
|
||||||
String query = binding.etSearchSale != null ? binding.etSearchSale.getText().toString().trim() : "";
|
String query = binding.etSearchSale != null ? binding.etSearchSale.getText().toString().trim() : "";
|
||||||
if (query.isEmpty()) query = null;
|
if (query.isEmpty()) query = null;
|
||||||
@@ -210,6 +265,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
viewModel.loadSales(reset, query, paymentMethod, storeId, isRefund, customerId);
|
viewModel.loadSales(reset, query, paymentMethod, storeId, isRefund, customerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles clicks on individual sales in the list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onSaleClick(int position) {
|
public void onSaleClick(int position) {
|
||||||
if (position < 0 || position >= saleList.size()) return;
|
if (position < 0 || position >= saleList.size()) return;
|
||||||
@@ -225,6 +283,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_sale_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_sale_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding when the view is destroyed.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Fragment for browsing services.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -39,12 +46,18 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
private ServiceListViewModel viewModel;
|
private ServiceListViewModel viewModel;
|
||||||
private BulkDeleteHandler bulkDeleteHandler;
|
private BulkDeleteHandler bulkDeleteHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewModel.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(ServiceListViewModel.class);
|
viewModel = new ViewModelProvider(this).get(ServiceListViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout and sets up UI components and observers.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -67,6 +80,9 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes the ViewModel for service list updates and loading status.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getServices().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getServices().observe(getViewLifecycleOwner(), list -> {
|
||||||
serviceList.clear();
|
serviceList.clear();
|
||||||
@@ -79,6 +95,9 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the bulk delete handler for multiple service deletion.
|
||||||
|
*/
|
||||||
private void setupBulkDelete() {
|
private void setupBulkDelete() {
|
||||||
bulkDeleteHandler = new BulkDeleteHandler(
|
bulkDeleteHandler = new BulkDeleteHandler(
|
||||||
this,
|
this,
|
||||||
@@ -92,20 +111,32 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding reference.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the filter visibility toggle.
|
||||||
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchService);
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the search input listener.
|
||||||
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchService, () -> loadServices(true));
|
UIUtils.attachSearch(binding.etSearchService, () -> loadServices(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the RecyclerView and its scroll listener for pagination.
|
||||||
|
*/
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new ServiceAdapter(serviceList, this);
|
adapter = new ServiceAdapter(serviceList, this);
|
||||||
binding.recyclerViewServices.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewServices.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
@@ -128,16 +159,25 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the swipe-to-refresh layout.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshService.setOnRefreshListener(() -> loadServices(true));
|
binding.swipeRefreshService.setOnRefreshListener(() -> loadServices(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads service data based on current filters and search query.
|
||||||
|
*/
|
||||||
private void loadServices(boolean reset) {
|
private void loadServices(boolean reset) {
|
||||||
String query = binding.etSearchService.getText().toString().trim();
|
String query = binding.etSearchService.getText().toString().trim();
|
||||||
if (query.isEmpty()) query = null;
|
if (query.isEmpty()) query = null;
|
||||||
viewModel.loadServices(reset, query);
|
viewModel.loadServices(reset, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the service detail screen.
|
||||||
|
*/
|
||||||
private void openDetail(ServiceDTO service) {
|
private void openDetail(ServiceDTO service) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (service != null) {
|
if (service != null) {
|
||||||
@@ -146,6 +186,9 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_service_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_service_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles service item clicks by opening details.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onServiceClick(int position) {
|
public void onServiceClick(int position) {
|
||||||
if (position >= 0 && position < serviceList.size()) {
|
if (position >= 0 && position < serviceList.size()) {
|
||||||
@@ -153,6 +196,9 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forwards selection changes to the bulk delete handler.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onSelectionChanged(int count) {
|
public void onSelectionChanged(int count) {
|
||||||
if (bulkDeleteHandler != null) {
|
if (bulkDeleteHandler != null) {
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Fragment for browsing staff members.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -24,6 +31,9 @@ import java.util.*;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and managing a list of staff members.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmployeeClickListener {
|
public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmployeeClickListener {
|
||||||
|
|
||||||
@@ -35,6 +45,9 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
|
|||||||
@Inject @Named("baseUrl") String baseUrl;
|
@Inject @Named("baseUrl") String baseUrl;
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout and initializes UI components, filters, and observers.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -60,6 +73,9 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes LiveData from the ViewModel to update the list and filter options.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getFilteredEmployees().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getFilteredEmployees().observe(getViewLifecycleOwner(), list -> {
|
||||||
staffList.clear();
|
staffList.clear();
|
||||||
@@ -77,6 +93,9 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the RecyclerView and its scroll listener for pagination.
|
||||||
|
*/
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new EmployeeAdapter(staffList, this);
|
adapter = new EmployeeAdapter(staffList, this);
|
||||||
adapter.setBaseUrl(baseUrl);
|
adapter.setBaseUrl(baseUrl);
|
||||||
@@ -101,19 +120,31 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the status filter spinner.
|
||||||
|
*/
|
||||||
private void setupStatusFilter() {
|
private void setupStatusFilter() {
|
||||||
String[] statuses = {"All Statuses", "Active", "Inactive"};
|
String[] statuses = {"All Statuses", "Active", "Inactive"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusStaff, statuses, this::applyFilters);
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusStaff, statuses, this::applyFilters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the store filter spinner.
|
||||||
|
*/
|
||||||
private void setupStoreFilter() {
|
private void setupStoreFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStoreStaff, this::applyFilters);
|
SpinnerUtils.setupFilterSpinner(binding.spinnerStoreStaff, this::applyFilters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches search functionality to the search input field.
|
||||||
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchStaff, this::applyFilters);
|
UIUtils.attachSearch(binding.etSearchStaff, this::applyFilters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the selected filters and triggers a data reload from the view model.
|
||||||
|
*/
|
||||||
private void applyFilters() {
|
private void applyFilters() {
|
||||||
String query = binding.etSearchStaff.getText().toString().trim();
|
String query = binding.etSearchStaff.getText().toString().trim();
|
||||||
String status = binding.spinnerStatusStaff.getSelectedItem() != null ?
|
String status = binding.spinnerStatusStaff.getSelectedItem() != null ?
|
||||||
@@ -128,10 +159,16 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
|
|||||||
viewModel.filter(query, storeId, status);
|
viewModel.filter(query, storeId, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the swipe-to-refresh layout.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshStaff.setOnRefreshListener(() -> viewModel.loadStaff(true));
|
binding.swipeRefreshStaff.setOnRefreshListener(() -> viewModel.loadStaff(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the staff detail screen for adding or editing an employee.
|
||||||
|
*/
|
||||||
private void openDetail(int position) {
|
private void openDetail(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
@@ -149,11 +186,17 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_staff_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_staff_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles clicks on individual staff members in the list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onEmployeeClick(int position) {
|
public void onEmployeeClick(int position) {
|
||||||
openDetail(position);
|
openDetail(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding when the view is destroyed.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Fragment for browsing suppliers.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -27,6 +34,9 @@ import java.util.List;
|
|||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and managing a list of suppliers.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupplierClickListener {
|
public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupplierClickListener {
|
||||||
|
|
||||||
@@ -36,12 +46,18 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
private SupplierListViewModel viewModel;
|
private SupplierListViewModel viewModel;
|
||||||
private BulkDeleteHandler bulkDeleteHandler;
|
private BulkDeleteHandler bulkDeleteHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ViewModel.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(SupplierListViewModel.class);
|
viewModel = new ViewModelProvider(this).get(SupplierListViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout and sets up UI components, bulk delete, and observers.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -63,6 +79,9 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes the ViewModel for supplier list updates and loading status.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getSuppliers().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getSuppliers().observe(getViewLifecycleOwner(), list -> {
|
||||||
supplierList.clear();
|
supplierList.clear();
|
||||||
@@ -75,6 +94,9 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the bulk delete handler for multiple supplier deletion.
|
||||||
|
*/
|
||||||
private void setupBulkDelete() {
|
private void setupBulkDelete() {
|
||||||
bulkDeleteHandler = new BulkDeleteHandler(
|
bulkDeleteHandler = new BulkDeleteHandler(
|
||||||
this,
|
this,
|
||||||
@@ -88,24 +110,39 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding reference.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the filter visibility toggle.
|
||||||
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSupplier);
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSupplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the search input listener.
|
||||||
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchSupplier, () -> loadSupplierData(true));
|
UIUtils.attachSearch(binding.etSearchSupplier, () -> loadSupplierData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the swipe-to-refresh layout.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshSupplier.setOnRefreshListener(() -> loadSupplierData(true));
|
binding.swipeRefreshSupplier.setOnRefreshListener(() -> loadSupplierData(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the supplier detail screen.
|
||||||
|
*/
|
||||||
private void openSupplierDetails(int position) {
|
private void openSupplierDetails(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
@@ -115,11 +152,17 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_supplier_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_supplier_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles supplier item clicks by opening details.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onSupplierClick(int position) {
|
public void onSupplierClick(int position) {
|
||||||
openSupplierDetails(position);
|
openSupplierDetails(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forwards selection changes to the bulk delete handler.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onSelectionChanged(int count) {
|
public void onSelectionChanged(int count) {
|
||||||
if (bulkDeleteHandler != null) {
|
if (bulkDeleteHandler != null) {
|
||||||
@@ -127,12 +170,18 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads supplier data based on current search query.
|
||||||
|
*/
|
||||||
private void loadSupplierData(boolean reset) {
|
private void loadSupplierData(boolean reset) {
|
||||||
String query = binding.etSearchSupplier != null ? binding.etSearchSupplier.getText().toString().trim() : "";
|
String query = binding.etSearchSupplier != null ? binding.etSearchSupplier.getText().toString().trim() : "";
|
||||||
if (query.isEmpty()) query = null;
|
if (query.isEmpty()) query = null;
|
||||||
viewModel.loadSuppliers(reset, query);
|
viewModel.loadSuppliers(reset, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the RecyclerView and its scroll listener for pagination.
|
||||||
|
*/
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new SupplierAdapter(supplierList, this);
|
adapter = new SupplierAdapter(supplierList, this);
|
||||||
binding.recyclerViewSuppliers.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewSuppliers.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Detail screen for viewing and editing a single adoption.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -39,12 +46,18 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
|
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the fragment and its ViewModel.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(AdoptionDetailViewModel.class);
|
viewModel = new ViewModelProvider(this).get(AdoptionDetailViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout for the fragment
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -52,6 +65,9 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up UI components, observers, and loads initial data after the view is created.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
@@ -68,6 +84,9 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
binding.btnDeleteAdoption.setOnClickListener(v -> confirmDelete());
|
binding.btnDeleteAdoption.setOnClickListener(v -> confirmDelete());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up observers for ViewModel to update the UI dynamically.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState);
|
viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState);
|
||||||
|
|
||||||
@@ -113,18 +132,27 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows or hides the loading bar.
|
||||||
|
*/
|
||||||
private void setLoading(boolean loading) {
|
private void setLoading(boolean loading) {
|
||||||
if (binding != null) {
|
if (binding != null) {
|
||||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding reference.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the spinners with data and selection listeners.
|
||||||
|
*/
|
||||||
private void setupSpinners() {
|
private void setupSpinners() {
|
||||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAdoptionStatus, new String[]{});
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAdoptionStatus, new String[]{});
|
||||||
|
|
||||||
@@ -141,11 +169,17 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerAdoptionStatus, p -> notifyDateStatusChange());
|
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerAdoptionStatus, p -> notifyDateStatusChange());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the date picker for the adoption date field.
|
||||||
|
*/
|
||||||
private void setupDatePicker() {
|
private void setupDatePicker() {
|
||||||
binding.etAdoptionDate.setOnClickListener(v ->
|
binding.etAdoptionDate.setOnClickListener(v ->
|
||||||
UIUtils.showDatePicker(requireContext(), binding.etAdoptionDate, this::notifyDateStatusChange));
|
UIUtils.showDatePicker(requireContext(), binding.etAdoptionDate, this::notifyDateStatusChange));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the ViewModel when the date or status changes to update available options.
|
||||||
|
*/
|
||||||
private void notifyDateStatusChange() {
|
private void notifyDateStatusChange() {
|
||||||
if (isUpdatingUI) return;
|
if (isUpdatingUI) return;
|
||||||
String date = binding.etAdoptionDate.getText().toString();
|
String date = binding.etAdoptionDate.getText().toString();
|
||||||
@@ -154,6 +188,9 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
viewModel.onDateChanged(date, status);
|
viewModel.onDateChanged(date, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses fragment arguments to determine if we are editing an existing record.
|
||||||
|
*/
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle a = getArguments();
|
Bundle a = getArguments();
|
||||||
if (a != null && a.containsKey("adoptionId")) {
|
if (a != null && a.containsKey("adoptionId")) {
|
||||||
@@ -164,6 +201,9 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
viewModel.setAdoptionId(-1);
|
viewModel.setAdoptionId(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the adoption data from the backend.
|
||||||
|
*/
|
||||||
private void loadAdoptionData() {
|
private void loadAdoptionData() {
|
||||||
viewModel.loadAdoption().observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadAdoption().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
@@ -174,6 +214,10 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the current ViewState to the UI elements.
|
||||||
|
* This handles enabling/disabling views and setting text/selections based on state.
|
||||||
|
*/
|
||||||
private void applyViewState(AdoptionDetailViewModel.ViewState state) {
|
private void applyViewState(AdoptionDetailViewModel.ViewState state) {
|
||||||
isUpdatingUI = true;
|
isUpdatingUI = true;
|
||||||
|
|
||||||
@@ -224,10 +268,16 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
isUpdatingUI = false;
|
isUpdatingUI = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the currently logged-in user has the STAFF role.
|
||||||
|
*/
|
||||||
private boolean isStaff() {
|
private boolean isStaff() {
|
||||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates input and saves the adoption record.
|
||||||
|
*/
|
||||||
private void saveAdoption() {
|
private void saveAdoption() {
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionCustomer, "Customer")) return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionCustomer, "Customer")) return;
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionPet, "Pet")) return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionPet, "Pet")) return;
|
||||||
@@ -275,6 +325,9 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a confirmation dialog before deleting the adoption record.
|
||||||
|
*/
|
||||||
private void confirmDelete() {
|
private void confirmDelete() {
|
||||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Adoption Record", () ->
|
DialogUtils.showDeleteConfirmDialog(requireContext(), "Adoption Record", () ->
|
||||||
viewModel.deleteAdoption().observe(getViewLifecycleOwner(), resource -> {
|
viewModel.deleteAdoption().observe(getViewLifecycleOwner(), resource -> {
|
||||||
@@ -289,6 +342,9 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates back to the previous screen.
|
||||||
|
*/
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Detail screen for viewing and editing a single appointment.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -47,18 +54,27 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
|
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the fragment and its ViewModel.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(AppointmentDetailViewModel.class);
|
viewModel = new ViewModelProvider(this).get(AppointmentDetailViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout for the fragment
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
binding = FragmentAppointmentDetailBinding.inflate(inflater, container, false);
|
binding = FragmentAppointmentDetailBinding.inflate(inflater, container, false);
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up UI components, observers, and loads initial data after the view is created.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
@@ -73,12 +89,18 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
binding.btnDeleteAppointment.setOnClickListener(v -> confirmDelete());
|
binding.btnDeleteAppointment.setOnClickListener(v -> confirmDelete());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding reference.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the spinners with data and selection listeners.
|
||||||
|
*/
|
||||||
private void setupSpinners() {
|
private void setupSpinners() {
|
||||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus, new String[]{});
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus, new String[]{});
|
||||||
|
|
||||||
@@ -108,11 +130,17 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerAppointmentStatus, p -> notifyDateTimeStatusChange());
|
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerAppointmentStatus, p -> notifyDateTimeStatusChange());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the date picker for the appointment date field.
|
||||||
|
*/
|
||||||
private void setupDatePicker() {
|
private void setupDatePicker() {
|
||||||
binding.etAppointmentDate.setOnClickListener(v ->
|
binding.etAppointmentDate.setOnClickListener(v ->
|
||||||
UIUtils.showDatePicker(requireContext(), binding.etAppointmentDate, this::notifyDateTimeStatusChange));
|
UIUtils.showDatePicker(requireContext(), binding.etAppointmentDate, this::notifyDateTimeStatusChange));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up observers for ViewModel to update the UI dynamically.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState);
|
viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState);
|
||||||
|
|
||||||
@@ -147,12 +175,19 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows or hides the loading bar.
|
||||||
|
*/
|
||||||
private void setLoading(boolean loading) {
|
private void setLoading(boolean loading) {
|
||||||
if (binding != null && binding.progressBar != null) {
|
if (binding != null && binding.progressBar != null) {
|
||||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the current ViewState to the UI elements.
|
||||||
|
* This handles enabling/disabling views and setting text/selections based on state.
|
||||||
|
*/
|
||||||
private void applyViewState(AppointmentDetailViewModel.ViewState state) {
|
private void applyViewState(AppointmentDetailViewModel.ViewState state) {
|
||||||
isUpdatingUI = true;
|
isUpdatingUI = true;
|
||||||
|
|
||||||
@@ -201,10 +236,16 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
isUpdatingUI = false;
|
isUpdatingUI = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the currently logged-in user has the STAFF role.
|
||||||
|
*/
|
||||||
private boolean isStaff() {
|
private boolean isStaff() {
|
||||||
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the ViewModel when the date, time, or status changes to update available options.
|
||||||
|
*/
|
||||||
private void notifyDateTimeStatusChange() {
|
private void notifyDateTimeStatusChange() {
|
||||||
if (isUpdatingUI) return;
|
if (isUpdatingUI) return;
|
||||||
|
|
||||||
@@ -215,6 +256,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
viewModel.onDateOrTimeChanged(date, time, status);
|
viewModel.onDateOrTimeChanged(date, time, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses fragment arguments to determine if we are editing an existing record.
|
||||||
|
*/
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle a = getArguments();
|
Bundle a = getArguments();
|
||||||
if (a != null && a.containsKey("appointmentId")) {
|
if (a != null && a.containsKey("appointmentId")) {
|
||||||
@@ -225,6 +269,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the appointment data from the backend.
|
||||||
|
*/
|
||||||
private void loadAppointmentData() {
|
private void loadAppointmentData() {
|
||||||
viewModel.loadAppointment().observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadAppointment().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
@@ -244,6 +291,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates input and saves the appointment record.
|
||||||
|
*/
|
||||||
private void saveAppointment() {
|
private void saveAppointment() {
|
||||||
if (!validateRequiredFields()) return;
|
if (!validateRequiredFields()) return;
|
||||||
|
|
||||||
@@ -275,6 +325,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that all required fields are selected or filled.
|
||||||
|
*/
|
||||||
private boolean validateRequiredFields() {
|
private boolean validateRequiredFields() {
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Customer")) return false;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Customer")) return false;
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return false;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return false;
|
||||||
@@ -284,15 +337,24 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the selected hour and minute from spinners into a time string.
|
||||||
|
*/
|
||||||
private String buildTimeString() {
|
private String buildTimeString() {
|
||||||
return DateTimeUtils.formatTime(HOURS[binding.spinnerHour.getSelectedItemPosition()], MINUTES[binding.spinnerMinute.getSelectedItemPosition()]);
|
return DateTimeUtils.formatTime(HOURS[binding.spinnerHour.getSelectedItemPosition()], MINUTES[binding.spinnerMinute.getSelectedItemPosition()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles errors that occur during the save process.
|
||||||
|
*/
|
||||||
private void handleSaveError(String errorMessage) {
|
private void handleSaveError(String errorMessage) {
|
||||||
if (errorMessage != null && errorMessage.toLowerCase().contains("not available")) showNoAvailabilityDialog();
|
if (errorMessage != null && errorMessage.toLowerCase().contains("not available")) showNoAvailabilityDialog();
|
||||||
else Toast.makeText(getContext(), errorMessage != null ? errorMessage : "Error saving", Toast.LENGTH_SHORT).show();
|
else Toast.makeText(getContext(), errorMessage != null ? errorMessage : "Error saving", Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a dialog when the selected time slot is not available.
|
||||||
|
*/
|
||||||
private void showNoAvailabilityDialog() {
|
private void showNoAvailabilityDialog() {
|
||||||
new androidx.appcompat.app.AlertDialog.Builder(requireContext())
|
new androidx.appcompat.app.AlertDialog.Builder(requireContext())
|
||||||
.setTitle("No Availability")
|
.setTitle("No Availability")
|
||||||
@@ -301,6 +363,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
.setNegativeButton("Cancel Booking", (d, w) -> navigateBack()).show();
|
.setNegativeButton("Cancel Booking", (d, w) -> navigateBack()).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a confirmation dialog before deleting the appointment record.
|
||||||
|
*/
|
||||||
private void confirmDelete() {
|
private void confirmDelete() {
|
||||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Appointment", () ->
|
DialogUtils.showDeleteConfirmDialog(requireContext(), "Appointment", () ->
|
||||||
viewModel.deleteAppointment().observe(getViewLifecycleOwner(), resource -> {
|
viewModel.deleteAppointment().observe(getViewLifecycleOwner(), resource -> {
|
||||||
@@ -310,10 +375,16 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates back to the previous screen.
|
||||||
|
*/
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a time string and sets the hour and minute spinners accordingly.
|
||||||
|
*/
|
||||||
private void parseAndSetTimeSpinners(String time) {
|
private void parseAndSetTimeSpinners(String time) {
|
||||||
int[] parsedTime = DateTimeUtils.parseTimeString(time);
|
int[] parsedTime = DateTimeUtils.parseTimeString(time);
|
||||||
if (parsedTime == null) return;
|
if (parsedTime == null) return;
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Detail screen for viewing and editing a single coupon.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -29,12 +36,18 @@ import java.util.Locale;
|
|||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and editing coupon details.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class CouponDetailFragment extends Fragment {
|
public class CouponDetailFragment extends Fragment {
|
||||||
private FragmentCouponDetailBinding binding;
|
private FragmentCouponDetailBinding binding;
|
||||||
private CouponDetailViewModel viewModel;
|
private CouponDetailViewModel viewModel;
|
||||||
private long couponId = -1;
|
private long couponId = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout, initializes ViewModel, and sets up UI components.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -74,10 +87,16 @@ public class CouponDetailFragment extends Fragment {
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the discount type spinner with options.
|
||||||
|
*/
|
||||||
private void setupDiscountTypeSpinner() {
|
private void setupDiscountTypeSpinner() {
|
||||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerDiscountTypeDetail, new String[]{"FIXED", "PERCENT"});
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerDiscountTypeDetail, new String[]{"FIXED", "PERCENT"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures a date picker for an EditText field.
|
||||||
|
*/
|
||||||
private void setupDatePicker(android.widget.EditText editText, android.widget.EditText dependOn, Runnable onDateSet) {
|
private void setupDatePicker(android.widget.EditText editText, android.widget.EditText dependOn, Runnable onDateSet) {
|
||||||
editText.setFocusable(false);
|
editText.setFocusable(false);
|
||||||
editText.setClickable(true);
|
editText.setClickable(true);
|
||||||
@@ -91,6 +110,9 @@ public class CouponDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads coupon details from the backend and populates the UI.
|
||||||
|
*/
|
||||||
private void loadCouponDetails() {
|
private void loadCouponDetails() {
|
||||||
binding.tvCouponId.setText(DateTimeUtils.formatId(couponId));
|
binding.tvCouponId.setText(DateTimeUtils.formatId(couponId));
|
||||||
binding.tvCouponId.setVisibility(View.VISIBLE);
|
binding.tvCouponId.setVisibility(View.VISIBLE);
|
||||||
@@ -111,6 +133,9 @@ public class CouponDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates input and saves the coupon record.
|
||||||
|
*/
|
||||||
private void saveCoupon() {
|
private void saveCoupon() {
|
||||||
if (!InputValidator.isNotEmpty(binding.etCouponCodeDetail, "Coupon Code")) return;
|
if (!InputValidator.isNotEmpty(binding.etCouponCodeDetail, "Coupon Code")) return;
|
||||||
if (!InputValidator.isGreaterThanZero(binding.etDiscountValueDetail, "Discount Value")) return;
|
if (!InputValidator.isGreaterThanZero(binding.etDiscountValueDetail, "Discount Value")) return;
|
||||||
@@ -156,6 +181,9 @@ public class CouponDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a confirmation dialog before deleting the coupon.
|
||||||
|
*/
|
||||||
private void confirmDelete() {
|
private void confirmDelete() {
|
||||||
new AlertDialog.Builder(requireContext())
|
new AlertDialog.Builder(requireContext())
|
||||||
.setTitle("Delete Coupon")
|
.setTitle("Delete Coupon")
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Detail screen for viewing and editing a single customer.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -21,6 +28,9 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and editing customer details.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class CustomerDetailFragment extends Fragment {
|
public class CustomerDetailFragment extends Fragment {
|
||||||
|
|
||||||
@@ -31,6 +41,9 @@ public class CustomerDetailFragment extends Fragment {
|
|||||||
|
|
||||||
private final String[] STATUSES = {"Active", "Inactive"};
|
private final String[] STATUSES = {"Active", "Inactive"};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout, initializes ViewModel, and sets up UI components.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -49,10 +62,16 @@ public class CustomerDetailFragment extends Fragment {
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the spinners with data.
|
||||||
|
*/
|
||||||
private void setupSpinners() {
|
private void setupSpinners() {
|
||||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerCustomerStatus, STATUSES);
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerCustomerStatus, STATUSES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses fragment arguments to determine if we are editing an existing record.
|
||||||
|
*/
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle a = getArguments();
|
Bundle a = getArguments();
|
||||||
if (a != null && a.getBoolean("isEditing", false)) {
|
if (a != null && a.getBoolean("isEditing", false)) {
|
||||||
@@ -86,6 +105,9 @@ public class CustomerDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads customer data from the backend.
|
||||||
|
*/
|
||||||
private void loadCustomerData(long id) {
|
private void loadCustomerData(long id) {
|
||||||
viewModel.loadCustomer(id).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadCustomer(id).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null) {
|
if (resource != null) {
|
||||||
@@ -105,12 +127,18 @@ public class CustomerDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows or hides the loading bar.
|
||||||
|
*/
|
||||||
private void setLoading(boolean loading) {
|
private void setLoading(boolean loading) {
|
||||||
if (binding != null && binding.progressBar != null) {
|
if (binding != null && binding.progressBar != null) {
|
||||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates input and saves the customer record.
|
||||||
|
*/
|
||||||
private void save() {
|
private void save() {
|
||||||
if (!InputValidator.isNotEmpty(binding.etCustomerUsername, "Username")) return;
|
if (!InputValidator.isNotEmpty(binding.etCustomerUsername, "Username")) return;
|
||||||
|
|
||||||
@@ -160,6 +188,9 @@ public class CustomerDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a confirmation dialog before deleting the customer.
|
||||||
|
*/
|
||||||
private void confirmDelete() {
|
private void confirmDelete() {
|
||||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Customer", () ->
|
DialogUtils.showDeleteConfirmDialog(requireContext(), "Customer", () ->
|
||||||
viewModel.deleteCustomer().observe(getViewLifecycleOwner(), resource -> {
|
viewModel.deleteCustomer().observe(getViewLifecycleOwner(), resource -> {
|
||||||
@@ -175,10 +206,16 @@ public class CustomerDetailFragment extends Fragment {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates back to the previous screen.
|
||||||
|
*/
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding reference.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Detail screen for viewing and editing a single inventory item.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -37,12 +44,18 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
private long preselectedStoreId = -1;
|
private long preselectedStoreId = -1;
|
||||||
private long preselectedProductId = -1;
|
private long preselectedProductId = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the fragment and its ViewModel.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(InventoryDetailViewModel.class);
|
viewModel = new ViewModelProvider(this).get(InventoryDetailViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout for the fragment.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -50,6 +63,9 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up UI components, observers, and loads initial data after the view is created.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
@@ -63,23 +79,35 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
binding.btnDeleteInventory.setOnClickListener(v -> confirmDelete());
|
binding.btnDeleteInventory.setOnClickListener(v -> confirmDelete());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up observers for ViewModel to update the UI dynamically.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> refreshStoreSpinner());
|
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> refreshStoreSpinner());
|
||||||
viewModel.getProductList().observe(getViewLifecycleOwner(), list -> refreshProductSpinner());
|
viewModel.getProductList().observe(getViewLifecycleOwner(), list -> refreshProductSpinner());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows or hides the loading bar.
|
||||||
|
*/
|
||||||
private void setLoading(boolean loading) {
|
private void setLoading(boolean loading) {
|
||||||
if (binding != null && binding.progressBar != null) {
|
if (binding != null && binding.progressBar != null) {
|
||||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding reference.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads initial data for the store and product spinners.
|
||||||
|
*/
|
||||||
private void loadSpinnersData() {
|
private void loadSpinnersData() {
|
||||||
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
@@ -97,18 +125,27 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the store spinner with the current data.
|
||||||
|
*/
|
||||||
private void refreshStoreSpinner() {
|
private void refreshStoreSpinner() {
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryStore, viewModel.getStoreList().getValue(),
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryStore, viewModel.getStoreList().getValue(),
|
||||||
DropdownDTO::getLabel, "-- Select Store --",
|
DropdownDTO::getLabel, "-- Select Store --",
|
||||||
preselectedStoreId, DropdownDTO::getId);
|
preselectedStoreId, DropdownDTO::getId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the product spinner with the current data.
|
||||||
|
*/
|
||||||
private void refreshProductSpinner() {
|
private void refreshProductSpinner() {
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryProduct, viewModel.getProductList().getValue(),
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryProduct, viewModel.getProductList().getValue(),
|
||||||
DropdownDTO::getLabel, "-- Select Product --",
|
DropdownDTO::getLabel, "-- Select Product --",
|
||||||
preselectedProductId, DropdownDTO::getId);
|
preselectedProductId, DropdownDTO::getId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses fragment arguments to determine if we are editing an existing record.
|
||||||
|
*/
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle args = getArguments();
|
Bundle args = getArguments();
|
||||||
if (args != null && args.containsKey("inventoryId")) {
|
if (args != null && args.containsKey("inventoryId")) {
|
||||||
@@ -131,6 +168,9 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the inventory data from the backend.
|
||||||
|
*/
|
||||||
private void loadInventoryData() {
|
private void loadInventoryData() {
|
||||||
viewModel.loadInventory().observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadInventory().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
@@ -149,6 +189,9 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates input and saves the inventory record.
|
||||||
|
*/
|
||||||
private void saveInventory() {
|
private void saveInventory() {
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerInventoryStore, "Store")) return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerInventoryStore, "Store")) return;
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerInventoryProduct, "Product")) return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerInventoryProduct, "Product")) return;
|
||||||
@@ -176,10 +219,16 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a confirmation dialog before deleting the inventory record.
|
||||||
|
*/
|
||||||
private void confirmDelete() {
|
private void confirmDelete() {
|
||||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Inventory Item", this::deleteInventory);
|
DialogUtils.showDeleteConfirmDialog(requireContext(), "Inventory Item", this::deleteInventory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls the ViewModel to delete the inventory record.
|
||||||
|
*/
|
||||||
private void deleteInventory() {
|
private void deleteInventory() {
|
||||||
setButtonsEnabled(false);
|
setButtonsEnabled(false);
|
||||||
viewModel.deleteInventory().observe(getViewLifecycleOwner(), resource -> {
|
viewModel.deleteInventory().observe(getViewLifecycleOwner(), resource -> {
|
||||||
@@ -197,10 +246,16 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates back to the previous screen.
|
||||||
|
*/
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the action buttons.
|
||||||
|
*/
|
||||||
private void setButtonsEnabled(boolean enabled) {
|
private void setButtonsEnabled(boolean enabled) {
|
||||||
UIUtils.setViewsEnabled(enabled, binding.btnSaveInventory, binding.btnDeleteInventory, binding.btnInventoryBack);
|
UIUtils.setViewsEnabled(enabled, binding.btnSaveInventory, binding.btnDeleteInventory, binding.btnInventoryBack);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Detail screen for viewing and editing a single pet.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* Detail screen for viewing and editing a single product.
|
||||||
|
*
|
||||||
|
* Author: Alex, Nikitha
|
||||||
|
* Date: April 2026
|
||||||
|
*/
|
||||||
|
|
||||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@@ -58,6 +65,9 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
@Inject @Named("baseUrl") String baseUrl;
|
@Inject @Named("baseUrl") String baseUrl;
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the fragment, its ViewModel, and the ImagePickerHelper.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -88,6 +98,9 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout for the fragment.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -95,6 +108,9 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up UI components, observers, and handles arguments after the view is created.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
@@ -108,6 +124,9 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
binding.ivProductImage.setOnClickListener(v -> imagePickerHelper.showImagePickerDialog("Select Product Image", hasImage));
|
binding.ivProductImage.setOnClickListener(v -> imagePickerHelper.showImagePickerDialog("Select Product Image", hasImage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up observers for the ViewModel and loads product categories.
|
||||||
|
*/
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getCategoryList().observe(getViewLifecycleOwner(), list -> updateCategorySpinner());
|
viewModel.getCategoryList().observe(getViewLifecycleOwner(), list -> updateCategorySpinner());
|
||||||
|
|
||||||
@@ -120,24 +139,36 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows or hides the loading bar.
|
||||||
|
*/
|
||||||
private void setLoading(boolean loading) {
|
private void setLoading(boolean loading) {
|
||||||
if (binding != null && binding.progressBar != null) {
|
if (binding != null && binding.progressBar != null) {
|
||||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates the category spinner with available categories.
|
||||||
|
*/
|
||||||
private void updateCategorySpinner() {
|
private void updateCategorySpinner() {
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, viewModel.getCategoryList().getValue(),
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, viewModel.getCategoryList().getValue(),
|
||||||
DropdownDTO::getLabel, "-- Select Category --",
|
DropdownDTO::getLabel, "-- Select Category --",
|
||||||
preselectedCategoryId, DropdownDTO::getId);
|
preselectedCategoryId, DropdownDTO::getId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the binding reference.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses fragment arguments to determine if we are editing an existing record.
|
||||||
|
*/
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle a = getArguments();
|
Bundle a = getArguments();
|
||||||
if (a != null && a.containsKey("prodId")) {
|
if (a != null && a.containsKey("prodId")) {
|
||||||
@@ -158,6 +189,9 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the product details from the backend.
|
||||||
|
*/
|
||||||
private void loadProductData() {
|
private void loadProductData() {
|
||||||
viewModel.loadProduct().observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadProduct().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
@@ -175,6 +209,9 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the product image from the server.
|
||||||
|
*/
|
||||||
private void loadProductImage() {
|
private void loadProductImage() {
|
||||||
String imageUrl = baseUrl + String.format(Locale.US, ProductApi.PRODUCT_IMAGE_PATH, viewModel.getProdId());
|
String imageUrl = baseUrl + String.format(Locale.US, ProductApi.PRODUCT_IMAGE_PATH, viewModel.getProdId());
|
||||||
String token = tokenManager.getToken();
|
String token = tokenManager.getToken();
|
||||||
@@ -192,6 +229,9 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs image-related actions (removal or upload) after a successful product save.
|
||||||
|
*/
|
||||||
private void performPendingImageActions(String successMsg) {
|
private void performPendingImageActions(String successMsg) {
|
||||||
if (isImageRemoved) {
|
if (isImageRemoved) {
|
||||||
viewModel.deleteProductImage().observe(getViewLifecycleOwner(), resource -> {
|
viewModel.deleteProductImage().observe(getViewLifecycleOwner(), resource -> {
|
||||||
@@ -214,6 +254,9 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads the selected product image to the server.
|
||||||
|
*/
|
||||||
private void uploadProductImageAndNavigate(Uri uri, String successMsg) {
|
private void uploadProductImageAndNavigate(Uri uri, String successMsg) {
|
||||||
File file = FileUtils.getFileFromUri(requireContext(), uri);
|
File file = FileUtils.getFileFromUri(requireContext(), uri);
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
@@ -239,6 +282,9 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates input and saves the product record.
|
||||||
|
*/
|
||||||
private void saveProduct() {
|
private void saveProduct() {
|
||||||
if (!InputValidator.isNotEmpty(binding.etProductName, "Product Name")) return;
|
if (!InputValidator.isNotEmpty(binding.etProductName, "Product Name")) return;
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerProductCategory, "Category")) return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerProductCategory, "Category")) return;
|
||||||
@@ -267,6 +313,9 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a confirmation dialog before deleting the product.
|
||||||
|
*/
|
||||||
private void confirmDelete() {
|
private void confirmDelete() {
|
||||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Product", () ->
|
DialogUtils.showDeleteConfirmDialog(requireContext(), "Product", () ->
|
||||||
viewModel.deleteProduct().observe(getViewLifecycleOwner(), resource -> {
|
viewModel.deleteProduct().observe(getViewLifecycleOwner(), resource -> {
|
||||||
@@ -280,6 +329,9 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates back to the previous screen.
|
||||||
|
*/
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user