636 Commits

Author SHA1 Message Date
061eccd87f fill gaps in env templates 2026-04-21 18:44:11 -06:00
68055ba720 document backend switching 2026-04-21 18:41:24 -06:00
e0505cbaaa accept email on login 2026-04-21 17:48:10 -06:00
8786cae3f8 fix product search matching descriptions 2026-04-21 12:32:00 -06:00
489543ffca fix tester-reported bugs 2026-04-21 11:09:18 -06:00
6631ad8e1f Fix seed product names 2026-04-20 22:22:15 -06:00
b4cef51a91 add web author headers 2026-04-20 22:01:45 -06:00
46c59336f4 Revert "add web file headers"
This reverts commit c4086c8072.
2026-04-20 22:01:15 -06:00
c4086c8072 add web file headers 2026-04-20 22:00:29 -06:00
97acb7e17f comment android app 2026-04-20 21:52:02 -06:00
329053bfb9 Merge pull request #338 from RecentRunner/final
merge final branch
2026-04-20 21:33:19 -06:00
Nikitha
d0f8445848 Updated PetDetailModelView
Added Comments
2026-04-20 20:48:37 -06:00
18016888ce clarify activity log checkbox 2026-04-20 20:26:47 -06:00
bbedb8af8c remove staff role from UI 2026-04-20 20:20:46 -06:00
f90cc4eb9b Merge pull request #337 from RecentRunner/web-lastfixes
web last fixes
2026-04-20 19:31:55 -06:00
d2f6a5fe82 Merge pull request #336 from RecentRunner/commenting-backend-desktop
comment backend and desktop
2026-04-20 19:19:40 -06:00
augmentedpotato
2cb0a94bbb Comments, appointments adjustments, fixed some issues 2026-04-20 19:19:30 -06:00
80ee62fb24 comment desktop models and utils 2026-04-20 17:51:03 -06:00
ca4375379e comment desktop api layer 2026-04-20 17:06:27 -06:00
049e142845 comment desktop controllers 2026-04-20 16:22:39 -06:00
3a155b6c03 comment backend events and utils 2026-04-20 15:43:11 -06:00
0f35da597f comment backend DTOs 2026-04-20 15:18:54 -06:00
447b5fc346 comment backend entities and repos 2026-04-20 14:24:33 -06:00
24e3d5ab80 comment backend controllers 2026-04-20 13:47:08 -06:00
3c6382318b add service file headers 2026-04-20 13:03:21 -06:00
e26239ae85 add service file headers 2026-04-20 12:38:42 -06:00
9c94ba41fb comment security and config 2026-04-20 12:02:17 -06:00
b6f8131b2e delayed refresh after CRUD 2026-04-20 11:24:43 -06:00
bbbfaaa31d Merge pull request #335 from RecentRunner/web-lastfixes
web last fixes
2026-04-20 11:23:59 -06:00
b4c9940013 add XSS content filter to DTOs 2026-04-20 10:45:45 -06:00
ea73ab687b auto-refresh product list 2026-04-20 10:41:49 -06:00
dd9ea67d90 validate pet price and species 2026-04-20 10:26:01 -06:00
43a298723a evict cache on logout 2026-04-20 10:10:49 -06:00
789c1c3172 fix remaining staff roles 2026-04-20 09:48:53 -06:00
639da61802 title-case all DB strings 2026-04-20 09:42:21 -06:00
7977380c16 auto-complete scheduled appointments 2026-04-20 08:24:28 -06:00
c346c9036f catch sort query exceptions 2026-04-20 08:15:59 -06:00
ef7384515d handle missing exception types 2026-04-20 08:02:45 -06:00
ecbcab8e31 fix store imageUrl length 2026-04-20 06:28:08 -06:00
9353749899 drop status from seed data 2026-04-20 06:16:56 -06:00
8c0c705dd8 idempotent schema indexes 2026-04-20 06:02:05 -06:00
164f738776 fix flyway baseline config 2026-04-20 05:53:49 -06:00
6e1dfdb79f add flyway baseline config 2026-04-20 05:45:10 -06:00
augmentedpotato
d3b9c51952 Minor tweaks, changed checkout UI 2026-04-20 05:41:36 -06:00
0f937f12f5 clean flyway config 2026-04-20 05:36:52 -06:00
88e4718f2e fix HQL pet query 2026-04-20 05:10:13 -06:00
Alex
0f5ac1f1f4 Merge branch 'AttachmentsToChat' 2026-04-19 20:19:57 -06:00
Alex
8aeccb0cab added comments to android 2026-04-19 20:18:28 -06:00
446d1bb7ae configurable rate limiter 2026-04-19 19:05:07 -06:00
ebb9b77025 add client run instructions 2026-04-19 19:04:13 -06:00
75651a6bd3 add project README 2026-04-19 18:56:58 -06:00
e409e4684d fix profile placeholder flash 2026-04-19 18:44:08 -06:00
ce0a79196c fix validation bugs 2026-04-19 18:26:16 -06:00
f5ebab5d01 fix seed data gaps 2026-04-19 17:57:22 -06:00
58a48215da flatten flyway migrations 2026-04-19 17:42:30 -06:00
11618f0cfd fix read state tracking 2026-04-19 17:33:48 -06:00
002f58f24e force logout on 401 2026-04-19 17:12:29 -06:00
augmentedpotato
a717837bd4 Replaced emoticons 2026-04-19 13:33:52 -06:00
augmentedpotato
a0eb6aa1f9 Plethora of changes 2026-04-19 10:33:17 -06:00
augmentedpotato
357fac2d56 Mobile UI for ai chat, fixed backend issue 2026-04-19 08:25:14 -06:00
d30f4b7add fix closed conversation messages 2026-04-18 20:36:46 -06:00
07c3219bec show store selector always 2026-04-18 16:36:15 -06:00
da74491d55 Merge pull request #326 from RecentRunner/web-refactor
web styling refactor
2026-04-18 16:23:00 -06:00
augmentedpotato
077d147498 Styling refactor 2026-04-18 16:22:38 -06:00
049c39984b Merge pull request #325 from RecentRunner/fix/postman-and-clients
fix postman and clients
2026-04-18 16:19:22 -06:00
4f7eeb9def fix cross-client consistency 2026-04-18 15:45:00 -06:00
3cdcc95656 fix postman collection 2026-04-18 14:15:00 -06:00
dc0d929eaf Merge pull request #324 from RecentRunner/refactor/backend-cleanup
backend DRY/KISS cleanup
2026-04-18 08:23:37 -06:00
f5430a1940 fix compatibility regressions 2026-04-17 18:52:00 -06:00
e2b9ae6e0c extract image delete to storage 2026-04-17 18:24:00 -06:00
99768ec9b9 add read-only transactional annotations 2026-04-17 17:58:00 -06:00
1ce390e528 simplify controllers and utilities 2026-04-17 17:35:00 -06:00
4d96d1961c inject AuthenticationHelper bean 2026-04-17 17:08:00 -06:00
18030d5d2e standardize CRUD services 2026-04-17 16:40:00 -06:00
4f73da1218 centralize StringUtils usage 2026-04-17 16:15:00 -06:00
ee0a643636 clean remaining code smells 2026-04-17 15:48:00 -06:00
287e71f2a9 fix review findings 2026-04-17 15:12:00 -06:00
d198fb3d42 externalize business constants 2026-04-17 14:43:00 -06:00
80df6116ab consolidate shared constants 2026-04-17 14:18:00 -06:00
f7c1ff453f use shared StringUtils.trimToNull 2026-04-17 13:54:00 -06:00
4162e34a5f unify error handling 2026-04-17 13:31:00 -06:00
46bdd5c3d7 fix tests and silent failures 2026-04-17 13:02:00 -06:00
a6056c11e4 fix appointment cancellation 2026-04-16 09:45:21 -06:00
be4932661d hide chat history button 2026-04-16 09:05:28 -06:00
87233a2240 fix navbar clipping 2026-04-16 08:57:24 -06:00
3c4ec9cac9 fix chat session leak 2026-04-16 08:40:38 -06:00
a008b18838 redeploy 2026-04-16 08:25:08 -06:00
c4d5c44a44 Merge pull request #322 from RecentRunner/android-desktop-parity
fix six app bugs
2026-04-16 08:13:06 -06:00
50f55ce8c0 merge main into branch 2026-04-16 08:12:46 -06:00
d26c963ee8 normalize pet status casing 2026-04-16 07:58:46 -06:00
2c9dedb65e guard stale init effects 2026-04-16 07:56:57 -06:00
7b4874b8b1 fix six app bugs 2026-04-16 07:55:13 -06:00
eda4549c36 Add V9 sales seed 2026-04-16 07:23:04 -06:00
4ca57c92f1 ai greets first with full context 2026-04-16 00:49:58 -06:00
03abba0679 add order history to profile 2026-04-16 00:38:10 -06:00
cd824dcc63 Seed activity logs, fix role filter 2026-04-16 00:34:13 -06:00
8d8ba41edf fix chat scroll behaviour 2026-04-16 00:25:32 -06:00
c09309a47a fix chat ux and ai model 2026-04-16 00:11:08 -06:00
86a1b0f72c fix contact form and appt ui 2026-04-15 23:37:16 -06:00
4f6807b28f Merge pull request #320 from RecentRunner/fix-desktop-launch
Desktop fixes
2026-04-15 23:21:28 -06:00
4e5f221749 restore activity logs endpoint 2026-04-15 23:19:22 -06:00
95b45c2e54 fix contact layout and chat ui 2026-04-15 23:12:23 -06:00
4402d0398f fix chat badge on reply 2026-04-15 23:11:53 -06:00
1972488eb0 pet owner search 2026-04-15 23:03:54 -06:00
73c4bc6cc7 fix about section spacing and text 2026-04-15 22:59:18 -06:00
006023e289 fix adopt search mobile layout 2026-04-15 22:51:39 -06:00
cd7ef12085 fix chat escalation and sidebar 2026-04-15 22:46:49 -06:00
52404422bd fix images and empty space 2026-04-15 22:37:51 -06:00
690c35415b fix nav and pagination 2026-04-15 22:30:30 -06:00
b711635afa fix layout and demo payment 2026-04-15 22:22:18 -06:00
795adacb57 mobile layout fixes (#319) 2026-04-15 22:01:49 -06:00
adcb695d85 fix mobile nav and env examples 2026-04-15 21:34:15 -06:00
b60db151cb fix stripe publishable key 2026-04-15 21:21:37 -06:00
f4cc5d4641 Merge pull request #318 from RecentRunner/fix-customer-ws-subscription
fix customer ws subscription
2026-04-15 21:19:58 -06:00
b08d1d29ae fix customer ws subscription 2026-04-15 20:57:50 -06:00
51829dd833 fix CORS for production 2026-04-15 18:39:00 -06:00
653560ee31 fix websocket backend url 2026-04-15 18:32:38 -06:00
b5a798adcc disable server compression 2026-04-15 18:20:18 -06:00
e7d5765ae1 remove incompatible jackson config 2026-04-15 18:12:16 -06:00
6b11de24b8 fix JPQL pet query field 2026-04-15 18:01:23 -06:00
5e5c79faff fix duplicate spring key 2026-04-15 17:25:31 -06:00
89e6e05e8e replace chat polling with websocket 2026-04-15 17:04:23 -06:00
b2f3bc117d fix UserServiceTest constructor 2026-04-15 16:33:14 -06:00
892d0394af force revision on deploy 2026-04-15 16:31:22 -06:00
726e1ee52d fix yaml and swagger defaults 2026-04-15 16:31:22 -06:00
f50928fef1 perf: azure deployment optimizations 2026-04-15 16:31:22 -06:00
e87bb7bebf species service validation (#317)
* fix species-service validation

* add grooming for hamster, other

* expand reptile and other services
2026-04-15 16:25:14 -06:00
e23f9f9318 hardcode stripe key 2026-04-15 16:24:58 -06:00
feeaed6244 exclude next cache from build 2026-04-15 16:21:27 -06:00
f68559d028 fix stripe key 2026-04-15 16:17:51 -06:00
ee94703773 Merge pull request #316 from RecentRunner/android-desktop-parity
add pet image support
2026-04-15 16:16:14 -06:00
f61a765624 add pet image support 2026-04-15 16:13:50 -06:00
c9904b18a1 fix stripe key 2026-04-15 16:10:38 -06:00
7154a4a7ba fix logo LCP 2026-04-15 16:09:07 -06:00
175fead68a fix nav and color theme 2026-04-15 16:09:07 -06:00
812f197b75 Merge pull request #314 from RecentRunner/bug-fixes
Backend bug fixes
2026-04-15 16:08:23 -06:00
8a9e018031 avatar on staff register (#315) 2026-04-15 16:06:17 -06:00
285da7df05 decouple emails from transactions 2026-04-15 16:03:10 -06:00
9a4039680e Merge pull request #313 from RecentRunner/worktree-fix-refund-idempotency
lock all stateful mutations
2026-04-15 16:02:37 -06:00
186eb36c7e lock all stateful mutations 2026-04-15 16:01:32 -06:00
2077c1b10c user avatar in edit dialogs (#312) 2026-04-15 15:58:46 -06:00
52e333b31c fix validation and 500 bugs 2026-04-15 15:58:34 -06:00
c24d49fa5b fix auth and logic bugs 2026-04-15 15:54:46 -06:00
df5510224b Merge branch 'loyalty-points' 2026-04-15 15:52:13 -06:00
3a38affdff rebuild stripe key 2026-04-15 15:51:06 -06:00
748a4ff866 fix loyalty points display 2026-04-15 15:49:47 -06:00
d7179665d9 lock refunds against duplicates 2026-04-15 15:48:17 -06:00
1ab4df0b81 fix sale and adoption bugs 2026-04-15 15:46:46 -06:00
06da74e193 center navbar links (#311) 2026-04-15 15:44:45 -06:00
59a83e81de Merge pull request #310 from RecentRunner/worktree-fix-cart-sessions
restore cart across devices
2026-04-15 15:43:42 -06:00
7730c0f80a rebuild stripe key 2026-04-15 15:42:59 -06:00
f8f2a4b05e restore cart across devices 2026-04-15 15:42:55 -06:00
5ff5995bcf rebuild stripe key 2026-04-15 15:38:28 -06:00
9c1cbb0ed8 fix stripe payment intent 2026-04-15 15:31:52 -06:00
e975d1d2b0 rebuild frontend 2026-04-15 15:10:22 -06:00
1bea2f808d disable validate on migrate 2026-04-15 14:58:19 -06:00
49685a75f7 fix flyway failed migration 2026-04-15 14:52:05 -06:00
0bfd709f1f update actions node 24 2026-04-15 14:24:39 -06:00
362da2fc06 fix test compilation 2026-04-15 14:20:47 -06:00
77b697ac83 fix coupon analytics 2026-04-15 14:10:24 -06:00
405fa60d61 ignore log files 2026-04-15 14:10:24 -06:00
045701d848 remove log artifacts 2026-04-15 14:10:24 -06:00
3b17f47efe activity logs to files 2026-04-15 14:10:24 -06:00
7926df8987 trim seed data 2026-04-15 14:10:14 -06:00
92660414c9 add seed data 2026-04-15 14:10:14 -06:00
f226e335c2 fix scroll sorting 2026-04-15 14:10:14 -06:00
89fb7554e0 fix deploy commands 2026-04-15 12:40:15 -06:00
076c36bc85 fix CI azure login 2026-04-15 12:36:07 -06:00
08cdb941a4 Merge pull request #309 from RecentRunner/websitefinal
merge websitefinal
2026-04-15 12:26:47 -06:00
d723f3e3cc merge main into websitefinal 2026-04-15 12:26:31 -06:00
15e08834a0 Merge pull request #308 from RecentRunner/azure-deploy
merge azure-deploy
2026-04-15 12:25:46 -06:00
db8499e18d point android app at azure backend 2026-04-15 08:13:53 -06:00
Nikitha
8e1ab89e6c Chat Widget, changes in nav bar
Auto Scroll chat and changes in Nav bar
2026-04-15 08:12:16 -06:00
6046d8720f point desktop app at azure backend 2026-04-15 08:10:17 -06:00
fc0712cc78 fix chat scroll jumps and move about us to home page 2026-04-15 08:07:17 -06:00
3d0e05daf2 fix proxy origin header and cors allowed origins config 2026-04-15 07:37:43 -06:00
839963049d Merge pull request #307 from RecentRunner/web-v2
merge web-v2
2026-04-15 07:07:34 -06:00
ecea09afc6 Merge pull request #306 from RecentRunner/fix-appointment-history
fix appointment history
2026-04-15 07:04:46 -06:00
d40904b5cd fix appointment history 2026-04-15 07:04:22 -06:00
b782e4e62b Merge pull request #305 from RecentRunner/web-fixes
fix appointments and pagination
2026-04-15 06:53:09 -06:00
fb2b070e32 fix appointments and pagination 2026-04-15 06:52:49 -06:00
augmentedpotato
85653af39e Small corrections 2026-04-15 06:39:41 -06:00
augmentedpotato
cdcc50e93a Minor change to appointments page 2026-04-15 02:49:26 -06:00
augmentedpotato
914e0bceda Points now subtract from costs 2026-04-15 02:44:14 -06:00
Alex
7ad35bd2dc Merge branch 'AttachmentsToChat' 2026-04-15 02:15:16 -06:00
Alex
1163961b90 fixed product supplier android 2026-04-15 02:14:20 -06:00
Alex
a2c8df16b7 added filtering to activity logs for desktop 2026-04-15 02:10:20 -06:00
Alex
a23171359f turned logs to laymen terms and added to android 2026-04-15 01:52:35 -06:00
404f162728 Merge pull request #304 from RecentRunner/web-coupons
web coupons
2026-04-15 01:39:43 -06:00
bd7368f0cf Merge pull request #303 from RecentRunner/chat-ui-updates
chat UI updates
2026-04-15 01:37:52 -06:00
fb7d71c86e unify ai and live chat 2026-04-15 01:35:54 -06:00
augmentedpotato
2c1871b6e2 loyalty points 2026-04-15 01:34:49 -06:00
f22a187148 always show chat sidebar 2026-04-15 01:31:29 -06:00
28ccf8b96d Merge pull request #302 from RecentRunner/web-features
fix web chat features
2026-04-15 01:25:47 -06:00
377439495c fix web chat features 2026-04-15 01:25:28 -06:00
Alex
c8a1c29cc3 removed status on purchase order android 2026-04-15 01:09:56 -06:00
3214d3893b fix chat scroll and button 2026-04-15 01:09:09 -06:00
5817f82b77 fix contact form style 2026-04-15 00:56:24 -06:00
ad63a314c9 add store images 2026-04-15 00:55:08 -06:00
augmentedpotato
1636ba14b6 Coupon system working properly 2026-04-15 00:54:46 -06:00
0315920b0d Merge pull request #301 from RecentRunner/web-fixes
web fixes
2026-04-15 00:49:04 -06:00
Alex
4643feb868 added pagenation to android for each fragment 2026-04-15 00:46:32 -06:00
a3eff2e738 contact form with email 2026-04-15 00:46:32 -06:00
2e13c0cea0 web issue fixes 2026-04-15 00:44:07 -06:00
023fdf5ee9 web fixes 2026-04-15 00:38:04 -06:00
7d3cc51c8f Merge branch 'main' of github.com:RecentRunner/group-2-threaded-project-petshop 2026-04-15 00:27:49 -06:00
fe8fdafdd8 Merge pull request #300 from RecentRunner/web-pwreset
web password reset
2026-04-15 00:27:40 -06:00
Alex
024a618473 fixed issue for desktop when sending a file too large 2026-04-15 00:27:06 -06:00
f194d7ca78 Merge branch 'main' of github.com:RecentRunner/group-2-threaded-project-petshop 2026-04-15 00:18:43 -06:00
augmentedpotato
755fa092c2 password reset 2026-04-15 00:17:26 -06:00
8cf12d32d3 merge azure-deploy 2026-04-15 00:14:09 -06:00
Alex
017ef65b5a desktop chat now shows images 2026-04-15 00:00:17 -06:00
934a857cdf document BACKEND_URL swap 2026-04-14 23:46:38 -06:00
6c4fc20870 add desktop backend config 2026-04-14 23:46:21 -06:00
Alex
be697f080e Merge branch 'AttachmentsToChat' 2026-04-14 23:43:39 -06:00
Alex
6235f96def added time stamp and sender name to android chat 2026-04-14 23:42:23 -06:00
Alex
4f6a6f71ed fixed chat messaging on same account with different devices 2026-04-14 23:34:07 -06:00
de37b27fcc Merge remote-tracking branch 'origin/main' into azure-deploy 2026-04-14 23:32:16 -06:00
0b244938cf Merge pull request #299 from RecentRunner/web-v1
Merge web-v1 into main
2026-04-14 23:29:51 -06:00
9cc2da6b92 merge main 2026-04-14 23:29:46 -06:00
Alex
08c8e54b2d Merge branch 'AttachmentsToChat' 2026-04-14 23:20:16 -06:00
3ee259abfd lazy init 2026-04-14 23:19:34 -06:00
Alex
42a4bcd104 made admin analyics able to select store 2026-04-14 23:17:12 -06:00
Alex
ec0d2d1ec7 added personal and store analytics 2026-04-14 23:10:03 -06:00
6aac0c6366 Merge remote-tracking branch 'origin/main' into azure-deploy 2026-04-14 23:06:48 -06:00
1a51f16ee0 show error details 2026-04-14 22:56:54 -06:00
Alex
7340a5616e Changed android phone validation 2026-04-14 22:53:42 -06:00
dea1caafd6 runtime backend proxy 2026-04-14 22:52:01 -06:00
d35c820967 use middleware for runtime backend proxy 2026-04-14 22:48:00 -06:00
Alex
9c47f5ac76 added staff and customer images to desktop 2026-04-14 22:43:24 -06:00
Alex
aca52efc44 maade it so sales display points earned 2026-04-14 22:33:10 -06:00
Alex
6848ab3586 implemented forget password for desktop 2026-04-14 22:15:15 -06:00
Alex
b3547b2971 fixed chat loading issue andriod 2026-04-14 22:08:14 -06:00
Nikitha
c5c5461167 Chat Saving
saving chat history
2026-04-14 22:02:05 -06:00
5b88ee242e fix lowercase image name in workflow 2026-04-14 21:37:16 -06:00
06a6eeff9c trigger CI on azure-deploy branch 2026-04-14 21:35:36 -06:00
9cafc305fd Azure deployment setup 2026-04-14 21:29:00 -06:00
Alex
6382c87d67 Merge branch 'AttachmentsToChat' 2026-04-14 21:15:19 -06:00
Alex
44b7fcbba2 added forget password 2026-04-14 21:13:57 -06:00
77d106cb06 Merge pull request #296 from RecentRunner/easy-fixes
Logs folder and activity log defaults
2026-04-14 20:50:14 -06:00
f8cf68eb1a Logs folder and activity log date default 2026-04-14 20:50:03 -06:00
67d3cb2c7f Merge pull request #280 from RecentRunner/chat-fixes
Fix chat attachments and avatars
2026-04-14 20:26:03 -06:00
8127b539e8 Fix chat attachments and avatars 2026-04-14 20:25:54 -06:00
ea1237942d Merge pull request #278 from RecentRunner/desktop-backend-fixes
Activity log, staff role, chat
2026-04-14 20:17:21 -06:00
9d6d7d885d Activity log filters, staff role, chat fix 2026-04-14 20:17:03 -06:00
86061e2733 Merge pull request #275 from RecentRunner/backend-fixes
Backend bug fixes
2026-04-14 20:03:32 -06:00
923d323808 Block chat injection 2026-04-14 20:03:11 -06:00
933bd6b7fd Add species filtering 2026-04-14 20:03:11 -06:00
0c63963ddf Drop status column 2026-04-14 20:03:11 -06:00
51a48e07eb Fix stuck pet status 2026-04-14 20:03:11 -06:00
3a7621678d Fix backend issues 2026-04-14 20:03:11 -06:00
0a8b96bc3b Merge pull request #274 from RecentRunner/desktop-notifications
desktop chat notifications
2026-04-14 19:59:57 -06:00
d3e203b575 add desktop chat notifications 2026-04-14 19:59:42 -06:00
02768f940a fix backend issues 2026-04-14 19:59:39 -06:00
711c018014 Merge pull request #268 from RecentRunner/fix/admin-account-guard
Harden admin guards
2026-04-14 16:10:10 -06:00
758b44d6d6 Harden admin guards 2026-04-14 16:09:39 -06:00
5964528524 use openrouter/free model 2026-04-14 15:46:42 -06:00
7c3e34e83d Merge pull request #267 from RecentRunner/nullable-appointment-pet
nullable appointment pet
2026-04-14 15:41:18 -06:00
060ecf1ef2 nullable petId in appointment 2026-04-14 15:39:30 -06:00
f0ce6d8c2d Merge pull request #266 from RecentRunner/resend-email
resend email
2026-04-14 15:23:49 -06:00
52b97f435a add rate limiting 2026-04-14 15:23:26 -06:00
39312c8698 add email flows 2026-04-14 15:23:07 -06:00
augmentedpotato
917d318566 Profile image works, editing profile works, now uses first/last name 2026-04-14 15:02:57 -06:00
augmentedpotato
fbcab5d097 Age input when editing/adding pet profile 2026-04-14 13:56:21 -06:00
augmentedpotato
113c8c61be Navbar fixed 2026-04-14 13:44:13 -06:00
augmentedpotato
a4a4831615 Removed addresses, adjusted contact page 2026-04-14 13:31:56 -06:00
augmentedpotato
c2f39c40f0 Fixes for appointments and My Pets fields. 2026-04-14 12:20:48 -06:00
67aadfa66f Updated the example environment file 2026-04-14 10:02:02 -06:00
augmentedpotato
208372c782 Favicon updated 2026-04-14 07:35:06 -06:00
augmentedpotato
1f389ca25c Pet adoption appointments (currently has a small issue) 2026-04-14 07:14:54 -06:00
ceafee2a80 Merge pull request #254 from RecentRunner/web-v1
Web v1
2026-04-14 07:06:21 -06:00
augmentedpotato
7303c22cd3 Chat now present in the bottom right. 2026-04-14 05:59:33 -06:00
augmentedpotato
7bddd74a6e Cart fixes (backend), adjusted header, added footer, mobile formatting updates 2026-04-14 05:24:40 -06:00
Alex
f3fc93d6e5 added filter to analytics desktop 2026-04-14 04:39:38 -06:00
Alex
eefb8de460 added filters to desktop 2026-04-14 04:29:28 -06:00
Alex
f623c17071 added filter by customer for sales to backend and android 2026-04-14 04:03:15 -06:00
Alex
4594139f8e added clendar to adoptions and appointments on desktop 2026-04-14 03:48:46 -06:00
Alex
a860a1c247 updated sales on desktop, and fixed sales with points again on back end 2026-04-14 03:32:29 -06:00
Alex
a3fcebfa15 Merge branch 'main' into AttachmentsToChat 2026-04-14 01:10:14 -06:00
Alex
6c4b3ef120 Merge branch 'main' into AttachmentsToChat 2026-04-14 00:57:16 -06:00
Alex
d4958ec914 fixes to desktop part 1 2026-04-14 00:55:51 -06:00
8e205ebca2 add webp support desktop 2026-04-14 00:17:43 -06:00
cf274f9013 consolidate migrations 2026-04-14 00:11:13 -06:00
3c4743fa70 fix image paths 2026-04-14 00:11:07 -06:00
168ebe94fc add cart points fields 2026-04-14 00:11:02 -06:00
Alex
94344b146f Merge branch 'AttachmentsToChat' 2026-04-13 22:36:53 -06:00
Alex
d898732a17 seperated staff and customer on desktop 2026-04-13 22:15:41 -06:00
2757bc66da Merge pull request #253 from RecentRunner/images
seed images
2026-04-13 21:58:01 -06:00
b115a4b66c localize seed image URLs to upload paths 2026-04-13 21:57:29 -06:00
Alex
c38bb24e94 fixed phone validation desktop 2026-04-13 21:52:53 -06:00
Alex
98584f1324 added dropdowns for breed desktop 2026-04-13 21:40:34 -06:00
Alex
3ef6604884 fixed pet busniss logic desktop 2026-04-13 21:10:35 -06:00
Alex
044e9ba7b2 made it so staff cannot change the status of pets for desktop for adopted or owned 2026-04-13 20:49:22 -06:00
f93f4f576b Merge pull request #252 from RecentRunner/merge-migration
merge migration
2026-04-13 19:48:37 -06:00
1b00366a1e make ActivityLog entity immutable 2026-04-13 19:47:45 -06:00
Alex
572895efa9 added correct refund logic and points for sales 2026-04-13 19:46:52 -06:00
Alex
c244e5742a added points to sale and logic backend 2026-04-13 19:46:28 -06:00
Alex
6efa440bbc added loyaltypoint usage to sales unfinished still needs to work with the backend 2026-04-13 18:52:07 -06:00
0e53b16d6c seed normalization 2026-04-13 18:29:49 -06:00
Alex
884f56c9a7 Can now edit loyalty points for customer on andriod, and pets now have breed dropdown 2026-04-13 18:25:11 -06:00
0e997071c3 Merge pull request #251 from RecentRunner/payment-fixes
Payment safety fixes
2026-04-13 17:52:21 -06:00
68893b1318 Unique sale constraint 2026-04-13 17:49:59 -06:00
a221f2a91b Add payment features 2026-04-13 17:49:59 -06:00
1577ed41cd Add checkout snapshot 2026-04-13 17:49:59 -06:00
Alex
1b4069e7e4 made sales readonly for andriod 2026-04-13 17:25:28 -06:00
Alex
a6f90f8477 Fixed phone validation for andriod 2026-04-13 17:18:46 -06:00
Alex
aefa00f95d added pending status on pets andiord, also made pets automatically switch to pending when an adoption is in pending 2026-04-13 17:12:41 -06:00
Alex
c145b3e552 Merge branch 'AttachmentsToChat' 2026-04-13 15:07:28 -06:00
Alex
c5de2fdd87 added store column to desktop and display only logged in data 2026-04-13 15:06:04 -06:00
ef3318a4ff Merge pull request #172 from RecentRunner/web-adopt-filter
Adopt page filter
2026-04-13 11:08:41 -06:00
39e30aa8d5 Remove XML declaration from misc.xml 2026-04-13 11:08:23 -06:00
augmentedpotato
de5bbbf3f7 Adopt page filter added 2026-04-13 10:34:26 -06:00
Alex
fba042d2b9 fixed rotated image for pets and product as well 2026-04-13 00:30:26 -06:00
Alex
d85530dd2c Fix profile image squish and rotate isusse 2026-04-13 00:07:58 -06:00
Alex
326182aeef added coupons to desktop app 2026-04-12 23:47:22 -06:00
Alex
2172bede74 desktop: added confirmation to change onwer and only admins can change this 2026-04-12 20:01:06 -06:00
Alex
f497251873 added closed chat section and fixed closed chat bug for desktop 2026-04-12 19:49:12 -06:00
Alex
e4e04940a9 Fixed minor bugs
- in sales we can no longer select 0 product for a sale
- ActivityLogFragment is locked to admin
- Spinners are loaded aftrer a selection if the spinner depends on a parent spinner
2026-04-12 18:34:43 -06:00
Alex
b493357f31 adjusted so only available pets for the selected store is displayed when adopting 2026-04-12 18:00:24 -06:00
Alex
4eaf98834d Modified project to use our utils on areas we arnt to manage code 2026-04-12 17:49:24 -06:00
Alex
7cdab36f5d Fixed Log filters and fixed chat attachment download 2026-04-12 17:28:40 -06:00
Alex
6831123aed added Activitylogs andriod 2026-04-12 16:58:12 -06:00
Alex
f7b8648778 Merge branch 'AttachmentsToChat' 2026-04-12 01:09:25 -06:00
Alex
870fa5488a added Forgetpassword page with no logic yet 2026-04-12 01:08:25 -06:00
Alex
3472b4bcd7 Faded text for disabled spinners in staff 2026-04-12 00:51:49 -06:00
Alex
d166ec1b4d staff cant change status or store for pets 2026-04-12 00:49:15 -06:00
Alex
f023077715 Made it so only admins can change pet owners 2026-04-12 00:37:15 -06:00
Alex
96e6cd6dc7 Fixed Coupon to use in sales 2026-04-12 00:25:33 -06:00
Alex
0311887185 Used the wrong endpoint for populating species, changed to to the correct one
also added coupon option to sales
2026-04-11 23:50:22 -06:00
27b15893a7 Merge pull request #171 from RecentRunner/admin-activity-logs
Add activity logging
2026-04-11 23:32:38 -06:00
9b59c5bfe0 restrict activity logging to admin and staff only 2026-04-11 23:29:23 -06:00
62094f2f4f Harden startup config 2026-04-11 23:10:18 -06:00
Alex
8ae47ef056 added petspecies spinner 2026-04-11 23:04:14 -06:00
Alex
d15940a5f2 Made it so staffs can only manage their own store and they cannot see other branches data 2026-04-11 23:02:52 -06:00
a7f2fc5b92 Consolidate log migrations 2026-04-11 22:54:43 -06:00
50c344091f Add log viewer 2026-04-11 22:54:27 -06:00
c69820241f Add activity logging 2026-04-11 22:54:23 -06:00
Alex
26bfd3973c added sort so appointment and adoption display the most recent created first 2026-04-11 22:18:39 -06:00
Alex
c0ebef7e96 fixed bug on appointments where spinners was not populating the correct data 2026-04-11 22:02:06 -06:00
Alex
cd74e5f06f replaced observer in viewmodels to observe only once to fix memleek 2026-04-11 15:46:30 -06:00
Alex
bb62b5d352 Added null checks for Appointment and Adoptions to make sure the spinner can load 2026-04-10 19:49:54 -06:00
Alex
f26c795d46 Added Customer CRUD 2026-04-10 19:34:28 -06:00
d6c4be3acf Merge pull request #170 from RecentRunner/fix-web-pr
Fix web
2026-04-10 12:52:44 -06:00
b888307ce7 Fix web 2026-04-10 09:17:31 -06:00
6b15207ad0 Update postman tests 2026-04-10 09:01:09 -06:00
ce04f16f97 Fix collection 2026-04-10 08:58:44 -06:00
6d7524c859 Ignore .env files 2026-04-10 08:58:44 -06:00
2f705b87f7 Add close chat and closed status 2026-04-10 08:58:44 -06:00
37410b2f7a Sync postman 2026-04-10 08:58:44 -06:00
b043577da0 Merge pull request #168 from RecentRunner/ai-chat-merge
Add AI chat
2026-04-10 08:58:23 -06:00
4cfed522eb Fix duplicate openrouter config 2026-04-10 08:28:43 -06:00
289a404c0a Fix sales UI 2026-04-10 08:20:25 -06:00
08e074607e Clean up OpenRouterService 2026-04-10 08:19:24 -06:00
8de2612b05 Update bot model 2026-04-10 08:18:54 -06:00
fb36a00fbf Fix bot runtime 2026-04-10 08:18:54 -06:00
bb28a8f31d OpenRouter bot fixes 2026-04-10 08:18:54 -06:00
33bf63cb1e Add OpenRouter bot 2026-04-10 08:18:54 -06:00
augmentedpotato
a50fa82a50 Web and AI chat 2026-04-10 08:18:54 -06:00
Alex
b9635cae68 Merge branch 'AttachmentsToChat' 2026-04-10 07:43:24 -06:00
Alex
5ecf322f0c added close chat option to chat 2026-04-10 07:36:54 -06:00
Alex
1e7d56499b fixed staff accounts and added coupons andriod 2026-04-10 07:17:19 -06:00
Alex
dff379c99d Sales bug fix 2026-04-10 05:56:05 -06:00
Alex
79261274f6 added Analytics filter 2026-04-10 05:03:36 -06:00
Alex
3a78021b98 Added so adoption status can be missed and fixed adoption bugs for andriod 2026-04-10 04:31:10 -06:00
Alex
5b1e0ea115 fixed spinners to populate the correct pets in edit mode for adoptions 2026-04-10 02:58:14 -06:00
Alex
57ad824d67 added viewstates to Supplier and Product 2026-04-10 00:28:01 -06:00
Alex
6ece516cc6 Fixed profile issue with camera and added viewstate to pet and service 2026-04-09 23:39:34 -06:00
b1f574b5a4 Merge pull request #164 from RecentRunner/implement-chat-notifications
implement chat notifications
2026-04-09 23:36:36 -06:00
31398fdcac Restore Main Attachments 2026-04-09 23:28:14 -06:00
12aa06f953 Defer Chat Attachments 2026-04-09 23:28:14 -06:00
6b055c4364 Implement chat features 2026-04-09 23:28:14 -06:00
4bd44727a5 Merge pull request #160 from RecentRunner/gui-fixes
Refactor user management
2026-04-09 23:23:57 -06:00
6be0099048 resolve merge conflict 2026-04-09 23:23:39 -06:00
9bfd3a48dc fix table column bindings using lambdas 2026-04-09 23:18:14 -06:00
92b66d7995 fix duplicate refresh in StaffAccountsController 2026-04-09 23:16:24 -06:00
d28fced5b3 Merge pull request #163 from RecentRunner/stripe-payment
Stripe Payments
2026-04-09 23:12:14 -06:00
fa01645d31 move stripe keys to .env 2026-04-09 23:01:41 -06:00
666c7e0ca6 fix user id getter in completeCheckout 2026-04-09 22:57:00 -06:00
39e4a3896e fix stripe payment flow 2026-04-09 22:52:57 -06:00
67b5da131e Merge pull request #162 from RecentRunner/AttachmentsToChat
Attachments to Chat
2026-04-09 22:34:59 -06:00
augmentedpotato
1010d57b79 Stripe Payment 2026-04-09 22:27:03 -06:00
3270971c7f Merge pull request #161 from RecentRunner/table-fixes
Merge Table Fixes
2026-04-09 21:49:18 -06:00
Alex
a8b5ee361e cleaning code 2026-04-09 21:16:11 -06:00
e68e39426b Restore Table Layout 2026-04-09 21:12:04 -06:00
Alex
fea01ba8ec made chat more user frendly 2026-04-09 19:47:43 -06:00
Alex
b9b74d2447 fixed sending message with attachments 2026-04-09 18:55:12 -06:00
56111f3ac7 Space Out Tables 2026-04-09 17:57:01 -06:00
da0c819da5 Add Sales Scroll 2026-04-09 17:39:52 -06:00
Alex
872e3a27f1 added attachments to chat 2026-04-09 17:39:45 -06:00
5cb32114c8 Shrink Sales View 2026-04-09 17:36:31 -06:00
3631201435 Format Appointment Times 2026-04-09 17:29:18 -06:00
fe7e81986d Refine Desktop Pricing 2026-04-09 17:29:03 -06:00
6e21e4fd6c Refine GUI Behavior 2026-04-09 15:51:21 -06:00
Alex
83eda83671 fixed bug again 2026-04-09 15:37:24 -06:00
Alex
01be4a7620 bug fix 2026-04-09 15:24:20 -06:00
Alex
872042de5a deleted unused viewmodels 2026-04-09 15:17:11 -06:00
Alex
6a3730ca04 refactored viewmodels for listfragments 2026-04-09 14:44:04 -06:00
Alex
8559a46cb9 created viewmodels for detailFragments 2026-04-09 14:17:51 -06:00
Alex
67cb178f46 Merge branch 'main' into AttachmentsToChat 2026-04-09 13:44:24 -06:00
54ff97448d Refactor user management 2026-04-09 12:28:33 -06:00
dd1502d2ce Adjust Sales Layout 2026-04-09 11:49:55 -06:00
c82a0efa93 Improve Desktop Tables 2026-04-09 11:49:14 -06:00
43715e05a5 Unify Table Behavior 2026-04-09 11:48:30 -06:00
4dce16f067 Merge pull request #158 from RecentRunner/remove-sidebar-emojis
Remove sidebar emojis
2026-04-09 11:11:28 -06:00
d1ff46844a Remove sidebar emojis 2026-04-09 11:10:59 -06:00
ab0dbec1af Merge pull request #156 from RecentRunner/early-fixes
Merge Early Fixes
2026-04-09 10:39:53 -06:00
Alex
4b8e0b2868 fixed spinner infinite loop in appointments 2026-04-09 02:48:55 -06:00
Alex
8fa74240bc split viewmodels for appointments 2026-04-09 02:07:35 -06:00
Alex
f98abf19ef Moved appointments businiss logic to modelview andriod 2026-04-09 00:55:00 -06:00
Alex
6ebec31f09 small change 2026-04-08 21:30:40 -06:00
Alex
f06f98a657 Appointments should be fully user frendly now 2026-04-08 20:14:52 -06:00
Alex
b6bee250df helper class added to enable and disable fields 2026-04-08 19:57:04 -06:00
Alex
5f9d7a848c updated backend so booked appointment automatically changes to completed 2026-04-08 19:16:18 -06:00
Alex
271314f990 fixing dropdowns 2026-04-08 18:10:18 -06:00
Alex
2bc0ffd47a update dropdowns to use backend dropdown endpoints part 1 2026-04-08 17:34:33 -06:00
Alex
6f11f4ebbb making appointment userfrendly part1 andriod 2026-04-08 16:53:42 -06:00
5734ceca6e Update early fixes 2026-04-08 16:43:50 -06:00
fb7c4c66ef Merge pull request #155 from RecentRunner/AttachmentsToChat
Merged attachments branch
2026-04-08 13:44:13 -06:00
75aec048ae Merge main branch 2026-04-08 13:43:20 -06:00
aa42a53c74 Fix adoption dialog 2026-04-08 11:23:02 -06:00
02184d9cfd Show store dropdown 2026-04-08 11:23:02 -06:00
0695f1d120 Update pet dialog 2026-04-08 11:23:02 -06:00
0f2b94a277 Create adoption sale 2026-04-08 11:23:02 -06:00
b80ffff296 Fix dialog issues 2026-04-08 11:23:02 -06:00
cbdec1882c Fix desktop chat 2026-04-08 11:23:02 -06:00
a7ba0fb4b4 fix empty desktop lists 2026-04-08 08:18:01 -06:00
4845aeb479 restrict adoption pets 2026-04-08 08:11:20 -06:00
55b61d3908 fix desktop chat 2026-04-08 08:07:17 -06:00
808b6e3d2b remove debit payments 2026-04-08 07:57:39 -06:00
56ffecba92 fix desktop forms 2026-04-08 07:55:55 -06:00
548d7629d3 fix web appointments 2026-04-08 07:17:48 -06:00
1fc675a138 Merge branch 'main' into web-more-fixes-for-wednesday 2026-04-08 07:17:26 -06:00
Alex
aeca5e4341 updated sales to have new backend data 2026-04-08 03:19:16 -06:00
Alex
811edf842b converted new fragments to use hilt, MVVM and jetpack nav 2026-04-08 02:22:34 -06:00
Alex
a35b432b54 Converted merged fragments to viewbinding 2026-04-08 02:06:07 -06:00
Alex
627ce7a987 Added hamburger menu helperfunction 2026-04-08 01:50:18 -06:00
Alex
57ac8ad2ce added filtering for Sales and added helper method for setting up filtertoggle andriod 2026-04-08 01:32:34 -06:00
Alex
526650bd98 fix minor bugs and UI inconsistancy 2026-04-08 00:22:25 -06:00
augmentedpotato
21d086a816 Can now add pets in the appointments page. 2026-04-07 23:53:48 -06:00
augmentedpotato
a6b188e0d6 Feature parity with admins and users (also a minor backend change) 2026-04-07 23:23:05 -06:00
augmentedpotato
bd46968b90 Fixed(?) being unable to create appointments on today's date 2026-04-07 22:44:50 -06:00
ef1b7ac716 fix image error responses 2026-04-07 22:41:32 -06:00
b3ff789f1b revert adoption fragment 2026-04-07 22:17:15 -06:00
3baef0e1ab Merge branch 'morefiles' 2026-04-07 21:43:20 -06:00
65140d77ac finish android merge wiring 2026-04-07 21:15:31 -06:00
1de6c981dc fix backend merge conflicts 2026-04-07 21:13:47 -06:00
9ca647f0cc merge origin/main into morefiles, resolve all conflicts 2026-04-07 20:29:54 -06:00
Alex
332f38db57 added filter by date for adoptions to backend 2026-04-07 18:34:08 -06:00
Alex
54ab737e2d added helper method for filter spinners to maintain code 2026-04-07 18:12:02 -06:00
Alex
155c64a729 added adoption search and filter andriod and backend 2026-04-07 18:06:07 -06:00
Alex
538b4440d8 fixed purchase order for android on new backend 2026-04-07 17:31:43 -06:00
Alex
5a3229eb19 Merge branch 'main' into AttachmentsToChat 2026-04-07 16:24:17 -06:00
Alex
c232f193d1 added bulk delete for ProductSupplier, appointments, and adoptions 2026-04-07 16:20:51 -06:00
f17ad44155 fix alias folder ordering and request bodies in Postman collection 2026-04-07 16:16:11 -06:00
4500b213c6 fix audit report mismatches across backend and android 2026-04-07 16:06:44 -06:00
Alex
4ccfe55174 bluk delete added for Service and suppliers on andriod 2026-04-07 15:24:25 -06:00
0173123898 update postman collection 2026-04-07 15:19:25 -06:00
Alex
9eaf64c7a9 added helper class for bulk delete and mad pets have bulk delete 2026-04-07 15:13:15 -06:00
Alex
aa30efd3b6 Did the same to inventory 2026-04-07 14:55:43 -06:00
852a8a0eb2 update web packages 2026-04-07 14:36:20 -06:00
Alex
4e887c1a73 updard Adoptions in andriod for new backend 2026-04-07 14:35:57 -06:00
Alex
108de589bc edited adapters in andriod to use viewbinding 2026-04-07 14:17:24 -06:00
27871d96f5 stabilize desktop chat 2026-04-07 09:38:17 -06:00
c776c579ab fix desktop chat 2026-04-07 09:34:31 -06:00
9096f4fd29 fix desktop user inventory crud 2026-04-07 09:31:26 -06:00
858d13cadc fix desktop adoption save 2026-04-07 09:27:34 -06:00
4d7f452a97 add my pets api 2026-04-07 09:15:01 -06:00
ee2cab953d fix web registration 2026-04-07 09:10:11 -06:00
0655dfdfea fix desktop appointments 2026-04-07 09:05:08 -06:00
Nikitha
c61f71d226 loading employee in appointments and adoptions
changes in backend and android
2026-04-07 08:43:49 -06:00
de654e487b Merge pull request #146 from RecentRunner/AttachmentsToChat
AttachmentsToChat
2026-04-07 08:23:47 -06:00
6d77a98d92 merge main 2026-04-07 08:23:23 -06:00
7e3d094217 update postman collection 2026-04-07 08:17:41 -06:00
Alex
7c0ab0bfae added an api connection to Users in Andriod
NOTE Will have to change backend so staffs can access other staffs
2026-04-07 07:46:22 -06:00
Alex
8c5348dbb6 fixed creating adoption for the backend and implemented adoption to andriod for changes 2026-04-07 07:27:37 -06:00
3c35e05e47 Merge pull request #145 from RecentRunner/AttachmentsToChat
AttachmentsToChat
2026-04-07 06:53:07 -06:00
Alex
77e93a38b7 added my appointments button for logged in user on andriod 2026-04-07 06:48:36 -06:00
Alex
6f646a7cf0 added filter options to appointments in the backend and andriod 2026-04-07 06:34:28 -06:00
Alex
b7f97c45a5 updated Appointments on andriod for new backend 2026-04-07 06:14:17 -06:00
Alex
5c9d04fc88 changed backend so can sortBy productName and added search to productSupplier 2026-04-07 05:48:24 -06:00
Alex
cbd038d8fb Added filter by store for inventory in back end and added search to inventory 2026-04-07 05:24:25 -06:00
Alex
6c832e01f3 updated inventory backend to have filter by store and added more search features to andriod 2026-04-07 05:09:48 -06:00
Alex
1d00a3b55c updated search to call api for supplier 2026-04-07 04:19:51 -06:00
Alex
5823152c56 updated search for service to call api 2026-04-07 04:12:29 -06:00
Alex
3d403ee35e Updated Filterdropdown design for pets 2026-04-07 03:51:09 -06:00
Alex
230f2715ca added more filter options to pets 2026-04-07 03:24:55 -06:00
Alex
6b3979c68f changed filtering and search in pets to use api calls 2026-04-07 02:46:00 -06:00
Alex
003c7ec58a changed petDetailFragment to support new backend 2026-04-07 02:23:58 -06:00
Alex
5cb625a710 fixed pet DTO and how it interacts with new backend 2026-04-07 00:27:17 -06:00
Alex
5a1ff67db4 Fixed backend missing file issue 2026-04-07 00:10:42 -06:00
Alex
870920f67e Merge branch 'main' into AttachmentsToChat 2026-04-06 23:27:47 -06:00
Alex
d9db1f778e Fixed seeding for backend 2026-04-06 23:27:23 -06:00
Nikitha
bb7fbf9f78 Employee files
Add, edit employee (staff and admin)
2026-04-06 22:56:40 -06:00
Nikitha
53246a78c6 Sale, refund and analytics documents
view sale history  and refund for sales
analytics of sale , employee and store performances
2026-04-06 22:55:45 -06:00
5a691a5ddf seed stores and suppliers before products on fresh DB 2026-04-06 21:18:30 -06:00
7a70d163c7 Merge pull request #144 from RecentRunner/backend-refactor
backend-refactor
2026-04-06 21:13:48 -06:00
24a2af0bee fix local seed: add missing categories and storeId in inventory insert 2026-04-06 21:13:17 -06:00
67116d2c89 Merge pull request #143 from RecentRunner/backend-refactor
Fix sale inventory and switch to port 3306
2026-04-06 21:06:08 -06:00
1029e48f42 point to port 3306 Petstoredb 2026-04-06 21:06:01 -06:00
3088573b0b scope inventory lookup by store on sale 2026-04-06 21:01:20 -06:00
ae653027c4 Merge pull request #142 from RecentRunner/backend-refactor
Fix lazy loading
2026-04-06 20:57:44 -06:00
386deff6b8 fix lazy loading on me, services, refunds 2026-04-06 20:56:14 -06:00
2057d5695b Merge pull request #141 from RecentRunner/backend-refactor
Backend refactor
2026-04-06 20:51:44 -06:00
f646c18035 enable Hibernate validation 2026-04-06 20:46:27 -06:00
e291a0e999 add activityLog store FK 2026-04-06 20:39:08 -06:00
3760d61e2e add message attachment fields 2026-04-06 20:38:29 -06:00
c96c3b1dab add service species collection 2026-04-06 20:34:03 -06:00
9f465b25bb add sale channel coupon cart columns 2026-04-06 20:32:05 -06:00
fd3f49255a add coupon cart cartItem entities 2026-04-06 20:30:11 -06:00
88dfdb05a8 add adoption sourceStore FK 2026-04-06 20:28:31 -06:00
c15313ea5f add purchaseOrder store FK 2026-04-06 20:26:42 -06:00
b2ddea8794 add storeLocation imageUrl 2026-04-06 20:25:33 -06:00
6682cdc047 add store dimension to inventory 2026-04-06 20:24:23 -06:00
beff4c5297 simplify appointment to single pet 2026-04-06 20:22:26 -06:00
0570758b07 merge customer/employee into users 2026-04-06 20:17:27 -06:00
a36dee75af expand User entity fields 2026-04-06 19:49:38 -06:00
48f1a9c1ba switch to target DB config 2026-04-06 19:47:18 -06:00
f3a611ad60 Add target DB setup 2026-04-06 19:37:15 -06:00
Alex
4725e11b89 Merge branch 'main' into AttachmentsToChat 2026-04-06 16:29:10 -06:00
d8704c38f1 Fix brittle migrations by replacing hardcoded IDs with robust subqueries 2026-04-06 16:25:45 -06:00
Alex
90628b37d9 Merge branch 'main' into AttachmentsToChat 2026-04-06 16:13:50 -06:00
d839081112 Merge pull request #139 from RecentRunner/pet-owner-store
Pet owner store
2026-04-06 16:12:09 -06:00
9e3e8c835e Seed pets and appointments 2026-04-06 15:50:52 -06:00
35640a1a39 Update pet desktop 2026-04-06 15:50:49 -06:00
9655a77972 Pet owner and store 2026-04-06 15:50:45 -06:00
Alex
6a515f0edc Merge branch 'main' into AttachmentsToChat 2026-04-06 15:39:38 -06:00
d3754c8018 Merge pull request #138 from RecentRunner/employee-phase
Employee phase
2026-04-06 13:37:38 -06:00
6f8c0674c2 Update Postman collection 2026-04-06 13:35:01 -06:00
18407f8328 Fix Flyway migration 2026-04-06 13:35:01 -06:00
Alex
4f0abd2f26 changed detailed fragment to fill data from the backend 2026-04-06 03:12:42 -06:00
d51b1b0ab7 Fix availability checks 2026-04-06 01:51:58 -06:00
d8e2c8c95d Add Missed status 2026-04-06 00:39:37 -06:00
6d4c9a5e65 Fix employee time conflicts 2026-04-06 00:18:49 -06:00
706cd94d14 Allow cross-store staff selection 2026-04-05 23:58:21 -06:00
8d3430bd75 Enforce pet ownership rules 2026-04-05 23:35:05 -06:00
Alex
4b9bf4dff4 Implemented View Binding to reduce code
- project uses view binding now so we don have to do
getViewbyId to refer to the xml
2026-04-05 22:50:25 -06:00
Alex
124a10c619 Edited RetrofitUtils to also call enqueue to reduce code in repository 2026-04-05 21:57:53 -06:00
Alex
eecb695b87 Fixed bug where it navigates back to petprofile after deleting the pet 2026-04-05 21:40:43 -06:00
Alex
38a0242264 Refactored more of the project to MVVM and created helper class RetrofitUtil to reduce redundent code 2026-04-05 21:27:32 -06:00
Alex
693829ce42 Created help class for displaying diolog and removed redundent code 2026-04-05 18:38:46 -06:00
Alex
defd851dbd Created Spinner Helper class and removed reducdent code 2026-04-05 18:15:36 -06:00
Alex
8b2d406433 remove dead code 2026-04-05 17:48:14 -06:00
Alex
65a8475e47 Added Helper class and commented most fragments 2026-04-05 17:16:40 -06:00
bdd5566493 Enforce staff-only assignments 2026-04-05 16:17:58 -06:00
8f635116df Restrict assignments to staff 2026-04-05 16:03:29 -06:00
c84d817810 Allow admin ownership bypass 2026-04-05 16:01:46 -06:00
30b5041ae5 Harden assignment rules 2026-04-05 15:51:11 -06:00
0294f078f9 Harden staff assignment 2026-04-05 12:17:37 -06:00
Alex
0c75ffbf35 Refactored Andriod project to use MVVM structure (Need to apply this so sales too after merge)
- Used MVVM structure so fragments are not doing all the operation from views to data and calls
- organized the structure of our proejct
2026-04-04 23:35:38 -06:00
Alex
47bd755e72 fix photo loading issue on pets and products 2026-04-04 21:21:25 -06:00
Alex
be79de7c82 integrated Jetpack navigation to project so we dont have to manually code the functionallities of loading to different fragments 2026-04-04 20:08:40 -06:00
Alex
e25a02fe1f fixed retrofit client, but will delete this file after merges
- kept class so nothing will break when merge
- then delete after merge buy making other files use Hilt
2026-04-04 18:23:15 -06:00
Alex
ddc8e98c19 integrated hilt so we dont have to manually pass context and inject retrofit in andriod 2026-04-04 18:15:05 -06:00
Alex
4ea76ddab5 Added calendar view to adoptions in andriod 2026-04-04 17:16:44 -06:00
Alex
7bb2d98639 Added enter send message and login for andriod feilds 2026-04-04 16:54:29 -06:00
b7d85053bc Merge pull request #135 from RecentRunner/clean-demo-branch
Protect appointment visibility
2026-04-04 16:28:34 -06:00
dd373d3800 Harden appointment dialog 2026-04-04 16:24:09 -06:00
259770ce69 Fix appointment ownership 2026-04-04 16:24:09 -06:00
ca06f6c8b3 Hide adopted pets 2026-04-04 16:24:09 -06:00
83a0448219 Merge pull request #134 from RecentRunner/main
Update branch
2026-04-04 16:11:26 -06:00
86267ddddf Merge pull request #133 from RecentRunner/AttachmentsToChat
Attachments to chat
2026-04-04 16:10:39 -06:00
Alex
3cc1e93c5b added calendar view to appointments
- NOTE: may have to change appointments abit after backend is updated
2026-04-03 19:37:43 -06:00
cfaee96c3e Merge pull request #108 from RecentRunner/web-more-fixes
Web more fixes
2026-04-03 18:13:18 -06:00
augmentedpotato
cfd5a7c8cd Fix profile images 2026-04-03 15:30:43 -06:00
augmentedpotato
a3e5e6701e Fix pet sorting 2026-04-03 15:21:15 -06:00
augmentedpotato
2a871a4d41 Fix item loading 2026-04-03 15:07:41 -06:00
augmentedpotato
4bd98ef06f Improve auth flows 2026-04-03 14:52:32 -06:00
augmentedpotato
82935303ba Fix web routing 2026-04-03 14:48:24 -06:00
Alex
b7681499ae Added images to products for android
- also added the option to delete the images to profile and pets
2026-04-02 19:21:55 -06:00
Alex
877e0cf0de made it so we can put attachments to chat
- Sending not implemented until backend is complete
2026-04-02 18:23:49 -06:00
3d6b87a7d2 Automate reset cleanup 2026-04-01 20:17:30 -06:00
5ef045165d Remove duplicate migration 2026-04-01 20:08:39 -06:00
488f67289d Integrate refund logic 2026-04-01 19:59:03 -06:00
38a71a8b59 Merge pull request #75 from RecentRunner/fix-features-icons-v2
Finalize feature fixes
2026-04-01 19:31:28 -06:00
054094a61e Add desktop icons 2026-04-01 18:10:20 -06:00
c48039eeb4 Fix phone formatting 2026-04-01 18:08:37 -06:00
e580ed3251 Apply service logic 2026-04-01 16:57:27 -06:00
695c80ba9d Merge migration fixes 2026-04-01 16:55:36 -06:00
bdbcd75042 Revert "Merge pull request #55 from RecentRunner/backend-normalize-users-payments"
This reverts commit aa0950df9b, reversing
changes made to fdd4af6746.
2026-03-30 09:58:02 -06:00
563f8ee691 Fix migration versions 2026-03-30 09:57:46 -06:00
352d7beec1 Merge pull request #60 from RecentRunner/web-products
Web products
2026-03-30 09:51:34 -06:00
d078af520d Merge remote-tracking branch 'origin/main' into web-products 2026-03-30 09:50:57 -06:00
77c5f53532 Merge pull request #59 from RecentRunner/web-index
Web index
2026-03-30 09:48:50 -06:00
06908ffacb Merge pull request #58 from RecentRunner/refund-layout-spacing
Refund polish
2026-03-30 09:41:16 -06:00
9c3f5d4d50 Fix refund display 2026-03-30 09:40:22 -06:00
31a11656c4 Stabilize refunds 2026-03-30 09:17:22 -06:00
07cd4f6c0e Polish sales tables 2026-03-30 09:16:52 -06:00
augmentedpotato
3b8b1e9b97 Appointments, account stuff, adopt a pet changes 2026-03-30 05:38:15 -06:00
8f05f22b23 Fix android backend url 2026-03-30 00:03:27 -06:00
467daa35f0 Merge pull request #57 from RecentRunner/staff-self-analytics
Staff analytics
2026-03-29 23:55:33 -06:00
0f20523b3c Fix staff analytics 2026-03-29 23:50:31 -06:00
a9fc3e3227 Show staff analytics 2026-03-29 23:34:52 -06:00
061275ba30 Scope staff analytics 2026-03-29 23:34:43 -06:00
edea9ef315 Merge remote-tracking branch 'origin/FixedUIConsistancy' 2026-03-29 23:22:43 -06:00
a7162682c4 Merge pull request #56 from RecentRunner/expand-pets-products-data
Expand catalog
2026-03-29 23:08:37 -06:00
5791ddc47d Tighten seed filters 2026-03-29 23:07:16 -06:00
e572d9f3cf Add pet product filters 2026-03-29 22:54:25 -06:00
c48e3b8a95 Expand pet product data 2026-03-29 22:54:16 -06:00
aa0950df9b Merge pull request #55 from RecentRunner/backend-normalize-users-payments
Normalize users
2026-03-29 22:40:13 -06:00
5d3efa0af5 Clean up customer accounts 2026-03-29 22:37:18 -06:00
3fbb108646 Preserve backfill emails 2026-03-29 22:14:53 -06:00
1a0fe7f95d Disable generated user accounts 2026-03-29 22:09:39 -06:00
f6147aa810 Tighten backfill migration 2026-03-29 22:02:44 -06:00
277d1dce8f Tighten user linking 2026-03-29 21:59:43 -06:00
24041f4242 Fix user linking 2026-03-29 21:52:45 -06:00
Alex
26c437261f added pet images to petfragment and changed other views to look consistant 2026-03-29 21:47:49 -06:00
1bab36f727 Remove debit payment data 2026-03-29 21:44:10 -06:00
a727878b0c Backfill user accounts 2026-03-29 21:44:06 -06:00
fdd4af6746 Merge pull request #54 from RecentRunner/backend-fixes-41-49
Fix backend appointments and chat
2026-03-29 21:24:36 -06:00
54b3b1d457 Remove chat close wrapper 2026-03-29 21:14:53 -06:00
9dba49e141 Add appointment tests 2026-03-29 21:07:14 -06:00
df42f68c04 Update chat conversation status 2026-03-29 21:07:10 -06:00
3cb3faa073 Fix appointment overlap rules 2026-03-29 19:02:19 -06:00
c5e9baad5e Add chat close endpoint 2026-03-29 19:02:12 -06:00
32ce987fbc Merge pull request #53 from RecentRunner/nomorebreaking
Merging in Nikitha's work
2026-03-29 18:21:56 -06:00
b8fa6b730f Fix status bar and navigation bar layout 2026-03-29 18:20:10 -06:00
8d419b0dd9 Fix Android app connection and timeout issues
Add proper timeout configuration to OkHttpClient (30s connect/read/write)
Update OkHttp logging-interceptor to 4.12.0 to match OkHttp version
Improve error messages to show server URL for debugging
Configure backend to listen on all interfaces (0.0.0.0)
Remove EdgeToEdge calls that interfered with layout
2026-03-29 17:54:23 -06:00
augmentedpotato
cce97b7509 Merge branch 'web-index' 2026-03-29 17:47:33 -06:00
d3723b8ae5 Merge main into nomorebreaking 2026-03-29 17:07:35 -06:00
a254d88775 Merge pull request #52 from RecentRunner/desktop-pet-product-pictures
Add desktop pet and product images
2026-03-29 16:58:06 -06:00
b3a30e10d1 fix desktop pet and product dialogs 2026-03-29 16:54:01 -06:00
8a278cd6e2 add desktop pet and product images 2026-03-27 10:07:37 -06:00
Alex
f5e031484e Changed android app icon 2026-03-26 22:45:28 -06:00
Alex
1360fa1592 Added petprofile images and uploads in petprofilefragment 2026-03-26 22:07:51 -06:00
Alex
998390c063 Merge branch 'MorePushNotification' 2026-03-26 21:39:38 -06:00
Alex
9f38ca1bcc Make chat notification display messengers name and disable notifying if already in chat view 2026-03-26 21:31:36 -06:00
a3ad1dab8c add pet and product images 2026-03-26 20:36:04 -06:00
Alex
177ac844ee Added push notifications when reciving any message and added filter status on pets in Andriod 2026-03-26 20:13:27 -06:00
Alex
4f02825b96 Added profile photo loading and uploading
- profile photos now load from backend
- profile photos can be uploaded to the backend
- RetrofitClient now automatically determines if the device is an emulator or hardware so we dont have to comment and uncomment everytime we test with a different device
2026-03-26 16:50:02 -06:00
Alex
d31db865cf Added role based access to android login
- Admin has access to everything
- Staff has limited access to what they can edit in listfragment
- Customers cannot login to app
- added validations to pets, supplier and services in their detailed view
2026-03-26 16:50:02 -06:00
5c94880733 use swing picker on wayland 2026-03-25 23:55:40 -06:00
3c9a3bf1b3 readd secure avatar endpoints 2026-03-25 22:58:04 -06:00
46d7b58238 Merge pull request #30 from RecentRunner/WorkingOnProfileAndPushNotification
Working on profile and push notification
2026-03-25 09:18:48 -06:00
29153cb6ee Merge pull request #29 from RecentRunner/desktop---validator-fixes
added null checks to validator, created a bunch of junit tests
2026-03-24 20:43:54 -06:00
augmentedpotato
4736b8bd3f added null checks to validator, created a bunch of junit tests 2026-03-24 20:42:45 -06:00
5b7c19f334 Merge pull request #28 from RecentRunner/web-index
uploading index to repo
2026-03-24 16:59:39 -06:00
601 changed files with 10359 additions and 5576 deletions

245
README.md
View File

@@ -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.

View File

@@ -1,3 +1,9 @@
/*
* Application entry point, sets up Hilt dependency injection.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile;
import android.app.Application;

View File

@@ -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;
import android.os.Bundle;
@@ -32,6 +38,9 @@ public class ForgotPasswordActivity extends AppCompatActivity {
private ActivityForgotPasswordBinding binding;
/**
* Set the content view for forget password page
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
EdgeToEdge.enable(this);
@@ -54,6 +63,10 @@ public class ForgotPasswordActivity extends AppCompatActivity {
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) {
binding.btnSubmit.setEnabled(false);

View File

@@ -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;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
@@ -20,6 +29,7 @@ import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.NavigationUI;
import com.example.petstoremobile.R;
import com.example.petstoremobile.api.auth.TokenManager;
import com.example.petstoremobile.databinding.ActivityHomeBinding;
import com.example.petstoremobile.services.ChatNotificationService;
@@ -30,6 +40,16 @@ public class HomeActivity extends AppCompatActivity {
private ActivityHomeBinding binding;
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
private final ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
@@ -67,18 +87,30 @@ public class HomeActivity extends AppCompatActivity {
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();
requestNotificationPermission();
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(forceLogoutReceiver);
}
/**
* Handles new intents received while the activity is already running (like notifications).
*/
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent); // Set the new intent so fragments can access updated extras
setIntent(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() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {

View File

@@ -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;
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) {
viewModel.login(username, password).observe(this, resource -> {
@@ -112,6 +118,7 @@ public class MainActivity extends AppCompatActivity {
case SUCCESS:
if (resource.data != null) {
String role = resource.data.getRole();
//Check if role is staff/admin or customer
if ("CUSTOMER".equalsIgnoreCase(role)) {
UIUtils.setViewsEnabled(true, binding.btnLogin);
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() {
viewModel.getMe().observe(this, resource -> {

View File

@@ -1,3 +1,10 @@
/*
* Adapter for displaying activity log entries in a list.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.adapters;
import android.view.LayoutInflater;
@@ -25,10 +32,16 @@ public class ActivityLogAdapter extends RecyclerView.Adapter<ActivityLogAdapter.
private final List<ActivityLogDTO> items;
/**
* Constructor for the ActivityLogAdapter.
*/
public ActivityLogAdapter(List<ActivityLogDTO> items) {
this.items = items;
}
/**
* Inflates the layout for an activity log item and creates a new ViewHolder.
*/
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -37,6 +50,9 @@ public class ActivityLogAdapter extends RecyclerView.Adapter<ActivityLogAdapter.
return new ViewHolder(view);
}
/**
* Binds the data from ActivityLogDTO to the items in the ViewHolder.
*/
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
ActivityLogDTO log = items.get(position);
@@ -71,9 +87,15 @@ public class ActivityLogAdapter extends RecyclerView.Adapter<ActivityLogAdapter.
holder.tvTimestamp.setText(formatTimestamp(log.getLogTimestamp()));
}
/**
* Returns the total number of items in the list.
*/
@Override
public int getItemCount() { return items.size(); }
/**
* Formats the timestamp string from the API to a readable date/time format.
*/
private String formatTimestamp(String raw) {
if (raw == null) return "";
try {
@@ -85,6 +107,9 @@ public class ActivityLogAdapter extends RecyclerView.Adapter<ActivityLogAdapter.
}
}
/**
* Format the Role string to be consistent
*/
private String formatRole(String role) {
if (role == null) return "";
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) {
for (String v : values) {
if (v != null && !v.isBlank()) return v;
@@ -102,9 +130,15 @@ public class ActivityLogAdapter extends RecyclerView.Adapter<ActivityLogAdapter.
return "";
}
/**
* ViewHolder class that holds references to the UI components for an activity log item.
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView tvActivity, tvTechnical, tvUser, tvMeta, tvTimestamp;
/**
* Initializes the ViewHolder by finding the views within the item layout.
*/
public ViewHolder(@NonNull View itemView) {
super(itemView);
tvActivity = itemView.findViewById(R.id.tvLogActivity);

View File

@@ -1,3 +1,10 @@
/*
* Adapter for showing adoption records in a list.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.adapters;
import android.graphics.Color;
@@ -23,6 +30,9 @@ public class AdoptionAdapter extends RecyclerView.Adapter<AdoptionAdapter.Adopti
void onSelectionChanged(int count);
}
/**
* Constructor for AdoptionAdapter.
*/
public AdoptionAdapter(List<AdoptionDTO> adoptionList, OnAdoptionClickListener listener) {
this.adoptionList = adoptionList;
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
public List<String> getSelectedKeys() {
return selectionHelper.getSelectedKeys();
}
/**
* Resets the selection state, deselecting all items.
*/
@Override
public void 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
@Override
public AdoptionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -65,6 +84,9 @@ public class AdoptionAdapter extends RecyclerView.Adapter<AdoptionAdapter.Adopti
return new AdoptionViewHolder(binding);
}
/**
* Binds adoption data to the UI components and handles click/long-click logic.
*/
@Override
public void onBindViewHolder(@NonNull AdoptionViewHolder holder, int 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
public int getItemCount() { return adoptionList.size(); }
}

View File

@@ -1,3 +1,10 @@
/*
* Adapter for showing appointments in a list.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.adapters;
import android.graphics.Color;
@@ -23,6 +30,9 @@ public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.
void onSelectionChanged(int count);
}
/**
* Constructor for AppointmentAdapter.
*/
public AppointmentAdapter(List<AppointmentDTO> appointmentList,
OnAppointmentClickListener appointmentClickListener) {
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
public List<String> getSelectedKeys() {
return selectionHelper.getSelectedKeys();
}
/**
* Resets the selection state, deselecting all items.
*/
@Override
public void clearSelection() {
selectionHelper.clearSelection();
}
/**
* ViewHolder class that holds references to the UI components for an appointment item.
*/
public static class AppointmentViewHolder extends RecyclerView.ViewHolder {
private final ItemAppointmentBinding binding;
/**
* Initializes the ViewHolder by finding the views within the item layout.
*/
public AppointmentViewHolder(@NonNull ItemAppointmentBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
/**
* Inflates the layout for an appointment item and creates the ViewHolder.
*/
@NonNull
@Override
public AppointmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -66,6 +91,9 @@ public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.
return new AppointmentViewHolder(binding);
}
/**
* Binds appointment data to the UI components and handles click/long-click logic.
*/
@Override
public void onBindViewHolder(@NonNull AppointmentViewHolder holder, int 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
public int getItemCount() {
return appointmentList.size();

View File

@@ -1,3 +1,10 @@
/*
* Custom array adapter that displays items with black text.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.adapters;
import android.content.Context;
@@ -12,6 +19,7 @@ import com.example.petstoremobile.R;
import java.util.List;
// 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 BlackTextArrayAdapter(@NonNull Context context, int resource, @NonNull T[] objects) {
super(context, resource, objects);

View File

@@ -1,3 +1,10 @@
/*
* Adapter for displaying chat conversations in a list.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.adapters;
import android.view.LayoutInflater;
@@ -20,11 +27,17 @@ public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.ChatViewHolder
void onChatClick(Chat chat);
}
/**
* Constructor for ChatAdapter.
*/
public ChatAdapter(List<Chat> chatList, OnChatClickListener listener) {
this.chatList = chatList;
this.listener = listener;
}
/**
* Inflates the layout for a chat item and creates the ViewHolder.
*/
@NonNull
@Override
public ChatViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -32,6 +45,9 @@ public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.ChatViewHolder
return new ChatViewHolder(binding);
}
/**
* Binds chat data to the UI components for a specific conversation.
*/
@Override
public void onBindViewHolder(@NonNull ChatViewHolder holder, int 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));
}
/**
* Returns the total number of chat items in the list.
*/
@Override
public int getItemCount() {
return chatList.size();
}
/**
* ViewHolder class that holds references to the UI components for a chat item.
*/
public static class ChatViewHolder extends RecyclerView.ViewHolder {
final ItemChatBinding binding;
/**
* Initializes the ViewHolder with the chat item's view binding.
*/
public ChatViewHolder(@NonNull ItemChatBinding binding) {
super(binding.getRoot());
this.binding = binding;

View File

@@ -1,3 +1,10 @@
/*
* Adapter for showing coupons in a list.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.adapters;
import android.view.LayoutInflater;
@@ -30,11 +37,17 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.ViewHolder
void onSelectionChanged(int count);
}
/**
* Constructor for CouponAdapter.
*/
public CouponAdapter(List<CouponDTO> coupons, OnCouponClickListener listener) {
this.coupons = coupons;
this.listener = listener;
}
/**
* Inflates the layout for a coupon item and creates the ViewHolder.
*/
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -42,6 +55,9 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.ViewHolder
return new ViewHolder(view);
}
/**
* Binds coupon data to the UI components and handles interaction logic.
*/
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int 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()));
}
/**
* Toggles the selection state of a specific coupon by its ID.
*/
private void toggleSelection(Long id) {
if (selectedIds.contains(id)) {
selectedIds.remove(id);
@@ -105,6 +124,9 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.ViewHolder
listener.onSelectionChanged(selectedIds.size());
}
/**
* Enables or disables bulk selection mode.
*/
public void setSelectionMode(boolean selectionMode) {
this.selectionMode = selectionMode;
if (!selectionMode) selectedIds.clear();
@@ -112,10 +134,16 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.ViewHolder
listener.onSelectionChanged(selectedIds.size());
}
/**
* Returns the set of IDs for the currently selected coupons.
*/
public Set<Long> getSelectedIds() {
return selectedIds;
}
/**
* Returns the total number of coupons in the list.
*/
@Override
public int getItemCount() {
return coupons.size();
@@ -125,6 +153,9 @@ public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.ViewHolder
TextView tvCouponCode, tvCouponDiscount, tvCouponMinOrder, tvCouponExpiry, tvCouponStatus;
CheckBox cbSelectCoupon;
/**
* Initializes the ViewHolder by finding the views within the item layout.
*/
public ViewHolder(@NonNull View itemView) {
super(itemView);
tvCouponCode = itemView.findViewById(R.id.tvCouponCode);

View File

@@ -1,3 +1,10 @@
/*
* Adapter for displaying customer entries in a list.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.adapters;
import android.graphics.Color;
@@ -24,23 +31,42 @@ public class CustomerAdapter extends RecyclerView.Adapter<CustomerAdapter.Custom
void onCustomerClick(int position);
}
/**
* Constructor for CustomerAdapter.
*/
public CustomerAdapter(List<CustomerDTO> list, OnCustomerClickListener listener) {
this.list = list;
this.listener = listener;
}
/**
* Sets the base URL for fetching customer profile images.
*/
public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; }
/**
* Sets the authentication token for fetching images.
*/
public void setToken(String token) { this.token = token; }
/**
* ViewHolder class for customer items.
*/
public static class CustomerViewHolder extends RecyclerView.ViewHolder {
final ItemCustomerBinding binding;
/**
* Initializes the ViewHolder with view binding.
*/
public CustomerViewHolder(@NonNull ItemCustomerBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
/**
* Inflates the layout for a customer item and creates the ViewHolder.
*/
@NonNull
@Override
public CustomerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -49,6 +75,9 @@ public class CustomerAdapter extends RecyclerView.Adapter<CustomerAdapter.Custom
return new CustomerViewHolder(binding);
}
/**
* Binds customer data to the UI components.
*/
@Override
public void onBindViewHolder(@NonNull CustomerViewHolder holder, int 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));
}
/**
* Returns the total number of customers in the list.
*/
@Override
public int getItemCount() { return list.size(); }
}

View File

@@ -1,3 +1,10 @@
/*
* Adapter for displaying employee entries in a list.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.adapters;
import android.graphics.Color;
@@ -25,28 +32,46 @@ public class EmployeeAdapter extends RecyclerView.Adapter<EmployeeAdapter.Employ
void onEmployeeClick(int position);
}
/**
* Constructor for EmployeeAdapter.
*/
public EmployeeAdapter(List<EmployeeDTO> list, OnEmployeeClickListener listener) {
this.list = list;
this.listener = listener;
}
/**
* Sets the base URL for fetching employee profile images.
*/
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
/**
* Sets the authentication token for fetching images.
*/
public void setToken(String token) {
this.token = token;
}
/**
* ViewHolder class for employee items.
*/
public static class EmployeeViewHolder extends RecyclerView.ViewHolder {
private final ItemEmployeeBinding binding;
/**
* Initializes the ViewHolder with view binding.
*/
public EmployeeViewHolder(@NonNull ItemEmployeeBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
/**
* Inflates the layout for an employee item and creates the ViewHolder.
*/
@NonNull
@Override
public EmployeeViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -55,6 +80,9 @@ public class EmployeeAdapter extends RecyclerView.Adapter<EmployeeAdapter.Employ
return new EmployeeViewHolder(binding);
}
/**
* Binds employee data to the UI components.
*/
@Override
public void onBindViewHolder(@NonNull EmployeeViewHolder holder, int 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));
}
/**
* Returns the total number of employees in the list.
*/
@Override
public int getItemCount() { return list.size(); }
}

View File

@@ -1,3 +1,10 @@
/*
* Adapter for showing inventory items in a list.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.adapters;
import android.graphics.Color;
@@ -27,6 +34,9 @@ public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.Inve
void onSelectionChanged(int selectedCount);
}
/**
* Constructor for InventoryAdapter.
*/
public InventoryAdapter(List<InventoryDTO> inventoryList, OnInventoryClickListener clickListener) {
this.inventoryList = inventoryList;
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
public List<String> getSelectedKeys() {
return selectionHelper.getSelectedKeys();
}
/**
* Resets the selection state, deselecting all items.
*/
@Override
public void clearSelection() {
selectionHelper.clearSelection();
}
/**
* ViewHolder class that holds references to the UI components for an inventory item.
*/
public static class InventoryViewHolder extends RecyclerView.ViewHolder {
final ItemInventoryBinding binding;
/**
* Initializes the ViewHolder with view binding.
*/
public InventoryViewHolder(@NonNull ItemInventoryBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
/**
* Inflates the layout for an inventory item and creates the ViewHolder.
*/
@NonNull
@Override
public InventoryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -69,6 +94,9 @@ public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.Inve
return new InventoryViewHolder(binding);
}
/**
* Binds inventory data to the UI components.
*/
@Override
public void onBindViewHolder(@NonNull InventoryViewHolder holder, int 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;
binding.tvQuantity.setText("Stock: " + qty);
// Low stock = red, normal = green (like desktop reorder concept)
if (qty <= 5) {
binding.tvQuantity.setTextColor(Color.parseColor("#F44336"));
} else {
@@ -119,6 +146,9 @@ public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.Inve
});
}
/**
* Returns the total number of inventory items in the list.
*/
@Override
public int getItemCount() {
return inventoryList.size();

View File

@@ -1,3 +1,10 @@
/*
* Adapter for displaying chat messages in a conversation.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.adapters;
import android.view.LayoutInflater;
@@ -37,40 +44,64 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
private String baseUrl;
private OnAttachmentClickListener attachmentClickListener;
/**
* Constructor for MessageAdapter.
*/
public MessageAdapter(List<Message> messages, Long currentUserId) {
this.messages = messages;
this.currentUserId = currentUserId;
setHasStableIds(true);
}
/**
* Returns an ID for each message.
*/
@Override
public long getItemId(int position) {
Message m = messages.get(position);
return m.getId() != null ? m.getId() : position;
}
/**
* Updates the current user's ID and refreshes the list.
*/
public void setCurrentUserId(Long id) {
this.currentUserId = id;
notifyDataSetChanged();
}
/**
* Updates the staff ID to identify staff messages in the UI.
*/
public void setStaffId(Long id) {
this.staffId = id;
notifyDataSetChanged();
}
/**
* Sets the authentication token.
*/
public void setToken(String token) {
this.token = token;
}
/**
* Sets the base API URL.
*/
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
/**
* Sets a listener for clicks on message attachments.
*/
public void setOnAttachmentClickListener(OnAttachmentClickListener listener) {
this.attachmentClickListener = listener;
}
/**
* Determines if a message is 'sent' or 'received' based on the sender's ID.
*/
@Override
public int getItemViewType(int position) {
Message m = messages.get(position);
@@ -80,6 +111,9 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
return TYPE_RECEIVED;
}
/**
* Inflates the chat layout for a message.
*/
@NonNull @Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
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
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int 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);
}
/**
* Returns the total number of messages.
*/
@Override public int getItemCount() { return messages.size(); }
/**
* ViewHolder for messages sent by the user.
*/
static class SentHolder extends RecyclerView.ViewHolder {
final ItemMessageSentBinding binding;
/**
* Initializes the SentHolder with view binding.
*/
SentHolder(ItemMessageSentBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
/**
* Binds sent message data to the bubble UI.
*/
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener) {
binding.tvSenderName.setText("You");
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 {
final ItemMessageReceivedBinding binding;
/**
* Initializes the ReceivedHolder with view binding.
*/
ReceivedHolder(ItemMessageReceivedBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
/**
* Binds received message data to the bubble UI.
*/
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener, Long staffId) {
binding.tvSenderName.setText(resolveSenderName(m, staffId));
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) {
if ("BOT".equalsIgnoreCase(m.getSenderRole())) {
return (m.getSenderDisplayName() != null && !m.getSenderDisplayName().isEmpty())
@@ -166,6 +227,9 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
return "Customer";
}
/**
* Formats the timestamp string into readable format.
*/
private static String formatTimestamp(String timestamp) {
if (timestamp == null || timestamp.isEmpty()) return "";
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) {
// 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) {
// Construct the download URL using the message ID
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;
url = cleanBase + "/api/v1/chat/messages/" + m.getId() + "/attachment";
} else {
url = m.getAttachmentUrl(); // Fallback
url = m.getAttachmentUrl();
}
if (url == null) {
@@ -208,7 +275,7 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
.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();
Glide.with(iv.getContext()).clear(iv);

View File

@@ -1,3 +1,10 @@
/*
* Adapter for showing pets in a list.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.adapters;
import android.graphics.Color;
@@ -31,7 +38,9 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> i
void onSelectionChanged(int selectedCount);
}
//Constructor
/**
* Constructor for PetAdapter.
*/
public PetAdapter(List<PetDTO> petList, OnPetClickListener petClickListener) {
this.petList = petList;
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) {
this.baseUrl = baseUrl;
}
/**
* Sets the authentication token
*/
public void setToken(String token) {
this.token = token;
}
/**
* Returns a list of IDs for the currently selected pet items.
*/
@Override
public List<String> getSelectedKeys() {
return selectionHelper.getSelectedKeys();
}
/**
* Resets the selection state, deselecting all items.
*/
@Override
public void 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 {
private final ItemPetBinding binding;
/**
* Initializes the ViewHolder with view binding.
*/
public PetViewHolder(@NonNull ItemPetBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
// Create a new row view
/**
* Inflates the layout for a pet item and creates the ViewHolder.
*/
@NonNull
@Override
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);
}
//populate the row with pet data
/**
* Binds pet data to the UI components.
*/
@Override
public void onBindViewHolder(@NonNull PetViewHolder holder, int position) {
PetDTO pet = petList.get(position);
@@ -103,7 +133,7 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> i
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) {
switch (pet.getPetStatus()) {
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
public int getItemCount() {
return petList.size();

View File

@@ -1,3 +1,10 @@
/*
* Adapter for displaying products in a list.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.adapters;
import android.view.*;
@@ -22,28 +29,46 @@ public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductV
void onProductClick(int position);
}
/**
* Constructor for ProductAdapter.
*/
public ProductAdapter(List<ProductDTO> productList, OnProductClickListener listener) {
this.productList = productList;
this.listener = listener;
}
/**
* Sets the base URL for fetching product images.
*/
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
/**
* Sets the authentication token
*/
public void setToken(String token) {
this.token = token;
}
/**
* ViewHolder class for product items.
*/
public static class ProductViewHolder extends RecyclerView.ViewHolder {
final ItemProductBinding binding;
/**
* Initializes the ViewHolder with view binding.
*/
public ProductViewHolder(@NonNull ItemProductBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
/**
* Inflates the layout for a product item and creates the ViewHolder.
*/
@NonNull
@Override
public ProductViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -51,6 +76,9 @@ public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductV
return new ProductViewHolder(binding);
}
/**
* Binds product data to the UI components.
*/
@Override
public void onBindViewHolder(@NonNull ProductViewHolder holder, int 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));
}
/**
* Returns the total number of product items in the list.
*/
@Override
public int getItemCount() { return productList.size(); }
}

View File

@@ -1,3 +1,10 @@
/*
* Adapter for showing product-supplier links in a list.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.adapters;
import android.view.LayoutInflater;
@@ -25,6 +32,9 @@ public class ProductSupplierAdapter extends RecyclerView.Adapter<ProductSupplier
void onSelectionChanged(int count);
}
/**
* Constructor for ProductSupplierAdapter.
*/
public ProductSupplierAdapter(List<ProductSupplierDTO> list, OnProductSupplierClickListener listener) {
this.list = list;
this.listener = listener;
@@ -41,25 +51,40 @@ public class ProductSupplierAdapter extends RecyclerView.Adapter<ProductSupplier
});
}
/**
* Returns the list of selected keys for bulk deletion.
*/
@Override
public List<String> getSelectedKeys() {
return selectionHelper.getSelectedKeys();
}
/**
* Clears all selected items and exits selection mode.
*/
@Override
public void clearSelection() {
selectionHelper.clearSelection();
}
/**
* ViewHolder for Product-Supplier relationship items.
*/
public static class PSViewHolder extends RecyclerView.ViewHolder {
final ItemProductSupplierBinding binding;
/**
* Initializes the ViewHolder with view binding.
*/
public PSViewHolder(@NonNull ItemProductSupplierBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
/**
* Inflates the layout for a Product-Supplier item.
*/
@NonNull
@Override
public PSViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -67,6 +92,9 @@ public class ProductSupplierAdapter extends RecyclerView.Adapter<ProductSupplier
return new PSViewHolder(binding);
}
/**
* Binds product-supplier data to the UI components.
*/
@Override
public void onBindViewHolder(@NonNull PSViewHolder holder, int position) {
ProductSupplierDTO ps = list.get(position);

View File

@@ -1,3 +1,10 @@
/*
* Adapter for displaying purchase orders in a list.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.adapters;
import android.view.LayoutInflater;
@@ -17,20 +24,32 @@ public class PurchaseOrderAdapter extends RecyclerView.Adapter<PurchaseOrderAdap
void onPurchaseOrderClick(int position);
}
/**
* Constructor for PurchaseOrderAdapter.
*/
public PurchaseOrderAdapter(List<PurchaseOrderDTO> list, OnPurchaseOrderClickListener listener) {
this.list = list;
this.listener = listener;
}
/**
* ViewHolder for Purchase Order items.
*/
public static class POViewHolder extends RecyclerView.ViewHolder {
final ItemPurchaseOrderBinding binding;
/**
* Initializes the ViewHolder with view binding.
*/
public POViewHolder(@NonNull ItemPurchaseOrderBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
/**
* Inflates the layout for a Purchase Order item.
*/
@NonNull
@Override
public POViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -38,6 +57,9 @@ public class PurchaseOrderAdapter extends RecyclerView.Adapter<PurchaseOrderAdap
return new POViewHolder(binding);
}
/**
* Binds purchase order data to the UI components.
*/
@Override
public void onBindViewHolder(@NonNull POViewHolder holder, int position) {
PurchaseOrderDTO po = list.get(position);

View File

@@ -1,3 +1,10 @@
/*
* Adapter for showing sales in a list.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.adapters;
import android.view.LayoutInflater;
@@ -20,20 +27,32 @@ public class SaleAdapter extends RecyclerView.Adapter<SaleAdapter.SaleViewHolder
void onSaleClick(int position);
}
/**
* Constructor for SaleAdapter.
*/
public SaleAdapter(List<SaleDTO> saleList, OnSaleClickListener listener) {
this.saleList = saleList;
this.listener = listener;
}
/**
* ViewHolder for Sale record items.
*/
public static class SaleViewHolder extends RecyclerView.ViewHolder {
final ItemSaleBinding binding;
/**
* Initializes the ViewHolder with view binding.
*/
public SaleViewHolder(@NonNull ItemSaleBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
/**
* Inflates the layout for a Sale record item.
*/
@NonNull
@Override
public SaleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -41,6 +60,9 @@ public class SaleAdapter extends RecyclerView.Adapter<SaleAdapter.SaleViewHolder
return new SaleViewHolder(binding);
}
/**
* Binds sale transaction data to the UI components.
*/
@Override
public void onBindViewHolder(@NonNull SaleViewHolder holder, int position) {
SaleDTO s = saleList.get(position);

View File

@@ -1,3 +1,10 @@
/*
* Adapter for displaying services in a list.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.adapters;
import android.view.LayoutInflater;
@@ -31,6 +38,9 @@ public class ServiceAdapter extends RecyclerView.Adapter<ServiceAdapter.ServiceV
void onSelectionChanged(int count);
}
/**
* Constructor for ServiceAdapter.
*/
public ServiceAdapter(List<ServiceDTO> serviceList, OnServiceClickListener clickListener) {
this.serviceList = serviceList;
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
public List<String> getSelectedKeys() {
return selectionHelper.getSelectedKeys();
}
/**
* Clears all selected items and exits selection mode.
*/
@Override
public void clearSelection() {
selectionHelper.clearSelection();
}
/**
* ViewHolder class for service items.
* ViewHolder for Service items.
*/
public static class ServiceViewHolder extends RecyclerView.ViewHolder {
final ItemServiceBinding binding;
/**
* Initializes the ViewHolder with view binding.
*/
public ServiceViewHolder(@NonNull ItemServiceBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
// Create a new row view
/**
* Inflates the layout for a Service item.
*/
@NonNull
@Override
public ServiceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -77,7 +98,9 @@ public class ServiceAdapter extends RecyclerView.Adapter<ServiceAdapter.ServiceV
return new ServiceViewHolder(binding);
}
//populate the row with service data
/**
* Binds service data to the UI components.
*/
@Override
public void onBindViewHolder(@NonNull ServiceViewHolder holder, int position) {
ServiceDTO service = serviceList.get(position);

View File

@@ -1,3 +1,10 @@
/*
* Adapter for showing suppliers in a list.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.adapters;
import android.view.LayoutInflater;
@@ -26,7 +33,9 @@ public class SupplierAdapter extends RecyclerView.Adapter<SupplierAdapter.Suppli
void onSelectionChanged(int count);
}
//Constructor
/**
* Constructor for SupplierAdapter.
*/
public SupplierAdapter(List<SupplierDTO> supplierList, OnSupplierClickListener supplierClickListener) {
this.supplierList = supplierList;
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
public List<String> getSelectedKeys() {
return selectionHelper.getSelectedKeys();
}
/**
* Clears all selected items and exits selection mode.
*/
@Override
public void clearSelection() {
selectionHelper.clearSelection();
}
// Get the controls of each row in recycler view
/**
* ViewHolder for Supplier items.
*/
public static class SupplierViewHolder extends RecyclerView.ViewHolder {
final ItemSupplierBinding binding;
/**
* Initializes the ViewHolder with view binding.
*/
public SupplierViewHolder(@NonNull ItemSupplierBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
// Create a new row view
/**
* Inflates the layout for a Supplier item.
*/
@NonNull
@Override
public SupplierViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -71,7 +93,9 @@ public class SupplierAdapter extends RecyclerView.Adapter<SupplierAdapter.Suppli
return new SupplierViewHolder(binding);
}
//populate the row with supplier data
/**
* Binds supplier data to the UI components.
*/
@Override
public void onBindViewHolder(@NonNull SupplierViewHolder holder, int position) {
SupplierDTO supplier = supplierList.get(position);

View File

@@ -1,3 +1,10 @@
/*
* Custom array adapter that displays items with white text.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.adapters;
import android.content.Context;
@@ -12,9 +19,8 @@ import androidx.core.content.ContextCompat;
import com.example.petstoremobile.R;
import java.util.List;
/**
* A class that overrides the arrayAdapter so the text color is white and background is transparent.
*/
// A class that overrides the arrayAdapter so the text color changes based on theme
// Used to make spinners have white text on dark background no matter the theme
public class WhiteTextArrayAdapter<T> extends ArrayAdapter<T> {
public WhiteTextArrayAdapter(@NonNull Context context, int resource, @NonNull T[] objects) {
super(context, resource, objects);

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for activity log endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.ActivityLogDTO;
@@ -8,8 +15,10 @@ import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
// api calls to get activity logs
public interface ActivityLogApi {
// Get activity logs with filters
@GET("api/v1/activity-logs")
Call<List<ActivityLogDTO>> getActivityLogs(
@Query("limit") int limit,

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for adoption endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.AdoptionDTO;
@@ -14,8 +21,10 @@ import retrofit2.http.PUT;
import retrofit2.http.Path;
import retrofit2.http.Query;
// api calls for adoptions
public interface AdoptionApi {
// Get all adoptions with filters
@GET("api/v1/adoptions")
Call<PageResponse<AdoptionDTO>> getAllAdoptions(
@Query("page") int page,
@@ -27,18 +36,23 @@ public interface AdoptionApi {
@Query("employeeId") Long employeeId,
@Query("sort") String sort);
// Get adoption by id
@GET("api/v1/adoptions/{id}")
Call<AdoptionDTO> getAdoptionById(@Path("id") Long id);
// Create adoption
@POST("api/v1/adoptions")
Call<AdoptionDTO> createAdoption(@Body AdoptionDTO adoption);
// Update adoption
@PUT("api/v1/adoptions/{id}")
Call<AdoptionDTO> updateAdoption(@Path("id") Long id, @Body AdoptionDTO adoption);
// Delete adoption
@DELETE("api/v1/adoptions/{id}")
Call<Void> deleteAdoption(@Path("id") Long id);
// Bulk delete adoptions
@HTTP(method = "DELETE", path = "api/v1/adoptions", hasBody = true)
Call<Void> bulkDeleteAdoptions(@Body BulkDeleteRequest request);
}

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for appointment endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.AppointmentDTO;
@@ -14,8 +21,10 @@ import retrofit2.http.PUT;
import retrofit2.http.Path;
import retrofit2.http.Query;
// api calls for appointments
public interface AppointmentApi {
// Get all appointments with filters
@GET("api/v1/appointments")
Call<PageResponse<AppointmentDTO>> getAllAppointments(
@Query("page") int page,
@@ -27,18 +36,23 @@ public interface AppointmentApi {
@Query("employeeId") Long employeeId,
@Query("sort") String sort);
// Get appointment by id
@GET("api/v1/appointments/{id}")
Call<AppointmentDTO> getAppointmentById(@Path("id") Long id);
// Create appointment
@POST("api/v1/appointments")
Call<AppointmentDTO> createAppointment(@Body AppointmentDTO appointment);
// Update appointment
@PUT("api/v1/appointments/{id}")
Call<AppointmentDTO> updateAppointment(@Path("id") Long id, @Body AppointmentDTO appointment);
// Delete appointment
@DELETE("api/v1/appointments/{id}")
Call<Void> deleteAppointment(@Path("id") Long id);
// Bulk delete appointments
@HTTP(method = "DELETE", path = "api/v1/appointments", hasBody = true)
Call<Void> bulkDeleteAppointments(@Body BulkDeleteRequest request);
}

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for category endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.CategoryDTO;
@@ -6,8 +13,10 @@ import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
// api calls for categories
public interface CategoryApi {
// Get all categories with pagination
@GET("api/v1/categories")
Call<PageResponse<CategoryDTO>> getAllCategories(
@Query("page") int page,

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for chat and conversation endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.ConversationDTO;
@@ -12,15 +19,18 @@ import retrofit2.http.GET;
import retrofit2.http.PUT;
import retrofit2.http.Path;
//api calls to get conversations
// api calls for chat conversations
public interface ChatApi {
// Get all conversations
@GET("api/v1/chat/conversations")
Call<List<ConversationDTO>> getAllConversations();
// Get conversation by id
@GET("api/v1/chat/conversations/{conversationId}")
Call<ConversationDTO> getConversationById(@Path("conversationId") Long conversationId);
// Update conversation status
@PUT("api/v1/chat/conversations/{conversationId}")
Call<ConversationDTO> updateConversationStatus(@Path("conversationId") Long conversationId, @Body UpdateConversationStatusRequest request);

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for coupon endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.CouponDTO;
@@ -6,9 +13,12 @@ import com.example.petstoremobile.dtos.PageResponse;
import java.util.List;
import retrofit2.Call;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.GET;
import retrofit2.http.HTTP;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
@@ -39,6 +49,6 @@ public interface CouponApi {
@DELETE("api/v1/coupons/{id}")
Call<Void> deleteCoupon(@Path("id") Long id);
@DELETE("api/v1/coupons")
Call<Void> bulkDeleteCoupons(@Query("ids") List<Long> ids);
@HTTP(method = "DELETE", path = "api/v1/coupons", hasBody = true)
Call<Void> bulkDeleteCoupons(@Body BulkDeleteRequest request);
}

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for customer endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.CustomerDTO;
@@ -15,23 +22,30 @@ import retrofit2.http.PUT;
import retrofit2.http.Path;
import retrofit2.http.Query;
// api calls for customers
public interface CustomerApi {
// Get all customers with pagination
@GET("api/v1/customers")
Call<PageResponse<CustomerDTO>> getAllCustomers(@Query("page") int page, @Query("size") int size);
// Get customer by id
@GET("api/v1/customers/{customerId}")
Call<CustomerDTO> getCustomerById(@Path("customerId") Long customerId);
// Update customer
@PUT("api/v1/customers/{customerId}")
Call<CustomerDTO> updateCustomer(@Path("customerId") Long customerId, @Body CustomerDTO customer);
// Delete customer
@DELETE("api/v1/customers/{customerId}")
Call<Void> deleteCustomer(@Path("customerId") Long customerId);
// Register customer
@POST("api/v1/auth/register")
Call<CustomerDTO> registerCustomer(@Body CustomerDTO customer);
// Get customer dropdowns
@GET("api/v1/dropdowns/customers")
Call<List<DropdownDTO>> getCustomerDropdowns();
}

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for employee endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.EmployeeDTO;
@@ -5,28 +12,28 @@ import com.example.petstoremobile.dtos.PageResponse;
import retrofit2.Call;
import retrofit2.http.*;
// api calls for employees
public interface EmployeeApi {
// Get all employees with pagination
@GET("api/v1/employees")
Call<PageResponse<EmployeeDTO>> getAllEmployees(
@Query("page") int page,
@Query("size") int size);
// Get employee by id
@GET("api/v1/employees/{id}")
Call<EmployeeDTO> getEmployeeById(@Path("id") Long id);
// Create employee
@POST("api/v1/employees")
Call<EmployeeDTO> createEmployee(@Body EmployeeDTO employee);
// Update employee
@PUT("api/v1/employees/{id}")
Call<EmployeeDTO> updateEmployee(@Path("id") Long id, @Body EmployeeDTO employee);
// Delete employee
@DELETE("api/v1/employees/{id}")
Call<Void> deleteEmployee(@Path("id") Long id);
}

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for inventory endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
@@ -14,8 +21,10 @@ import retrofit2.http.PUT;
import retrofit2.http.Path;
import retrofit2.http.Query;
// api calls for inventory
public interface InventoryApi {
// Get all inventory with filters
@GET("api/v1/inventory")
Call<PageResponse<InventoryDTO>> getAllInventory(
@Query("page") int page,
@@ -24,23 +33,23 @@ public interface InventoryApi {
@Query("storeId") Long storeId,
@Query("sort") String sort);
// GET /api/v1/inventory/{id}
// Get inventory by id
@GET("api/v1/inventory/{id}")
Call<InventoryDTO> getInventoryById(@Path("id") Long id);
// POST /api/v1/inventory
// Create inventory
@POST("api/v1/inventory")
Call<InventoryDTO> createInventory(@Body InventoryDTO request);
// PUT /api/v1/inventory/{id}
// Update inventory
@PUT("api/v1/inventory/{id}")
Call<InventoryDTO> updateInventory(@Path("id") Long id, @Body InventoryDTO request);
// DELETE /api/v1/inventory/{id}
// Delete inventory
@DELETE("api/v1/inventory/{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)
Call<Void> bulkDeleteInventory(@Body BulkDeleteRequest request);
}

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for message endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.MessageDTO;
@@ -15,15 +22,18 @@ import retrofit2.http.Part;
import retrofit2.http.Path;
import retrofit2.http.Streaming;
//api calls to get and send messages
// api calls for messages
public interface MessageApi {
// Get messages for a conversation
@GET("api/v1/chat/conversations/{id}/messages")
Call<List<MessageDTO>> getMessages(@Path("id") Long conversationId);
// Send a message
@POST("api/v1/chat/conversations/{id}/messages")
Call<MessageDTO> sendMessage(@Path("id") Long conversationId, @Body SendMessageRequest request);
// Send a message with attachment
@Multipart
@POST("api/v1/chat/conversations/{id}/attachments")
Call<MessageDTO> sendMessageWithAttachment(
@@ -32,6 +42,7 @@ public interface MessageApi {
@Part MultipartBody.Part file
);
// Download attachment
@GET("api/v1/chat/messages/{id}/attachment")
@Streaming
Call<ResponseBody> downloadAttachment(@Path("id") Long messageId);

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for pet endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
@@ -38,18 +45,23 @@ public interface PetApi {
@Query("sort") String sort
);
// Get pets by customer id
@GET("api/v1/dropdowns/customers/{customerId}/pets")
Call<List<DropdownDTO>> getCustomerPets(@Path("customerId") Long customerId);
// Get adoption pets
@GET("api/v1/dropdowns/adoption-pets")
Call<List<DropdownDTO>> getAdoptionPets(@Query("storeId") Long storeId);
// Get pet dropdowns
@GET("api/v1/dropdowns/pets")
Call<List<DropdownDTO>> getPetDropdowns();
// Get pet species dropdowns
@GET("api/v1/dropdowns/pet-species")
Call<List<DropdownDTO>> getPetSpeciesDropdowns();
// Get pet breeds dropdowns
@GET("api/v1/dropdowns/pet-breeds")
Call<List<DropdownDTO>> getPetBreedsDropdowns(@Query("species") String species);
@@ -81,5 +93,4 @@ public interface PetApi {
// Delete pet image
@DELETE("api/v1/pets/{id}/image")
Call<Void> deletePetImage(@Path("id") Long id);
}

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for product endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.DropdownDTO;
@@ -9,9 +16,12 @@ import retrofit2.http.*;
import java.util.List;
// api calls for products
public interface ProductApi {
// endpoint for downloading the product's image file
String PRODUCT_IMAGE_PATH = "api/v1/products/%d/image";
// Get all products with filters
@GET("api/v1/products")
Call<PageResponse<ProductDTO>> getAllProducts(
@Query("q") String query,
@@ -20,28 +30,36 @@ public interface ProductApi {
@Query("size") int size,
@Query("sort") String sort);
// Get product by id
@GET("api/v1/products/{id}")
Call<ProductDTO> getProductById(@Path("id") Long id);
// Create product
@POST("api/v1/products")
Call<ProductDTO> createProduct(@Body ProductDTO product);
// Update product
@PUT("api/v1/products/{id}")
Call<ProductDTO> updateProduct(@Path("id") Long id, @Body ProductDTO product);
// Delete product
@DELETE("api/v1/products/{id}")
Call<Void> deleteProduct(@Path("id") Long id);
// Upload product image
@Multipart
@POST("api/v1/products/{id}/image")
Call<Void> uploadProductImage(@Path("id") Long id, @Part MultipartBody.Part image);
// Delete product image
@DELETE("api/v1/products/{id}/image")
Call<Void> deleteProductImage(@Path("id") Long id);
// Get product dropdowns
@GET("api/v1/dropdowns/products")
Call<List<DropdownDTO>> getProductDropdowns();
// Get category dropdowns
@GET("api/v1/dropdowns/categories")
Call<List<DropdownDTO>> getCategoryDropdowns();
}

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for product-supplier endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
@@ -6,8 +13,10 @@ import com.example.petstoremobile.dtos.ProductSupplierDTO;
import retrofit2.Call;
import retrofit2.http.*;
// api calls for product-supplier relationships
public interface ProductSupplierApi {
// Get all product-suppliers with filters
@GET("api/v1/product-suppliers")
Call<PageResponse<ProductSupplierDTO>> getAllProductSuppliers(
@Query("page") int page,
@@ -17,24 +26,30 @@ public interface ProductSupplierApi {
@Query("supplierId") Long supplierId,
@Query("sort") String sort);
// Get product-supplier by composite id
@GET("api/v1/product-suppliers/{productId}/{supplierId}")
Call<ProductSupplierDTO> getProductSupplierById(
@Path("productId") Long productId,
@Path("supplierId") Long supplierId);
// Create product-supplier
@POST("api/v1/product-suppliers")
Call<ProductSupplierDTO> createProductSupplier(@Body ProductSupplierDTO dto);
// Update product-supplier
@PUT("api/v1/product-suppliers/{productId}/{supplierId}")
Call<ProductSupplierDTO> updateProductSupplier(
@Path("productId") Long productId,
@Path("supplierId") Long supplierId,
@Body ProductSupplierDTO dto);
// Delete product-supplier
@DELETE("api/v1/product-suppliers/{productId}/{supplierId}")
Call<Void> deleteProductSupplier(
@Path("productId") Long productId,
@Path("supplierId") Long supplierId);
// Bulk delete product-suppliers
@HTTP(method = "DELETE", path = "api/v1/product-suppliers", hasBody = true)
Call<Void> bulkDeleteProductSuppliers(@Body BulkDeleteRequest request);
}

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for purchase order endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.PageResponse;
@@ -7,8 +14,10 @@ import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.Query;
// api calls for purchase orders
public interface PurchaseOrderApi {
// Get all purchase orders with filters
@GET("api/v1/purchase-orders")
Call<PageResponse<PurchaseOrderDTO>> getAllPurchaseOrders(
@Query("page") int page,
@@ -17,6 +26,7 @@ public interface PurchaseOrderApi {
@Query("storeId") Long storeId,
@Query("sort") String sort);
// Get purchase order by id
@GET("api/v1/purchase-orders/{id}")
Call<PurchaseOrderDTO> getPurchaseOrderById(@Path("id") Long id);
}

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for refund endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.RefundDTO;
@@ -6,20 +13,26 @@ import retrofit2.http.*;
import java.util.List;
// api calls for refunds
public interface RefundApi {
// Get all refunds
@GET("api/v1/refunds")
Call<List<RefundDTO>> getAllRefunds();
// Get refund by id
@GET("api/v1/refunds/{id}")
Call<RefundDTO> getRefundById(@Path("id") Long id);
// Create refund
@POST("api/v1/refunds")
Call<RefundDTO> createRefund(@Body RefundDTO refund);
// Update refund
@PUT("api/v1/refunds/{id}")
Call<RefundDTO> updateRefund(@Path("id") Long id, @Body RefundDTO refund);
// Delete refund
@DELETE("api/v1/refunds/{id}")
Call<Void> deleteRefund(@Path("id") Long id);
}

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for sale endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.PageResponse;
@@ -10,8 +17,10 @@ import retrofit2.http.POST;
import retrofit2.http.Path;
import retrofit2.http.Query;
// api calls for sales
public interface SaleApi {
// Get all sales with filters
@GET("api/v1/sales")
Call<PageResponse<SaleDTO>> getAllSales(
@Query("page") int page,
@@ -23,9 +32,11 @@ public interface SaleApi {
@Query("customerId") Long customerId,
@Query("sort") String sort);
// Get sale by id
@GET("api/v1/sales/{id}")
Call<SaleDTO> getSaleById(@Path("id") Long id);
// Create sale
@POST("api/v1/sales")
Call<SaleDTO> createSale(@Body SaleDTO sale);
}

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for service endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.BulkDeleteRequest;

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for store endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.DropdownDTO;
@@ -11,16 +18,20 @@ import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.Query;
// api calls for stores
public interface StoreApi {
// Get all stores with pagination
@GET("api/v1/stores")
Call<PageResponse<StoreDTO>> getAllStores(
@Query("page") int page,
@Query("size") int size);
// Get store dropdowns
@GET("api/v1/dropdowns/stores")
Call<List<DropdownDTO>> getStoreDropdowns();
// Get employees of a specific store
@GET("api/v1/dropdowns/stores/{storeId}/employees")
Call<List<DropdownDTO>> getStoreEmployees(@Path("storeId") Long storeId);
}

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for supplier endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.BulkDeleteRequest;

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for user endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.PageResponse;
@@ -7,9 +14,12 @@ import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
// api calls for users
public interface UserApi {
// endpoint for downloading the user's avatar file
String AVATAR_PATH = "api/v1/users/%d/avatar/file";
// Get all users with filters
@GET("api/v1/users")
Call<PageResponse<UserDTO>> getUsers(@Query("role") String role, @Query("page") int page, @Query("size") int size);
}

View File

@@ -1,3 +1,10 @@
/*
* Retrofit interface for login and registration endpoints.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api.auth;
import com.example.petstoremobile.dtos.AuthDTO;

View File

@@ -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;
import androidx.annotation.NonNull;
@@ -23,19 +30,26 @@ public class AuthInterceptor implements Interceptor {
String token = tokenManager.getToken();
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());
}
//If we have a token then add it to the request
Response response;
if (token != null) {
Request request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + token)
.build();
return chain.proceed(request);
response = chain.proceed(request);
} else {
response = chain.proceed(chain.request());
}
//If no token then just pass the request
return chain.proceed(chain.request());
if (response.code() == 401) {
tokenManager.forceLogout();
}
return response;
}
}

View File

@@ -1,6 +1,14 @@
/*
* Handles saving and retrieving the authentication token.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.api.auth;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import javax.inject.Inject;
@@ -8,8 +16,11 @@ import javax.inject.Singleton;
import dagger.hilt.android.qualifiers.ApplicationContext;
//Used to save and retrieve login data
@Singleton
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 USERNAME_KEY = "username";
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 PRIMARY_STORE_ID_KEY = "primary_store_id";
private final Context context;
private SharedPreferences prefs;
@Inject
public TokenManager(@ApplicationContext Context context) {
this.context = context;
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
public void saveLoginData(String token, String username, String role) {
prefs.edit()

View File

@@ -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;
import android.content.Context;

View File

@@ -1,5 +1,15 @@
/*
* Data transfer object for activity log entries.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
/**
* Data Transfer Object for activity logs.
*/
public class ActivityLogDTO {
private Long logId;
private String activity;

View File

@@ -1,7 +1,17 @@
/*
* Data transfer object for adoption records.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
import java.math.BigDecimal;
/**
* Data Transfer Object for pet adoptions.
*/
public class AdoptionDTO {
private Long adoptionId;

View File

@@ -1,5 +1,15 @@
/*
* Data transfer object for appointments.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
/**
* Data Transfer Object for appointments.
*/
public class AppointmentDTO {
private Long appointmentId;

View File

@@ -1,6 +1,15 @@
/*
* Data transfer object for authentication requests and responses.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
//Used to send login data to the backend
/**
* Data Transfer Object for authentication credentials.
*/
public class AuthDTO {
public static class LoginRequest {
private String username;

View File

@@ -1,5 +1,15 @@
/*
* Response object returned after uploading an avatar image.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
/**
* Response containing the URL of a newly uploaded avatar.
*/
public class AvatarUploadResponse {
private String avatarUrl;
private String message;

View File

@@ -1,22 +1,32 @@
/*
* Request object for deleting multiple items at once.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
import java.util.List;
/**
* Request body for deleting multiple records at once.
*/
public class BulkDeleteRequest {
private List<String> ids;
private List<Long> ids;
public BulkDeleteRequest() {
}
public BulkDeleteRequest(List<String> ids) {
public BulkDeleteRequest(List<Long> ids) {
this.ids = ids;
}
public List<String> getIds() {
public List<Long> getIds() {
return ids;
}
public void setIds(List<String> ids) {
public void setIds(List<Long> ids) {
this.ids = ids;
}
}

View File

@@ -1,5 +1,15 @@
/*
* Data transfer object for categories.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
/**
* Data Transfer Object for product categories.
*/
public class CategoryDTO {
private Long categoryId;
private String categoryName;

View File

@@ -1,5 +1,15 @@
/*
* Data transfer object for chat conversations.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
/**
* Data Transfer Object for chat conversations.
*/
public class ConversationDTO {
private Long id;
private Long customerId;

View File

@@ -1,7 +1,17 @@
/*
* Data transfer object for coupons.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
import java.math.BigDecimal;
/**
* Data Transfer Object for coupons.
*/
public class CouponDTO {
private Long couponId;
private String couponCode;

View File

@@ -1,7 +1,17 @@
/*
* Data transfer object for customers.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
import com.google.gson.annotations.SerializedName;
/**
* Data Transfer Object for customers.
*/
public class CustomerDTO {
@SerializedName("id")
private Long customerId;

View File

@@ -1,5 +1,15 @@
/*
* Data transfer object used to populate dropdown menus.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
/**
* Data Transfer Object for simple dropdown selection lists.
*/
public class DropdownDTO {
private Long id;
private String label;

View File

@@ -1,5 +1,15 @@
/*
* Data transfer object for employees.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
/**
* Data Transfer Object for employees.
*/
public class EmployeeDTO {
private Long id;

View File

@@ -1,7 +1,15 @@
/*
* Represents an error response from the server.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
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 {
private String message;

View File

@@ -1,5 +1,15 @@
/*
* Data transfer object for inventory items.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
/**
* Data Transfer Object for inventory stock.
*/
public class InventoryDTO {
// Response fields (from backend InventoryResponse)
private Long inventoryId;

View File

@@ -1,7 +1,17 @@
/*
* Data transfer object for chat messages.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
import com.google.gson.annotations.SerializedName;
/**
* Data Transfer Object for chat messages.
*/
public class MessageDTO {
@SerializedName("id")

View File

@@ -1,8 +1,17 @@
/*
* Wrapper for paginated responses from the server.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
import java.util.List;
//Used to get data from the API
/**
* Generic response wrapper for paginated API results.
*/
public class PageResponse<T> {
private List<T> content;
private int totalPages;

View File

@@ -1,5 +1,15 @@
/*
* Data transfer object for pets.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
/**
* Data Transfer Object representing a pet.
*/
public class PetDTO {
private Long petId;
private String petName;

View File

@@ -1,7 +1,17 @@
/*
* Data transfer object for products.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
import java.math.BigDecimal;
/**
* Data Transfer Object for products.
*/
public class ProductDTO {
private Long prodId;
private String prodName;

View File

@@ -1,7 +1,17 @@
/*
* Data transfer object for product-supplier relationships.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
import java.math.BigDecimal;
/**
* Data Transfer Object for mapping products to suppliers.
*/
public class ProductSupplierDTO {
private Long productId;
private String productName;

View File

@@ -1,5 +1,15 @@
/*
* Data transfer object for purchase orders.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
/**
* Data Transfer Object for purchase orders.
*/
public class PurchaseOrderDTO {
private Long purchaseOrderId;
private Long supId;

View File

@@ -1,7 +1,17 @@
/*
* Data transfer object for refunds.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
import java.math.BigDecimal;
/**
* Data Transfer Object for refund processing.
*/
public class RefundDTO {
// Response fields
private Long id;

View File

@@ -1,8 +1,18 @@
/*
* Data transfer object for sales.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
import java.math.BigDecimal;
import java.util.List;
/**
* Data Transfer Object for sales transactions.
*/
public class SaleDTO {
// Response fields
private Long saleId;

View File

@@ -1,5 +1,15 @@
/*
* Request object for sending a new chat message.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
/**
* Request body for sending a new chat message.
*/
public class SendMessageRequest {
private String content;

View File

@@ -1,5 +1,15 @@
/*
* Data transfer object for services.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
/**
* Data Transfer Object for services.
*/
public class ServiceDTO {
private Long serviceId;
private String serviceName;

View File

@@ -1,5 +1,15 @@
/*
* Data transfer object for store information.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
/**
* Data Transfer Object for store information.
*/
public class StoreDTO {
private Long storeId;
private String storeName;

View File

@@ -1,5 +1,15 @@
/*
* Data transfer object for suppliers.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
/**
* Data Transfer Object for suppliers.
*/
public class SupplierDTO {
private Long supId;
private String supCompany;

View File

@@ -1,5 +1,15 @@
/*
* Request object for changing a conversation's status.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
/**
* Request body for updating chat conversation status.
*/
public class UpdateConversationStatusRequest {
private String status;

View File

@@ -1,5 +1,15 @@
/*
* Data transfer object for user account information.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.dtos;
/**
* Data Transfer Object for user account details.
*/
public class UserDTO {
private Long id;
private String username;

View File

@@ -1,3 +1,10 @@
/*
* Fragment for the real-time chat screen.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.fragments;
import android.app.Activity;
@@ -65,6 +72,9 @@ import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
/**
* Fragment for handling customer support chat.
*/
@AndroidEntryPoint
public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickListener, StompChatManager.MessageListener,
StompChatManager.ConversationListener, StompChatManager.ConnectionListener {
@@ -90,6 +100,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
private StompChatManager stompChatManager;
private ActivityResultLauncher<Intent> attachmentLauncher;
/**
* Initializes the view model and attachment launcher.
*/
@Override
public void onCreate(Bundle 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
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
@@ -137,6 +153,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
return binding.getRoot();
}
/**
* Sets up the logic to open and close the chat drawer.
*/
private void setupDrawerToggles() {
binding.headerActiveChats.setOnClickListener(v -> {
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() {
activeChatAdapter = new ChatAdapter(activeChatList, this);
binding.rvActiveChats.setLayoutManager(new LinearLayoutManager(getContext()));
@@ -187,6 +209,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
setConversationActive(false, null);
}
/**
* Displays a full-screen image preview for message attachments.
*/
private void showFullScreenImage(Message message) {
if (baseUrl == null || message.getId() == null) return;
@@ -208,6 +233,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
dialog.show();
}
/**
* Initiates the download process for a message attachment.
*/
private void downloadFile(Message message) {
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) {
android.os.Handler mainHandler = new android.os.Handler(android.os.Looper.getMainLooper());
new Thread(() -> {
@@ -270,6 +301,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
}).start();
}
/**
* Observes LiveData from the ViewModel to update chat lists and messages.
*/
private void observeViewModel() {
viewModel.getActiveChats().observe(getViewLifecycleOwner(), list -> {
activeChatList.clear();
@@ -300,12 +334,18 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
viewModel.getIsLoading().observe(getViewLifecycleOwner(), this::setLoading);
}
/**
* Toggles the visibility of the progress bar.
*/
private void setLoading(boolean loading) {
if (binding != null && binding.progressBar != null) {
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) {
if (activeConversationId != null) {
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() {
String token = tokenManager.getToken();
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
public void onChatClick(Chat chat) {
activeConversationId = Long.parseLong(chat.getChatId());
@@ -368,6 +414,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
viewModel.loadMessageHistory(activeConversationId);
}
/**
* Closes the active chat conversation.
*/
private void closeChat() {
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() {
if (activeConversationId == null) return;
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() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
attachmentLauncher.launch(intent);
}
/**
* Displays a preview of the selected attachment.
*/
private void showAttachmentPreview(Uri uri) {
pendingAttachmentUri = uri;
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() {
pendingAttachmentUri = null;
binding.layoutAttachmentPreview.setVisibility(View.GONE);
}
/**
* Sends a message with a file attachment.
*/
private void sendWithAttachment(Uri uri) {
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
public void onMessageReceived(MessageDTO dto) {
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
public void onConversationUpdated(ConversationDTO dto) {
requireActivity().runOnUiThread(() -> {
@@ -489,6 +559,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
});
}
/**
* Callback for when the WebSocket connection is successfully opened.
*/
@Override
public void onSocketOpened() {
if (!isAdded()) return;
@@ -498,12 +571,18 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
});
}
/**
* Callback for when the WebSocket connection is closed.
*/
@Override
public void onSocketClosed() {
if (!isAdded()) return;
requireActivity().runOnUiThread(viewModel::loadConversations);
}
/**
* Callback for when a WebSocket error occurs.
*/
@Override
public void onSocketError() {
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() {
if (!messageList.isEmpty()) {
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) {
boolean isClosed = "CLOSED".equalsIgnoreCase(status);
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
public void onDestroyView() {
super.onDestroyView();

View File

@@ -1,3 +1,10 @@
/*
* Base fragment that provides common list and search functionality.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.fragments;
import android.os.Bundle;
@@ -23,7 +30,9 @@ import javax.inject.Inject;
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
public class ListFragment extends Fragment {
@@ -97,6 +106,9 @@ public class ListFragment extends Fragment {
return binding.getRoot();
}
/**
* Cleans up the binding when the view is destroyed.
*/
@Override
public void onDestroyView() {
super.onDestroyView();

View File

@@ -1,3 +1,10 @@
/*
* Fragment for viewing and editing the user's profile.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.fragments;
import android.net.Uri;
@@ -173,12 +180,18 @@ public class ProfileFragment extends Fragment {
return binding.getRoot();
}
/**
* Toggles the visibility of the progress bar.
*/
private void setLoading(boolean loading) {
if (binding != null && binding.progressBar != null) {
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
}
}
/**
* Cleans up the binding when the view is destroyed.
*/
@Override
public void onDestroyView() {
super.onDestroyView();

View File

@@ -1,3 +1,10 @@
/*
* Fragment for browsing the activity log.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.fragments.listfragments;
import android.app.DatePickerDialog;
@@ -34,6 +41,9 @@ import java.util.List;
import dagger.hilt.android.AndroidEntryPoint;
/**
* Fragment for viewing application activity logs with various filtering options.
*/
@AndroidEntryPoint
public class ActivityLogFragment extends Fragment {
private FragmentActivityLogBinding binding;
@@ -46,6 +56,9 @@ public class ActivityLogFragment extends Fragment {
@Inject TokenManager tokenManager;
/**
* Inflates the layout, checks for admin access, and initializes ViewModel and UI components.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -69,12 +82,18 @@ public class ActivityLogFragment extends Fragment {
return binding.getRoot();
}
/**
* Triggers initial data loading after the view is created.
*/
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewModel.loadInitialData();
}
/**
* Configures the RecyclerView and its scroll listener for pagination.
*/
private void setupRecyclerView() {
adapter = new ActivityLogAdapter(logList);
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() {
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter,
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) {
Calendar cal = Calendar.getInstance();
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();
}
/**
* Handles store selection from the filter spinner.
*/
private void onStoreSelected() {
int pos = binding.spinnerStoreFilter.getSelectedItemPosition();
Long storeId = (pos > 0 && !storeList.isEmpty()) ? storeList.get(pos - 1).getId() : null;
viewModel.setStoreFilter(storeId);
}
/**
* Observes the ViewModel for log list updates, store options, and loading status.
*/
private void observeViewModel() {
viewModel.getLogs().observe(getViewLifecycleOwner(), list -> {
logList.clear();
@@ -164,6 +195,9 @@ public class ActivityLogFragment extends Fragment {
binding.swipeRefreshActivityLog.setRefreshing(loading));
}
/**
* Cleans up the binding reference.
*/
@Override
public void onDestroyView() {
super.onDestroyView();

View File

@@ -1,3 +1,10 @@
/*
* Fragment for browsing adoption records.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.fragments.listfragments;
import android.graphics.Color;
@@ -44,6 +51,9 @@ import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
/**
* Fragment for displaying and managing a list of adoptions with a calendar view and filtering.
*/
@AndroidEntryPoint
public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdoptionClickListener {
@@ -58,12 +68,18 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
@Inject TokenManager tokenManager;
/**
* Initializes the ViewModel.
*/
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(AdoptionListViewModel.class);
}
/**
* Inflates the layout and sets up UI components, calendar, and observers.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -88,6 +104,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
return binding.getRoot();
}
/**
* Observes the ViewModel for adoption list, stores, and loading status.
*/
private void observeViewModel() {
viewModel.getAdoptions().observe(getViewLifecycleOwner(), list -> {
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() {
bulkDeleteHandler = new BulkDeleteHandler(
this,
@@ -119,6 +141,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
);
}
/**
* Reloads adoption data and stores when the fragment resumes.
*/
@Override
public void onResume() {
super.onResume();
@@ -126,6 +151,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
if (!isStaff()) viewModel.loadStores();
}
/**
* Toggles between month and week display modes for the calendar.
*/
private void toggleCalendarMode() {
isMonthMode = !isMonthMode;
binding.calendarViewAdoption.state().edit()
@@ -133,6 +161,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
.commit();
}
/**
* Sets up the filter visibility toggle, considering user roles.
*/
private void setupFilterToggle() {
if (isStaff()) {
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() {
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
}
/**
* Configures the calendar view for date-based filtering.
*/
private void setupCalendar() {
binding.calendarViewAdoption.setOnDateChangedListener((widget, date, 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() {
HashSet<CalendarDay> datesWithAdoptions = new HashSet<>();
for (AdoptionDTO adoption : adoptionList) {
@@ -184,6 +224,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
binding.calendarViewAdoption.addDecorator(new EventDecorator(Color.RED, datesWithAdoptions));
}
/**
* Configures the RecyclerView and its scroll listener for pagination.
*/
private void setupRecyclerView() {
adapter = new AdoptionAdapter(adoptionList, this);
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() {
UIUtils.attachSearch(binding.etSearchAdoption, () -> loadAdoptions(true));
}
/**
* Configures the status filter spinner.
*/
private void setupStatusFilter() {
String[] statuses = {"All Statuses", "Completed", "Pending", "Missed", "Cancelled"};
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusAdoption, statuses, () -> loadAdoptions(true));
}
/**
* Configures the store filter spinner.
*/
private void setupStoreFilter() {
SpinnerUtils.setupFilterSpinner(binding.spinnerStoreAdoption, () -> loadAdoptions(true));
}
/**
* Configures the swipe-to-refresh layout.
*/
private void setupSwipeRefresh() {
binding.swipeRefreshAdoption.setOnRefreshListener(() -> loadAdoptions(true));
}
/**
* Loads adoption data based on current filters, search query, and selected date.
*/
private void loadAdoptions(boolean reset) {
String query = binding.etSearchAdoption.getText().toString().trim();
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);
}
/**
* Navigates to the adoption detail screen.
*/
private void openDetail(int position) {
Bundle args = new Bundle();
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);
}
/**
* Handles adoption item clicks by opening details.
*/
@Override
public void onAdoptionClick(int position) { openDetail(position); }
/**
* Forwards selection changes to the bulk delete handler.
*/
@Override
public void onSelectionChanged(int selectedCount) {
if (bulkDeleteHandler != null) {
@@ -269,6 +336,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
}
}
/**
* Cleans up the binding reference.
*/
@Override
public void onDestroyView() {
super.onDestroyView();

View File

@@ -1,3 +1,10 @@
/*
* Fragment for displaying store analytics and charts.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.fragments.listfragments;
import android.graphics.Color;
@@ -18,6 +25,9 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
/**
* Fragment for displaying business analytics, including revenue, transactions, and product performance.
*/
@AndroidEntryPoint
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 int[] TOP_N_VALUES = { 5, 10, 15, 20 };
/**
* Inflates the layout, initializes ViewModel, and sets up UI components and filters.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -51,6 +64,9 @@ public class AnalyticsFragment extends Fragment {
private static final int COLOR_SELECTED = 0xFF4ECDC4;
private static final int COLOR_UNSELECTED = 0xFFCBD5E1;
/**
* Configures the view mode toggle buttons (My Analytics vs Store Analytics).
*/
private void setupViewModeToggle() {
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) {
binding.btnMyAnalytics.setBackgroundTintList(
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));
}
/**
* Updates the visibility of the store filter based on the user's role and selected view mode.
*/
private void updateStoreFilterVisibility(String mode) {
boolean isAdmin = "ADMIN".equalsIgnoreCase(tokenManager.getRole());
int vis = (isAdmin && mode.equals("store")) ? View.VISIBLE : View.GONE;
@@ -81,8 +103,10 @@ public class AnalyticsFragment extends Fragment {
binding.spinnerFilterStore.setVisibility(vis);
}
// Filter Panel
/**
* Configures the filter panel, including date pickers, presets, and action buttons.
*/
private void setupFilterPanel() {
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerTopN, TOP_N_OPTIONS);
@@ -111,12 +135,18 @@ public class AnalyticsFragment extends Fragment {
binding.btnFilterReset.setOnClickListener(v -> resetFilters());
}
/**
* Toggles the visibility of the filter content section.
*/
private void toggleFilters() {
filtersExpanded = !filtersExpanded;
binding.llFilterContent.setVisibility(filtersExpanded ? View.VISIBLE : View.GONE);
binding.tvFilterToggleIcon.setText(filtersExpanded ? "" : "");
}
/**
* Applies a date range preset to the filter fields.
*/
private void applyPreset(int startOffset, int endOffset) {
binding.etFilterStartDate.setText(getDateString(startOffset));
binding.etFilterEndDate.setText(getDateString(endOffset));
@@ -124,6 +154,9 @@ public class AnalyticsFragment extends Fragment {
applyFiltersFromUI();
}
/**
* Reads filter values from the UI and applies them to the ViewModel.
*/
private void applyFiltersFromUI() {
AnalyticsViewModel.FilterState filter = new AnalyticsViewModel.FilterState();
filter.startDate = binding.etFilterStartDate.getText().toString().trim();
@@ -142,6 +175,9 @@ public class AnalyticsFragment extends Fragment {
viewModel.applyFilter(filter);
}
/**
* Resets all filters to their default values.
*/
private void resetFilters() {
binding.etFilterStartDate.setText("");
binding.etFilterEndDate.setText("");
@@ -152,6 +188,9 @@ public class AnalyticsFragment extends Fragment {
viewModel.resetFilter();
}
/**
* Updates the text summary of the currently selected date range.
*/
private void updateFilterSummary() {
String start = binding.etFilterStartDate.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) {
return (date != null && date.length() >= 10) ? date.substring(5) : date;
}
/**
* Returns a formatted date string for a given day.
*/
private String getDateString(int offsetDays) {
Calendar c = Calendar.getInstance();
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));
}
// ViewModel Observation
/**
* Observes the ViewModel for analytics data, loading status, errors, and filter options.
*/
private void observeViewModel() {
viewModel.getAnalyticsData().observe(getViewLifecycleOwner(), this::computeAndDisplay);
@@ -216,14 +263,19 @@ public class AnalyticsFragment extends Fragment {
});
}
/**
* Cleans up the binding reference.
*/
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
// Display
/**
* Computes and displays analytics data in summary cards and bar charts.
*/
private void computeAndDisplay(AnalyticsViewModel.AnalyticsData data) {
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) {
if (getContext() == null) return;
LinearLayout row = new LinearLayout(getContext());
@@ -359,6 +413,9 @@ public class AnalyticsFragment extends Fragment {
parent.addView(row);
}
/**
* Adds an empty message row to a given layout container.
*/
private void addEmptyRow(LinearLayout parent, String message) {
if (getContext() == null) return;
TextView tv = new TextView(getContext());
@@ -368,6 +425,9 @@ public class AnalyticsFragment extends Fragment {
parent.addView(tv);
}
/**
* Displays an error message and updates UI to reflect the error state.
*/
private void showError(String msg) {
if (getContext() == null || binding == null) return;
binding.tvTotalRevenue.setText("Error");

View File

@@ -1,3 +1,10 @@
/*
* Fragment for browsing appointments.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.fragments.listfragments;
import android.graphics.Color;
@@ -45,6 +52,9 @@ import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
/**
* Fragment for displaying and managing a list of appointments with calendar integration and filtering.
*/
@AndroidEntryPoint
public class AppointmentFragment extends Fragment implements AppointmentAdapter.OnAppointmentClickListener {
@@ -63,6 +73,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
private Long currentUserId = null;
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
/**
* Initializes ViewModels for appointment and authentication data.
*/
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -70,6 +83,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
authViewModel = new ViewModelProvider(this).get(AuthViewModel.class);
}
/**
* Inflates the layout and sets up UI components, calendar, and observers.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -97,6 +113,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
return binding.getRoot();
}
/**
* Observes the ViewModel for appointment list, stores, and loading status.
*/
private void observeViewModel() {
viewModel.getAppointments().observe(getViewLifecycleOwner(), list -> {
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() {
bulkDeleteHandler = new BulkDeleteHandler(
this,
@@ -128,6 +150,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
);
}
/**
* Reloads appointment data and stores when the fragment resumes.
*/
@Override
public void onResume() {
super.onResume();
@@ -135,6 +160,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
if (!isStaff()) viewModel.loadStores();
}
/**
* Toggles between month and week display modes for the calendar.
*/
private void toggleCalendarMode() {
isMonthMode = !isMonthMode;
binding.calendarView.state().edit()
@@ -142,12 +170,18 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
.commit();
}
/**
* Sets up the "My Appointments" filter button.
*/
private void setupMyAppointmentFilter() {
binding.btnMyAppointments.setOnClickListener(v -> {
loadAppointmentData(true);
});
}
/**
* Loads information about the currently logged-in user.
*/
private void loadCurrentUserInfo() {
authViewModel.getMe().observe(getViewLifecycleOwner(), resource -> {
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() {
if (isStaff()) {
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() {
binding.calendarView.setOnDateChangedListener((widget, date, 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() {
HashSet<CalendarDay> datesWithAppointments = new HashSet<>();
for (AppointmentDTO appointment : appointmentList) {
@@ -200,23 +243,38 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
binding.calendarView.addDecorator(new EventDecorator(Color.RED, datesWithAppointments));
}
/**
* Sets up the search input listener.
*/
private void setupSearch() {
UIUtils.attachSearch(binding.etSearchAppointment, () -> loadAppointmentData(true));
}
/**
* Configures the status filter spinner.
*/
private void setupStatusFilter() {
String[] statuses = {"All Statuses", "Booked", "Completed", "Cancelled", "Missed"};
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatus, statuses, () -> loadAppointmentData(true));
}
/**
* Configures the store filter spinner.
*/
private void setupStoreFilter() {
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadAppointmentData(true));
}
/**
* Configures the swipe-to-refresh layout.
*/
private void setupSwipeRefresh() {
binding.swipeRefreshAppointment.setOnRefreshListener(() -> loadAppointmentData(true));
}
/**
* Navigates to the appointment detail screen.
*/
private void openAppointmentDetails(int position) {
Bundle args = new Bundle();
if (position != -1) {
@@ -226,11 +284,17 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
NavHostFragment.findNavController(this).navigate(R.id.nav_appointment_detail, args);
}
/**
* Handles appointment item clicks by opening details.
*/
@Override
public void onAppointmentClick(int position) {
openAppointmentDetails(position);
}
/**
* Forwards selection changes to the bulk delete handler.
*/
@Override
public void onSelectionChanged(int count) {
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() {
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
}
/**
* Loads appointment data based on current filters, search query, and selected date.
*/
private void loadAppointmentData(boolean reset) {
String query = binding.etSearchAppointment.getText().toString().trim();
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);
}
/**
* Configures the RecyclerView and its scroll listener for pagination.
*/
private void setupRecyclerView() {
adapter = new AppointmentAdapter(appointmentList, this);
binding.recyclerViewAppointments.setLayoutManager(new LinearLayoutManager(getContext()));
@@ -296,6 +369,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
});
}
/**
* Cleans up the binding reference.
*/
@Override
public void onDestroyView() {
super.onDestroyView();

View File

@@ -1,3 +1,10 @@
/*
* Fragment for browsing coupons.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.fragments.listfragments;
import android.os.Bundle;
@@ -27,6 +34,9 @@ import java.util.List;
import dagger.hilt.android.AndroidEntryPoint;
/**
* Fragment for displaying and managing a list of coupons.
*/
@AndroidEntryPoint
public class CouponFragment extends Fragment implements CouponAdapter.OnCouponClickListener {
private FragmentCouponBinding binding;
@@ -34,6 +44,9 @@ public class CouponFragment extends Fragment implements CouponAdapter.OnCouponCl
private CouponAdapter adapter;
private final List<CouponDTO> couponList = new ArrayList<>();
/**
* Inflates the layout, initializes ViewModel, and sets up UI components.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -59,6 +72,9 @@ public class CouponFragment extends Fragment implements CouponAdapter.OnCouponCl
return binding.getRoot();
}
/**
* Observes the ViewModel for coupon list updates and loading status.
*/
private void observeViewModel() {
viewModel.getCoupons().observe(getViewLifecycleOwner(), list -> {
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() {
adapter = new CouponAdapter(couponList, this);
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() {
String[] statuses = {"All Statuses", "Active", "Inactive"};
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusCoupon, statuses, () -> applyFilters(true));
}
/**
* Configures the discount type filter spinner.
*/
private void setupTypeFilter() {
String[] types = {"All Types", "FIXED", "PERCENT"};
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerTypeCoupon, types, () -> applyFilters(true));
}
/**
* Sets up the search input listener.
*/
private void setupSearch() {
UIUtils.attachSearch(binding.etSearchCoupon, () -> applyFilters(true));
}
/**
* Configures the swipe-to-refresh layout.
*/
private void setupSwipeRefresh() {
binding.swipeRefreshCoupon.setOnRefreshListener(() -> applyFilters(true));
}
/**
* Applies filters and loads the coupon list.
*/
private void applyFilters(boolean reset) {
String statusStr = binding.spinnerStatusCoupon.getSelectedItem() != null ?
binding.spinnerStatusCoupon.getSelectedItem().toString() : "All Statuses";
@@ -122,25 +156,39 @@ public class CouponFragment extends Fragment implements CouponAdapter.OnCouponCl
binding.spinnerTypeCoupon.getSelectedItem().toString() : "All Types";
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) {
Bundle args = new Bundle();
args.putLong("couponId", id);
Navigation.findNavController(requireView()).navigate(R.id.couponDetailFragment, args);
}
/**
* Handles coupon item clicks by opening details.
*/
@Override
public void onCouponClick(CouponDTO coupon) {
openDetail(coupon.getCouponId());
}
/**
* Shows or hides the bulk delete button based on selection count.
*/
@Override
public void onSelectionChanged(int count) {
binding.btnBulkDeleteCoupons.setVisibility(count > 0 ? View.VISIBLE : View.GONE);
}
/**
* Displays a confirmation dialog for deleting multiple coupons.
*/
private void confirmBulkDelete() {
new AlertDialog.Builder(requireContext())
.setTitle("Confirm Bulk Delete")

View File

@@ -1,3 +1,10 @@
/*
* Fragment for browsing customers.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.fragments.listfragments;
import android.os.Bundle;
@@ -22,6 +29,9 @@ import java.util.*;
import javax.inject.Inject;
import javax.inject.Named;
/**
* Fragment for displaying and managing a list of customers.
*/
@AndroidEntryPoint
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 TokenManager tokenManager;
/**
* Inflates the layout, initializes ViewModel, and sets up UI components and filters.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -45,17 +58,21 @@ public class CustomerFragment extends Fragment implements CustomerAdapter.OnCust
setupSwipeRefresh();
observeViewModel();
viewModel.resetFilters();
viewModel.loadCustomers(true);
binding.fabAddCustomer.setOnClickListener(v -> openDetail(-1));
UIUtils.setupHamburgerMenu(binding.btnHamburgerCustomer, this);
UIUtils.setupFilterToggle(binding.btnToggleFilterCustomer, binding.layoutFilterCustomer,
binding.etSearchCustomer, binding.spinnerStatusCustomer);
binding.etSearchCustomer, this::applyFilters, binding.spinnerStatusCustomer);
return binding.getRoot();
}
/**
* Observes the ViewModel for customer list updates and loading status.
*/
private void observeViewModel() {
viewModel.getFilteredCustomers().observe(getViewLifecycleOwner(), list -> {
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() {
adapter = new CustomerAdapter(customerList, this);
adapter.setBaseUrl(baseUrl);
@@ -92,15 +112,24 @@ public class CustomerFragment extends Fragment implements CustomerAdapter.OnCust
});
}
/**
* Configures the status filter spinner.
*/
private void setupStatusFilter() {
String[] statuses = {"All Statuses", "Active", "Inactive"};
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusCustomer, statuses, this::applyFilters);
}
/**
* Sets up the search input listener.
*/
private void setupSearch() {
UIUtils.attachSearch(binding.etSearchCustomer, this::applyFilters);
}
/**
* Applies filters and triggers data reloading or filtering in ViewModel.
*/
private void applyFilters() {
String query = binding.etSearchCustomer.getText().toString().trim();
String status = binding.spinnerStatusCustomer.getSelectedItem() != null ?
@@ -108,10 +137,16 @@ public class CustomerFragment extends Fragment implements CustomerAdapter.OnCust
viewModel.filter(query, status);
}
/**
* Configures the swipe-to-refresh layout.
*/
private void setupSwipeRefresh() {
binding.swipeRefreshCustomer.setOnRefreshListener(() -> viewModel.loadCustomers(true));
}
/**
* Navigates to the customer detail screen.
*/
private void openDetail(int position) {
Bundle args = new Bundle();
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);
}
/**
* Handles customer item clicks by opening details.
*/
@Override
public void onCustomerClick(int position) {
openDetail(position);
}
/**
* Cleans up the binding reference.
*/
@Override
public void onDestroyView() {
super.onDestroyView();

View File

@@ -1,3 +1,10 @@
/*
* Fragment for browsing inventory items.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.fragments.listfragments;
import android.os.Bundle;
@@ -32,6 +39,9 @@ import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
/**
* Fragment for displaying and managing inventory items.
*/
@AndroidEntryPoint
public class InventoryFragment extends Fragment implements InventoryAdapter.OnInventoryClickListener {
@@ -43,12 +53,18 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
@Inject TokenManager tokenManager;
/**
* Initializes the ViewModel.
*/
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(InventoryListViewModel.class);
}
/**
* Inflates the layout and sets up UI components, filters, and observers.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -71,6 +87,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
return binding.getRoot();
}
/**
* Observes the ViewModel for inventory list updates, store list, and loading status.
*/
private void observeViewModel() {
viewModel.getInventory().observe(getViewLifecycleOwner(), list -> {
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() {
bulkDeleteHandler = new BulkDeleteHandler(
this,
@@ -101,18 +123,27 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
);
}
/**
* Reloads store data if necessary when the fragment resumes.
*/
@Override
public void onResume() {
super.onResume();
if (!isStaff()) viewModel.loadStores();
}
/**
* Cleans up the binding reference.
*/
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
/**
* Sets up the filter visibility toggle, considering user roles.
*/
private void setupFilterToggle() {
if (isStaff()) {
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() {
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
}
/**
* Sets up the search input listener.
*/
private void setupSearch() {
UIUtils.attachSearch(binding.etSearchInventory, () -> loadInventory(true));
}
/**
* Configures the store filter spinner.
*/
private void setupStoreFilter() {
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadInventory(true));
}
/**
* Configures the RecyclerView and its scroll listener for pagination.
*/
private void setupRecyclerView() {
adapter = new InventoryAdapter(inventoryList, this);
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() {
binding.swipeRefreshInventory.setOnRefreshListener(() -> loadInventory(true));
}
/**
* Loads inventory data based on current search query and store filter.
*/
private void loadInventory(boolean reset) {
String query = binding.etSearchInventory != null ? binding.etSearchInventory.getText().toString().trim() : "";
if (query.isEmpty()) query = null;
@@ -178,6 +227,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
viewModel.loadInventory(reset, query, storeId);
}
/**
* Navigates to the inventory detail screen.
*/
private void openDetail(InventoryDTO inv) {
Bundle args = new Bundle();
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);
}
/**
* Handles inventory item clicks by opening details.
*/
@Override
public void onInventoryClick(int position) {
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
public void onSelectionChanged(int selectedCount) {
if (bulkDeleteHandler != null) {

View File

@@ -1,3 +1,10 @@
/*
* Fragment for browsing pets.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.fragments.listfragments;
import android.os.Bundle;
@@ -33,6 +40,9 @@ import javax.inject.Named;
import dagger.hilt.android.AndroidEntryPoint;
/**
* Fragment for displaying and managing a list of pets.
*/
@AndroidEntryPoint
public class PetFragment extends Fragment implements PetAdapter.OnPetClickListener {
private FragmentPetBinding binding;
@@ -44,12 +54,18 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
@Inject @Named("baseUrl") String baseUrl;
@Inject TokenManager tokenManager;
/**
* Initializes the view model.
*/
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(PetListViewModel.class);
}
/**
* Inflates the layout and initializes UI components and filters.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -72,6 +88,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
return binding.getRoot();
}
/**
* Observes LiveData from the ViewModel to update the list and filter options.
*/
private void observeViewModel() {
viewModel.getPets().observe(getViewLifecycleOwner(), list -> {
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() {
bulkDeleteHandler = new BulkDeleteHandler(
this,
@@ -107,6 +129,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
);
}
/**
* Refreshes pet data and filters when the fragment is resumed.
*/
@Override
public void onResume() {
super.onResume();
@@ -115,6 +140,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
if (!isStaff()) viewModel.loadStores();
}
/**
* Sets up the visibility of filters based on the user's role.
*/
private void setupFilterToggle() {
if (isStaff()) {
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() {
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
}
/**
* Attaches search functionality to the search input field.
*/
private void setupSearch() {
UIUtils.attachSearch(binding.etSearchPet, () -> loadPetData(true));
}
/**
* Initializes the status filter spinner.
*/
private void setupStatusFilter() {
String[] statuses = {"All Statuses", "Available", "Adopted", "Owned", "Pending"};
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatus, statuses, () -> loadPetData(true));
}
/**
* Initializes the species filter spinner.
*/
private void setupSpeciesFilter() {
String[] initial = {"All Species"};
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerSpecies, initial, () -> loadPetData(true));
}
/**
* Initializes the store filter spinner.
*/
private void setupStoreFilter() {
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadPetData(true));
}
/**
* Configures the swipe-to-refresh layout.
*/
private void setupSwipeRefresh() {
binding.swipeRefreshPet.setOnRefreshListener(() -> loadPetData(true));
}
/**
* Triggers loading of pet data from the backend with current filters.
*/
private void loadPetData(boolean reset) {
String query = binding.etSearchPet.getText().toString().trim();
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);
}
/**
* Configures the RecyclerView and its scroll listener for pagination.
*/
private void setupRecyclerView() {
adapter = new PetAdapter(petList, this);
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) {
Bundle args = new Bundle();
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);
}
/**
* Navigates to the screen for adding a new pet.
*/
private void openPetDetails() {
NavHostFragment.findNavController(this).navigate(R.id.nav_pet_detail);
}
/**
* Handles clicks on individual pets in the list.
*/
@Override
public void onPetClick(int position) {
openPetProfile(position);
}
/**
* Notifies the bulk delete handler when item selection changes.
*/
@Override
public void onSelectionChanged(int selectedCount) {
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
public void onDestroyView() {
super.onDestroyView();

View File

@@ -1,3 +1,10 @@
/*
* Fragment for browsing products.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.fragments.listfragments;
import android.os.Bundle;
@@ -31,6 +38,9 @@ import javax.inject.Named;
import dagger.hilt.android.AndroidEntryPoint;
/**
* Fragment for displaying and managing a list of products.
*/
@AndroidEntryPoint
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;
/**
* Initializes the ViewModel.
*/
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(ProductListViewModel.class);
}
/**
* Inflates the layout and sets up UI components.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -66,6 +82,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
return binding.getRoot();
}
/**
* Observes the ViewModel for product list, categories, and loading status.
*/
private void observeViewModel() {
viewModel.getProducts().observe(getViewLifecycleOwner(), list -> {
productList.clear();
@@ -83,6 +102,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
});
}
/**
* Reloads product data and categories when the fragment resumes.
*/
@Override
public void onResume() {
super.onResume();
@@ -90,23 +112,38 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
viewModel.loadCategories();
}
/**
* Sets up the filter visibility toggle.
*/
private void setupFilterToggle() {
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter,
binding.etSearchProduct, binding.spinnerCategory);
}
/**
* Sets up the search input listener.
*/
private void setupSearch() {
UIUtils.attachSearch(binding.etSearchProduct, () -> loadProductData(true));
}
/**
* Configures the category filter spinner.
*/
private void setupCategoryFilter() {
SpinnerUtils.setupFilterSpinner(binding.spinnerCategory, () -> loadProductData(true));
}
/**
* Configures the swipe-to-refresh layout.
*/
private void setupSwipeRefresh() {
binding.swipeRefreshProduct.setOnRefreshListener(() -> loadProductData(true));
}
/**
* Loads product data based on current filters and search query.
*/
private void loadProductData(boolean reset) {
String query = binding.etSearchProduct.getText().toString().trim();
if (query.isEmpty()) query = null;
@@ -120,6 +157,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
viewModel.loadProducts(reset, query, categoryId);
}
/**
* Configures the RecyclerView and its scroll listener for pagination.
*/
private void setupRecyclerView() {
adapter = new ProductAdapter(productList, this);
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) {
Bundle args = new Bundle();
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);
}
/**
* Handles product item clicks by opening details.
*/
@Override
public void onProductClick(int position) {
openProductDetails(position);
}
/**
* Cleans up the binding reference.
*/
@Override
public void onDestroyView() {
super.onDestroyView();

View File

@@ -1,3 +1,10 @@
/*
* Fragment for browsing product-supplier links.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.fragments.listfragments;
import android.os.Bundle;
@@ -29,6 +36,9 @@ import java.util.List;
import dagger.hilt.android.AndroidEntryPoint;
/**
* Fragment for displaying and managing the relationships between products and suppliers.
*/
@AndroidEntryPoint
public class ProductSupplierFragment extends Fragment
implements ProductSupplierAdapter.OnProductSupplierClickListener {
@@ -40,12 +50,18 @@ public class ProductSupplierFragment extends Fragment
private ProductSupplierListViewModel viewModel;
private BulkDeleteHandler bulkDeleteHandler;
/**
* Initializes the ViewModel.
*/
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(ProductSupplierListViewModel.class);
}
/**
* Inflates the layout and sets up UI components, filters, and observers.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -67,6 +83,9 @@ public class ProductSupplierFragment extends Fragment
return binding.getRoot();
}
/**
* Observes the ViewModel for product-supplier list, products, suppliers, and loading status.
*/
private void observeViewModel() {
viewModel.getProductSuppliers().observe(getViewLifecycleOwner(), list -> {
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() {
bulkDeleteHandler = new BulkDeleteHandler(
this,
@@ -102,6 +124,9 @@ public class ProductSupplierFragment extends Fragment
);
}
/**
* Reloads data and filter options when the fragment resumes.
*/
@Override
public void onResume() {
super.onResume();
@@ -109,17 +134,26 @@ public class ProductSupplierFragment extends Fragment
viewModel.loadFilterData();
}
/**
* Cleans up the binding reference.
*/
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
/**
* Sets up the filter visibility toggle.
*/
private void setupFilterToggle() {
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPS,
binding.spinnerProduct, binding.spinnerSupplier);
}
/**
* Configures the RecyclerView and its scroll listener for pagination.
*/
private void setupRecyclerView() {
adapter = new ProductSupplierAdapter(psList, this);
binding.recyclerViewPS.setLayoutManager(new LinearLayoutManager(getContext()));
@@ -142,22 +176,37 @@ public class ProductSupplierFragment extends Fragment
});
}
/**
* Sets up the search input listener.
*/
private void setupSearch() {
UIUtils.attachSearch(binding.etSearchPS, () -> loadData(true));
}
/**
* Configures the product filter spinner.
*/
private void setupProductFilter() {
SpinnerUtils.setupFilterSpinner(binding.spinnerProduct, () -> loadData(true));
}
/**
* Configures the supplier filter spinner.
*/
private void setupSupplierFilter() {
SpinnerUtils.setupFilterSpinner(binding.spinnerSupplier, () -> loadData(true));
}
/**
* Configures the swipe-to-refresh layout.
*/
private void setupSwipeRefresh() {
binding.swipeRefreshPS.setOnRefreshListener(() -> loadData(true));
}
/**
* Loads product-supplier data based on current search query and filters.
*/
private void loadData(boolean reset) {
String query = binding.etSearchPS.getText().toString().trim();
if (query.isEmpty()) query = null;
@@ -177,6 +226,9 @@ public class ProductSupplierFragment extends Fragment
viewModel.loadProductSuppliers(reset, query, productId, supplierId);
}
/**
* Navigates to the product-supplier detail screen.
*/
private void openDetail(int position) {
Bundle args = new Bundle();
if (position != -1) {
@@ -187,9 +239,15 @@ public class ProductSupplierFragment extends Fragment
NavHostFragment.findNavController(this).navigate(R.id.nav_product_supplier_detail, args);
}
/**
* Handles product-supplier item clicks by opening details.
*/
@Override
public void onProductSupplierClick(int position) { openDetail(position); }
/**
* Forwards selection changes to the bulk delete handler.
*/
@Override
public void onSelectionChanged(int count) {
if (bulkDeleteHandler != null) {

View File

@@ -1,3 +1,10 @@
/*
* Fragment for browsing purchase orders.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.fragments.listfragments;
import android.os.Bundle;
@@ -30,6 +37,9 @@ import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
/**
* Fragment for displaying and managing a list of purchase orders.
*/
@AndroidEntryPoint
public class PurchaseOrderFragment extends Fragment
implements PurchaseOrderAdapter.OnPurchaseOrderClickListener {
@@ -41,12 +51,18 @@ public class PurchaseOrderFragment extends Fragment
@Inject TokenManager tokenManager;
/**
* Initializes the ViewModel.
*/
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(PurchaseOrderListViewModel.class);
}
/**
* Inflates the layout and sets up UI components, filters, and observers.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -64,6 +80,9 @@ public class PurchaseOrderFragment extends Fragment
return binding.getRoot();
}
/**
* Observes the ViewModel for purchase order list, stores, and loading status.
*/
private void observeViewModel() {
viewModel.getPurchaseOrders().observe(getViewLifecycleOwner(), list -> {
poList.clear();
@@ -81,6 +100,9 @@ public class PurchaseOrderFragment extends Fragment
});
}
/**
* Reloads data and stores if necessary when the fragment resumes.
*/
@Override
public void onResume() {
super.onResume();
@@ -88,6 +110,9 @@ public class PurchaseOrderFragment extends Fragment
if (!isStaff()) viewModel.loadStores();
}
/**
* Sets up the filter visibility toggle
*/
private void setupFilterToggle() {
if (isStaff()) {
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() {
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
}
/**
* Sets up the search input listener.
*/
private void setupSearch() {
UIUtils.attachSearch(binding.etSearchPO, () -> loadData(true));
}
/**
* Configures the store filter spinner.
*/
private void setupStoreFilter() {
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadData(true));
}
/**
* Configures the RecyclerView and its scroll listener for pagination.
*/
private void setupRecyclerView() {
adapter = new PurchaseOrderAdapter(poList, this);
binding.recyclerViewPO.setLayoutManager(new LinearLayoutManager(getContext()));
@@ -131,10 +168,16 @@ public class PurchaseOrderFragment extends Fragment
});
}
/**
* Configures the swipe-to-refresh layout.
*/
private void setupSwipeRefresh() {
binding.swipeRefreshPO.setOnRefreshListener(() -> loadData(true));
}
/**
* Loads purchase order data based on current search query and store filter.
*/
private void loadData(boolean reset) {
String query = binding.etSearchPO != null ? binding.etSearchPO.getText().toString().trim() : "";
if (query.isEmpty()) query = null;
@@ -153,6 +196,9 @@ public class PurchaseOrderFragment extends Fragment
viewModel.loadPurchaseOrders(reset, query, storeId);
}
/**
* Navigates to the purchase order detail screen.
*/
private void openDetail(int position) {
Bundle args = new Bundle();
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);
}
/**
* Handles purchase order item clicks by opening details.
*/
@Override
public void onPurchaseOrderClick(int position) {
openDetail(position);
}
/**
* Cleans up the binding reference.
*/
@Override
public void onDestroyView() {
super.onDestroyView();

View File

@@ -1,3 +1,10 @@
/*
* Fragment for browsing sales.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.fragments.listfragments;
import android.os.Bundle;
@@ -31,6 +38,9 @@ import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
/**
* Fragment for displaying and managing a list of sales.
*/
@AndroidEntryPoint
public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickListener {
@@ -41,6 +51,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
@Inject TokenManager tokenManager;
/**
* Inflates the layout for this fragment.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -48,6 +61,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
return binding.getRoot();
}
/**
* Initializes UI components and observers after the view is created.
*/
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle 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));
}
/**
* Observes LiveData from the ViewModel to update the list and filter options.
*/
private void observeViewModel() {
viewModel.getSales().observe(getViewLifecycleOwner(), list -> {
saleList.clear();
@@ -101,6 +120,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
});
}
/**
* Refreshes lookup data when the fragment is resumed.
*/
@Override
public void onResume() {
super.onResume();
@@ -108,6 +130,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
viewModel.loadCustomers();
}
/**
* Sets up the visibility of filters.
*/
private void setupFilterToggle() {
if (isStaff()) {
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() {
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
}
/**
* Checks if the current user has the 'ADMIN' role.
*/
private boolean isAdmin() {
return "ADMIN".equalsIgnoreCase(tokenManager.getRole());
}
/**
* Initializes the store filter spinner.
*/
private void setupStoreFilter() {
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadSales(true));
}
/**
* Initializes the payment method filter spinner.
*/
private void setupPaymentMethodFilter() {
String[] paymentMethods = {"All Payments", "Cash", "Card"};
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerPaymentMethod, paymentMethods, () -> loadSales(true));
}
/**
* Initializes the refund status filter spinner.
*/
private void setupRefundStatusFilter() {
String[] refundStatuses = {"All Status", "Sale", "Refund"};
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerRefundStatus, refundStatuses, () -> loadSales(true));
}
/**
* Initializes the customer filter spinner.
*/
private void setupCustomerFilter() {
SpinnerUtils.setupFilterSpinner(binding.spinnerCustomer, () -> loadSales(true));
}
/**
* Configures the RecyclerView and its scroll listener for pagination.
*/
private void setupRecyclerView() {
adapter = new SaleAdapter(saleList, this);
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() {
UIUtils.attachSearch(binding.etSearchSale, () -> loadSales(true));
}
/**
* Configures the swipe-to-refresh layout.
*/
private void setupSwipeRefresh() {
binding.swipeRefreshSale.setOnRefreshListener(() -> loadSales(true));
}
/**
* Triggers loading of sale data from the backend with current filters.
*/
private void loadSales(boolean reset) {
String query = binding.etSearchSale != null ? binding.etSearchSale.getText().toString().trim() : "";
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);
}
/**
* Handles clicks on individual sales in the list.
*/
@Override
public void onSaleClick(int position) {
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);
}
/**
* Cleans up the binding when the view is destroyed.
*/
@Override
public void onDestroyView() {
super.onDestroyView();

View File

@@ -1,3 +1,10 @@
/*
* Fragment for browsing services.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.fragments.listfragments;
import android.os.Bundle;
@@ -39,12 +46,18 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
private ServiceListViewModel viewModel;
private BulkDeleteHandler bulkDeleteHandler;
/**
* Initializes the ViewModel.
*/
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(ServiceListViewModel.class);
}
/**
* Inflates the layout and sets up UI components and observers.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -67,6 +80,9 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
return binding.getRoot();
}
/**
* Observes the ViewModel for service list updates and loading status.
*/
private void observeViewModel() {
viewModel.getServices().observe(getViewLifecycleOwner(), list -> {
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() {
bulkDeleteHandler = new BulkDeleteHandler(
this,
@@ -92,20 +111,32 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
);
}
/**
* Cleans up the binding reference.
*/
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
/**
* Sets up the filter visibility toggle.
*/
private void setupFilterToggle() {
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchService);
}
/**
* Sets up the search input listener.
*/
private void setupSearch() {
UIUtils.attachSearch(binding.etSearchService, () -> loadServices(true));
}
/**
* Configures the RecyclerView and its scroll listener for pagination.
*/
private void setupRecyclerView() {
adapter = new ServiceAdapter(serviceList, this);
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() {
binding.swipeRefreshService.setOnRefreshListener(() -> loadServices(true));
}
/**
* Loads service data based on current filters and search query.
*/
private void loadServices(boolean reset) {
String query = binding.etSearchService.getText().toString().trim();
if (query.isEmpty()) query = null;
viewModel.loadServices(reset, query);
}
/**
* Navigates to the service detail screen.
*/
private void openDetail(ServiceDTO service) {
Bundle args = new Bundle();
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);
}
/**
* Handles service item clicks by opening details.
*/
@Override
public void onServiceClick(int position) {
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
public void onSelectionChanged(int count) {
if (bulkDeleteHandler != null) {

View File

@@ -1,3 +1,10 @@
/*
* Fragment for browsing staff members.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.fragments.listfragments;
import android.os.Bundle;
@@ -24,6 +31,9 @@ import java.util.*;
import javax.inject.Inject;
import javax.inject.Named;
/**
* Fragment for displaying and managing a list of staff members.
*/
@AndroidEntryPoint
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 TokenManager tokenManager;
/**
* Inflates the layout and initializes UI components, filters, and observers.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -60,6 +73,9 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
return binding.getRoot();
}
/**
* Observes LiveData from the ViewModel to update the list and filter options.
*/
private void observeViewModel() {
viewModel.getFilteredEmployees().observe(getViewLifecycleOwner(), list -> {
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() {
adapter = new EmployeeAdapter(staffList, this);
adapter.setBaseUrl(baseUrl);
@@ -101,19 +120,31 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
});
}
/**
* Initializes the status filter spinner.
*/
private void setupStatusFilter() {
String[] statuses = {"All Statuses", "Active", "Inactive"};
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusStaff, statuses, this::applyFilters);
}
/**
* Initializes the store filter spinner.
*/
private void setupStoreFilter() {
SpinnerUtils.setupFilterSpinner(binding.spinnerStoreStaff, this::applyFilters);
}
/**
* Attaches search functionality to the search input field.
*/
private void setupSearch() {
UIUtils.attachSearch(binding.etSearchStaff, this::applyFilters);
}
/**
* Applies the selected filters and triggers a data reload from the view model.
*/
private void applyFilters() {
String query = binding.etSearchStaff.getText().toString().trim();
String status = binding.spinnerStatusStaff.getSelectedItem() != null ?
@@ -128,10 +159,16 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
viewModel.filter(query, storeId, status);
}
/**
* Configures the swipe-to-refresh layout.
*/
private void setupSwipeRefresh() {
binding.swipeRefreshStaff.setOnRefreshListener(() -> viewModel.loadStaff(true));
}
/**
* Navigates to the staff detail screen for adding or editing an employee.
*/
private void openDetail(int position) {
Bundle args = new Bundle();
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);
}
/**
* Handles clicks on individual staff members in the list.
*/
@Override
public void onEmployeeClick(int position) {
openDetail(position);
}
/**
* Cleans up the binding when the view is destroyed.
*/
@Override
public void onDestroyView() {
super.onDestroyView();

View File

@@ -1,3 +1,10 @@
/*
* Fragment for browsing suppliers.
*
* Author: Alex, Nikitha
* Date: April 2026
*/
package com.example.petstoremobile.fragments.listfragments;
import android.os.Bundle;
@@ -27,6 +34,9 @@ import java.util.List;
import dagger.hilt.android.AndroidEntryPoint;
/**
* Fragment for displaying and managing a list of suppliers.
*/
@AndroidEntryPoint
public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupplierClickListener {
@@ -36,12 +46,18 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
private SupplierListViewModel viewModel;
private BulkDeleteHandler bulkDeleteHandler;
/**
* Initializes the ViewModel.
*/
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(SupplierListViewModel.class);
}
/**
* Inflates the layout and sets up UI components, bulk delete, and observers.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -63,6 +79,9 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
return binding.getRoot();
}
/**
* Observes the ViewModel for supplier list updates and loading status.
*/
private void observeViewModel() {
viewModel.getSuppliers().observe(getViewLifecycleOwner(), list -> {
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() {
bulkDeleteHandler = new BulkDeleteHandler(
this,
@@ -88,24 +110,39 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
);
}
/**
* Cleans up the binding reference.
*/
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
/**
* Sets up the filter visibility toggle.
*/
private void setupFilterToggle() {
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSupplier);
}
/**
* Sets up the search input listener.
*/
private void setupSearch() {
UIUtils.attachSearch(binding.etSearchSupplier, () -> loadSupplierData(true));
}
/**
* Configures the swipe-to-refresh layout.
*/
private void setupSwipeRefresh() {
binding.swipeRefreshSupplier.setOnRefreshListener(() -> loadSupplierData(true));
}
/**
* Navigates to the supplier detail screen.
*/
private void openSupplierDetails(int position) {
Bundle args = new Bundle();
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);
}
/**
* Handles supplier item clicks by opening details.
*/
@Override
public void onSupplierClick(int position) {
openSupplierDetails(position);
}
/**
* Forwards selection changes to the bulk delete handler.
*/
@Override
public void onSelectionChanged(int count) {
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) {
String query = binding.etSearchSupplier != null ? binding.etSearchSupplier.getText().toString().trim() : "";
if (query.isEmpty()) query = null;
viewModel.loadSuppliers(reset, query);
}
/**
* Configures the RecyclerView and its scroll listener for pagination.
*/
private void setupRecyclerView() {
adapter = new SupplierAdapter(supplierList, this);
binding.recyclerViewSuppliers.setLayoutManager(new LinearLayoutManager(getContext()));

View File

@@ -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;
import android.os.Bundle;
@@ -39,12 +46,18 @@ public class AdoptionDetailFragment extends Fragment {
@Inject TokenManager tokenManager;
/**
* Initializes the fragment and its ViewModel.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(AdoptionDetailViewModel.class);
}
/**
* Inflates the layout for the fragment
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -52,6 +65,9 @@ public class AdoptionDetailFragment extends Fragment {
return binding.getRoot();
}
/**
* Sets up UI components, observers, and loads initial data after the view is created.
*/
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@@ -68,6 +84,9 @@ public class AdoptionDetailFragment extends Fragment {
binding.btnDeleteAdoption.setOnClickListener(v -> confirmDelete());
}
/**
* Sets up observers for ViewModel to update the UI dynamically.
*/
private void observeViewModel() {
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) {
if (binding != null) {
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
}
}
/**
* Cleans up the binding reference.
*/
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
/**
* Initializes the spinners with data and selection listeners.
*/
private void setupSpinners() {
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAdoptionStatus, new String[]{});
@@ -141,11 +169,17 @@ public class AdoptionDetailFragment extends Fragment {
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerAdoptionStatus, p -> notifyDateStatusChange());
}
/**
* Configures the date picker for the adoption date field.
*/
private void setupDatePicker() {
binding.etAdoptionDate.setOnClickListener(v ->
UIUtils.showDatePicker(requireContext(), binding.etAdoptionDate, this::notifyDateStatusChange));
}
/**
* Notifies the ViewModel when the date or status changes to update available options.
*/
private void notifyDateStatusChange() {
if (isUpdatingUI) return;
String date = binding.etAdoptionDate.getText().toString();
@@ -154,6 +188,9 @@ public class AdoptionDetailFragment extends Fragment {
viewModel.onDateChanged(date, status);
}
/**
* Uses fragment arguments to determine if we are editing an existing record.
*/
private void handleArguments() {
Bundle a = getArguments();
if (a != null && a.containsKey("adoptionId")) {
@@ -164,6 +201,9 @@ public class AdoptionDetailFragment extends Fragment {
viewModel.setAdoptionId(-1);
}
/**
* Load the adoption data from the backend.
*/
private void loadAdoptionData() {
viewModel.loadAdoption().observe(getViewLifecycleOwner(), resource -> {
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) {
isUpdatingUI = true;
@@ -224,10 +268,16 @@ public class AdoptionDetailFragment extends Fragment {
isUpdatingUI = false;
}
/**
* Checks if the currently logged-in user has the STAFF role.
*/
private boolean isStaff() {
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
}
/**
* Validates input and saves the adoption record.
*/
private void saveAdoption() {
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionCustomer, "Customer")) 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() {
DialogUtils.showDeleteConfirmDialog(requireContext(), "Adoption Record", () ->
viewModel.deleteAdoption().observe(getViewLifecycleOwner(), resource -> {
@@ -289,6 +342,9 @@ public class AdoptionDetailFragment extends Fragment {
}));
}
/**
* Navigates back to the previous screen.
*/
private void navigateBack() {
NavHostFragment.findNavController(this).popBackStack();
}

View File

@@ -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;
import android.os.Bundle;
@@ -47,18 +54,27 @@ public class AppointmentDetailFragment extends Fragment {
@Inject TokenManager tokenManager;
/**
* Initializes the fragment and its ViewModel.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(AppointmentDetailViewModel.class);
}
/**
* Inflates the layout for the fragment
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentAppointmentDetailBinding.inflate(inflater, container, false);
return binding.getRoot();
}
/**
* Sets up UI components, observers, and loads initial data after the view is created.
*/
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@@ -73,12 +89,18 @@ public class AppointmentDetailFragment extends Fragment {
binding.btnDeleteAppointment.setOnClickListener(v -> confirmDelete());
}
/**
* Cleans up the binding reference.
*/
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
/**
* Initializes the spinners with data and selection listeners.
*/
private void setupSpinners() {
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus, new String[]{});
@@ -108,11 +130,17 @@ public class AppointmentDetailFragment extends Fragment {
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerAppointmentStatus, p -> notifyDateTimeStatusChange());
}
/**
* Configures the date picker for the appointment date field.
*/
private void setupDatePicker() {
binding.etAppointmentDate.setOnClickListener(v ->
UIUtils.showDatePicker(requireContext(), binding.etAppointmentDate, this::notifyDateTimeStatusChange));
}
/**
* Sets up observers for ViewModel to update the UI dynamically.
*/
private void observeViewModel() {
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) {
if (binding != null && binding.progressBar != null) {
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) {
isUpdatingUI = true;
@@ -201,10 +236,16 @@ public class AppointmentDetailFragment extends Fragment {
isUpdatingUI = false;
}
/**
* Checks if the currently logged-in user has the STAFF role.
*/
private boolean isStaff() {
return "STAFF".equalsIgnoreCase(tokenManager.getRole());
}
/**
* Notifies the ViewModel when the date, time, or status changes to update available options.
*/
private void notifyDateTimeStatusChange() {
if (isUpdatingUI) return;
@@ -215,6 +256,9 @@ public class AppointmentDetailFragment extends Fragment {
viewModel.onDateOrTimeChanged(date, time, status);
}
/**
* Uses fragment arguments to determine if we are editing an existing record.
*/
private void handleArguments() {
Bundle a = getArguments();
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() {
viewModel.loadAppointment().observe(getViewLifecycleOwner(), resource -> {
if (resource == null) return;
@@ -244,6 +291,9 @@ public class AppointmentDetailFragment extends Fragment {
});
}
/**
* Validates input and saves the appointment record.
*/
private void saveAppointment() {
if (!validateRequiredFields()) return;
@@ -275,6 +325,9 @@ public class AppointmentDetailFragment extends Fragment {
});
}
/**
* Validates that all required fields are selected or filled.
*/
private boolean validateRequiredFields() {
if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Customer")) return false;
if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return false;
@@ -284,15 +337,24 @@ public class AppointmentDetailFragment extends Fragment {
return true;
}
/**
* Formats the selected hour and minute from spinners into a time string.
*/
private String buildTimeString() {
return DateTimeUtils.formatTime(HOURS[binding.spinnerHour.getSelectedItemPosition()], MINUTES[binding.spinnerMinute.getSelectedItemPosition()]);
}
/**
* Handles errors that occur during the save process.
*/
private void handleSaveError(String errorMessage) {
if (errorMessage != null && errorMessage.toLowerCase().contains("not available")) showNoAvailabilityDialog();
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() {
new androidx.appcompat.app.AlertDialog.Builder(requireContext())
.setTitle("No Availability")
@@ -301,6 +363,9 @@ public class AppointmentDetailFragment extends Fragment {
.setNegativeButton("Cancel Booking", (d, w) -> navigateBack()).show();
}
/**
* Displays a confirmation dialog before deleting the appointment record.
*/
private void confirmDelete() {
DialogUtils.showDeleteConfirmDialog(requireContext(), "Appointment", () ->
viewModel.deleteAppointment().observe(getViewLifecycleOwner(), resource -> {
@@ -310,10 +375,16 @@ public class AppointmentDetailFragment extends Fragment {
}));
}
/**
* Navigates back to the previous screen.
*/
private void navigateBack() {
NavHostFragment.findNavController(this).popBackStack();
}
/**
* Parses a time string and sets the hour and minute spinners accordingly.
*/
private void parseAndSetTimeSpinners(String time) {
int[] parsedTime = DateTimeUtils.parseTimeString(time);
if (parsedTime == null) return;

View File

@@ -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;
import android.os.Bundle;
@@ -29,12 +36,18 @@ import java.util.Locale;
import dagger.hilt.android.AndroidEntryPoint;
/**
* Fragment for displaying and editing coupon details.
*/
@AndroidEntryPoint
public class CouponDetailFragment extends Fragment {
private FragmentCouponDetailBinding binding;
private CouponDetailViewModel viewModel;
private long couponId = -1;
/**
* Inflates the layout, initializes ViewModel, and sets up UI components.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -74,10 +87,16 @@ public class CouponDetailFragment extends Fragment {
return binding.getRoot();
}
/**
* Initializes the discount type spinner with options.
*/
private void setupDiscountTypeSpinner() {
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) {
editText.setFocusable(false);
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() {
binding.tvCouponId.setText(DateTimeUtils.formatId(couponId));
binding.tvCouponId.setVisibility(View.VISIBLE);
@@ -111,6 +133,9 @@ public class CouponDetailFragment extends Fragment {
});
}
/**
* Validates input and saves the coupon record.
*/
private void saveCoupon() {
if (!InputValidator.isNotEmpty(binding.etCouponCodeDetail, "Coupon Code")) 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() {
new AlertDialog.Builder(requireContext())
.setTitle("Delete Coupon")

View File

@@ -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;
import android.os.Bundle;
@@ -21,6 +28,9 @@ import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
/**
* Fragment for displaying and editing customer details.
*/
@AndroidEntryPoint
public class CustomerDetailFragment extends Fragment {
@@ -31,6 +41,9 @@ public class CustomerDetailFragment extends Fragment {
private final String[] STATUSES = {"Active", "Inactive"};
/**
* Inflates the layout, initializes ViewModel, and sets up UI components.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -49,10 +62,16 @@ public class CustomerDetailFragment extends Fragment {
return binding.getRoot();
}
/**
* Initializes the spinners with data.
*/
private void setupSpinners() {
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerCustomerStatus, STATUSES);
}
/**
* Uses fragment arguments to determine if we are editing an existing record.
*/
private void handleArguments() {
Bundle a = getArguments();
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) {
viewModel.loadCustomer(id).observe(getViewLifecycleOwner(), resource -> {
if (resource != null) {
@@ -105,12 +127,18 @@ public class CustomerDetailFragment extends Fragment {
});
}
/**
* Shows or hides the loading bar.
*/
private void setLoading(boolean loading) {
if (binding != null && binding.progressBar != null) {
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
}
}
/**
* Validates input and saves the customer record.
*/
private void save() {
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() {
DialogUtils.showDeleteConfirmDialog(requireContext(), "Customer", () ->
viewModel.deleteCustomer().observe(getViewLifecycleOwner(), resource -> {
@@ -175,10 +206,16 @@ public class CustomerDetailFragment extends Fragment {
}));
}
/**
* Navigates back to the previous screen.
*/
private void navigateBack() {
NavHostFragment.findNavController(this).popBackStack();
}
/**
* Cleans up the binding reference.
*/
@Override
public void onDestroyView() {
super.onDestroyView();

View File

@@ -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;
import android.os.Bundle;
@@ -37,12 +44,18 @@ public class InventoryDetailFragment extends Fragment {
private long preselectedStoreId = -1;
private long preselectedProductId = -1;
/**
* Initializes the fragment and its ViewModel.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(InventoryDetailViewModel.class);
}
/**
* Inflates the layout for the fragment.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -50,6 +63,9 @@ public class InventoryDetailFragment extends Fragment {
return binding.getRoot();
}
/**
* Sets up UI components, observers, and loads initial data after the view is created.
*/
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@@ -63,23 +79,35 @@ public class InventoryDetailFragment extends Fragment {
binding.btnDeleteInventory.setOnClickListener(v -> confirmDelete());
}
/**
* Sets up observers for ViewModel to update the UI dynamically.
*/
private void observeViewModel() {
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> refreshStoreSpinner());
viewModel.getProductList().observe(getViewLifecycleOwner(), list -> refreshProductSpinner());
}
/**
* Shows or hides the loading bar.
*/
private void setLoading(boolean loading) {
if (binding != null && binding.progressBar != null) {
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
}
}
/**
* Cleans up the binding reference.
*/
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
/**
* Loads initial data for the store and product spinners.
*/
private void loadSpinnersData() {
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
if (resource == null) return;
@@ -97,18 +125,27 @@ public class InventoryDetailFragment extends Fragment {
});
}
/**
* Refreshes the store spinner with the current data.
*/
private void refreshStoreSpinner() {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryStore, viewModel.getStoreList().getValue(),
DropdownDTO::getLabel, "-- Select Store --",
preselectedStoreId, DropdownDTO::getId);
}
/**
* Refreshes the product spinner with the current data.
*/
private void refreshProductSpinner() {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryProduct, viewModel.getProductList().getValue(),
DropdownDTO::getLabel, "-- Select Product --",
preselectedProductId, DropdownDTO::getId);
}
/**
* Uses fragment arguments to determine if we are editing an existing record.
*/
private void handleArguments() {
Bundle args = getArguments();
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() {
viewModel.loadInventory().observe(getViewLifecycleOwner(), resource -> {
if (resource == null) return;
@@ -149,6 +189,9 @@ public class InventoryDetailFragment extends Fragment {
});
}
/**
* Validates input and saves the inventory record.
*/
private void saveInventory() {
if (!InputValidator.isSpinnerSelected(binding.spinnerInventoryStore, "Store")) 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() {
DialogUtils.showDeleteConfirmDialog(requireContext(), "Inventory Item", this::deleteInventory);
}
/**
* Calls the ViewModel to delete the inventory record.
*/
private void deleteInventory() {
setButtonsEnabled(false);
viewModel.deleteInventory().observe(getViewLifecycleOwner(), resource -> {
@@ -197,10 +246,16 @@ public class InventoryDetailFragment extends Fragment {
});
}
/**
* Navigates back to the previous screen.
*/
private void navigateBack() {
NavHostFragment.findNavController(this).popBackStack();
}
/**
* Enables or disables the action buttons.
*/
private void setButtonsEnabled(boolean enabled) {
UIUtils.setViewsEnabled(enabled, binding.btnSaveInventory, binding.btnDeleteInventory, binding.btnInventoryBack);
}

View File

@@ -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;
import android.net.Uri;

View File

@@ -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;
import android.net.Uri;
@@ -58,6 +65,9 @@ public class ProductDetailFragment extends Fragment {
@Inject @Named("baseUrl") String baseUrl;
@Inject TokenManager tokenManager;
/**
* Initializes the fragment, its ViewModel, and the ImagePickerHelper.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -88,6 +98,9 @@ public class ProductDetailFragment extends Fragment {
});
}
/**
* Inflates the layout for the fragment.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -95,6 +108,9 @@ public class ProductDetailFragment extends Fragment {
return binding.getRoot();
}
/**
* Sets up UI components, observers, and handles arguments after the view is created.
*/
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@@ -108,6 +124,9 @@ public class ProductDetailFragment extends Fragment {
binding.ivProductImage.setOnClickListener(v -> imagePickerHelper.showImagePickerDialog("Select Product Image", hasImage));
}
/**
* Sets up observers for the ViewModel and loads product categories.
*/
private void observeViewModel() {
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) {
if (binding != null && binding.progressBar != null) {
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
}
}
/**
* Populates the category spinner with available categories.
*/
private void updateCategorySpinner() {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, viewModel.getCategoryList().getValue(),
DropdownDTO::getLabel, "-- Select Category --",
preselectedCategoryId, DropdownDTO::getId);
}
/**
* Cleans up the binding reference.
*/
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
/**
* Uses fragment arguments to determine if we are editing an existing record.
*/
private void handleArguments() {
Bundle a = getArguments();
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() {
viewModel.loadProduct().observe(getViewLifecycleOwner(), resource -> {
if (resource == null) return;
@@ -175,6 +209,9 @@ public class ProductDetailFragment extends Fragment {
});
}
/**
* Loads the product image from the server.
*/
private void loadProductImage() {
String imageUrl = baseUrl + String.format(Locale.US, ProductApi.PRODUCT_IMAGE_PATH, viewModel.getProdId());
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) {
if (isImageRemoved) {
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) {
File file = FileUtils.getFileFromUri(requireContext(), uri);
if (file == null) {
@@ -239,6 +282,9 @@ public class ProductDetailFragment extends Fragment {
});
}
/**
* Validates input and saves the product record.
*/
private void saveProduct() {
if (!InputValidator.isNotEmpty(binding.etProductName, "Product Name")) 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() {
DialogUtils.showDeleteConfirmDialog(requireContext(), "Product", () ->
viewModel.deleteProduct().observe(getViewLifecycleOwner(), resource -> {
@@ -280,6 +329,9 @@ public class ProductDetailFragment extends Fragment {
}));
}
/**
* Navigates back to the previous screen.
*/
private void navigateBack() {
NavHostFragment.findNavController(this).popBackStack();
}

Some files were not shown because too many files have changed in this diff Show More