222 Commits
1.0 ... newkvv

Author SHA1 Message Date
Caesar2011
342d81f5c4 Fixed Offline Mode 2018-11-14 01:24:12 +01:00
Caesar2011
460713c0db Bug Fixes 2018-11-13 23:53:59 +01:00
Caesar2011
6c146ad3d9 E-Mail Intent eingefügt 2018-11-13 22:18:14 +01:00
Caesar2011
4f8fa937a7 Added Lecturers to Overview and List 2018-11-13 21:28:42 +01:00
Caesar2011
07fbd7f74e Even more CardView and new Semester Class, updated Module List 2018-11-13 17:58:42 +01:00
Caesar2011
9668fe889e Rename and Bug Fixes 2018-11-13 16:20:19 +01:00
Caesar2011
a879a085b4 Fixed Warnings 2018-11-13 01:39:12 +01:00
Caesar2011
6daa41cbcd Massive Renaming and Name Shorting 2018-11-13 00:44:25 +01:00
Caesar2011
99933d5f80 Implemented Network Callback 2018-11-12 18:40:15 +01:00
Caesar2011
cceb317457 Bug Fixes 2018-11-09 21:17:21 +01:00
Caesar2011
9b48f9b414 Removed Old KVV 2018-11-09 20:45:54 +01:00
Caesar2011
af9d2fed0e Bug fixing, Implemented Rescource, Event and Gradebook 2018-11-09 20:11:40 +01:00
Caesar2011
0439c845a5 Bug fixing, JSON parsing more robust 2018-11-09 01:17:59 +01:00
Caesar2011
518ae756b1 Re-added new download opener 2018-11-08 20:39:07 +01:00
Caesar2011
57ce9ffa26 Updated ModDetails: Overview, Assignments, Announcements (not runnable) 2018-11-08 00:22:31 +01:00
Caesar2011
e46a184f35 Updated LoginFragment and ModulesFragment (not runnable) 2018-11-07 19:36:33 +01:00
Caesar2011
3cdce73c5a Working Progress (not runnable) 2018-11-07 03:30:38 +01:00
Joshua
44b95d7b70 Download überarbeitet, nun wird die Datei vom richtigen Standard Programm geöffnet 2018-11-06 17:39:14 +01:00
Caesar2011
146807b93e Removed deprecations 2018-10-31 00:22:01 +01:00
Caesar2011
ce0486bd2c Version 9 2018-10-31 00:05:07 +01:00
Caesar2011
582e6e17e8 Fixed MainActivity Navigation bug 2018-10-31 00:02:17 +01:00
Joshua
a7ff15142f Datei Download für Ankündigung und Aufgaben 2018-10-30 20:10:15 +01:00
Joshua
e95b1ecf74 mehrfache anzeigen von Dateien in Ankündigungen und Aufgaben entfernt 2018-10-30 16:19:50 +01:00
Caesar2011
bcb09d9a26 Version 8 2018-10-30 03:05:50 +01:00
Caesar2011
4fb969479e Show "No item available" on announcement and assignment 2018-10-30 03:05:16 +01:00
Caesar2011
70072c6605 Resource and Event Order changed in Module Detail View 2018-10-30 02:35:45 +01:00
Caesar2011
92e609babc Event overview updated 2018-10-30 02:31:48 +01:00
Caesar2011
aba6471c1d Fixed non-selected navigation items 2 2018-10-25 00:21:25 +02:00
Caesar2011
359330bb9d Fixed non-selected navigation items 2018-10-24 00:43:39 +02:00
Caesar2011
dddfe3b1d2 Added new layout to Announcement and assignment with attachment tags 2018-10-24 00:15:17 +02:00
Caesar2011
14cf917ec8 Fixed DateSortedList 2018-10-24 00:14:25 +02:00
Caesar2011
9d5416ab9b Fixed offline mode navigation 2018-10-23 20:37:24 +02:00
Caesar2011
98db93c255 grandle sync failed 2018-10-23 18:17:34 +02:00
Caesar2011
8c9505eb4f Merge branch 'version7' 2018-10-22 17:06:44 +02:00
Caesar2011
6d09d8e885 getParsed NullPointerException / Version 7 2018-10-22 16:58:49 +02:00
Caesar2011
16a7d8ae13 getParsed NullPointerException / Version 7 2018-10-22 16:51:54 +02:00
Caesar2011
2e76f276e0 Announcement list design update 2018-10-22 01:12:51 +02:00
Caesar2011
1e9c6a2b70 Navigation layout refreshment 2018-10-22 00:15:44 +02:00
Caesar2011
3af8b55e7b No context bugfix 2018-10-22 00:14:31 +02:00
Caesar2011
9f060227c0 Set version 6 to 1.1.4 2018-10-21 01:58:25 +02:00
Caesar2011
7c0623ea60 Code cleanup, spelling correction 2018-10-21 01:55:43 +02:00
Caesar2011
abdc4695e9 Code clean up, Download folder name escaping, LoginListener Bug 2018-10-21 00:42:42 +02:00
Caesar2011
e70daa33cc Added canteens, made Canteen plan more stable 2018-10-20 00:27:06 +02:00
Caesar2011
6e45f1f660 Cleaned up and optimized 2018-10-19 19:09:50 +02:00
Caesar2011
4f2cb4be8c Show Errors on login! 2018-10-19 18:00:34 +02:00
Caesar2011
a6f7e3c7fd Fixed errors and spelling 2018-10-19 17:07:21 +02:00
Caesar2011
6b1205bde8 Changed build version 2018-10-19 16:50:55 +02:00
Joshua
ae10bff497 Fehler bei der Reinfoge wie die Module in der Kurs ansicht dar gestellt werden behoben 2018-10-19 14:22:09 +02:00
Caesar2011
03df7c10be Removed .idea 2 2018-10-19 01:09:36 +02:00
Caesar2011
ed0ace8f6f Removed .idea 2018-10-19 01:05:14 +02:00
Caesar2011
bfabcb2672 Minor changes 2018-10-19 01:01:53 +02:00
Caesar2011
67138dee8b Merge branch 'master' of https://git.imp.fu-berlin.de/seedorf96/fuplanner
# Conflicts:
#	.idea/misc.xml
#	app/src/main/java/de/sebse/fuplanner/MainActivity.java
#	app/src/main/java/de/sebse/fuplanner/fragments/ScheduleFragment.java
2018-10-19 00:55:22 +02:00
Caesar2011
40443cd4c2 Canteen day view margin/padding 2018-10-19 00:44:37 +02:00
Caesar2011
4e8e205a20 Lighter Background Color 2018-10-19 00:39:37 +02:00
Caesar2011
f86ee2ddf3 Migration to AndroidX 2018-10-19 00:25:50 +02:00
Joshua
ff1845d7aa Kein Fragment Commit wenn isStateSaved 2018-10-18 20:02:17 +02:00
Joshua
c0b4268f0e ResourceFragment Warnumgen entfernt 2018-10-18 19:31:46 +02:00
Joshua
7ba31b8617 ResourceFragment aufgeräumt 2018-10-18 19:31:10 +02:00
Joshua
71f5bb735c Download Bugs gefixt und Berechtigungsprüfung verbessert 2018-10-18 19:25:46 +02:00
Caesar2011
18ce71cb87 Updated Grandle 2018-10-16 18:36:49 +02:00
Caesar2011
f7730f5ed6 Fixed Login 2018-10-15 22:25:31 +02:00
Caesar2011
9be7a18827 Merge remote-tracking branch 'origin/master' 2018-10-15 18:34:46 +02:00
Caesar2011
06a430f7fd Logs removed 2018-10-15 18:34:32 +02:00
Joshua
b5fc82c138 Strings bearbeitet 2018-10-15 18:33:00 +02:00
Caesar2011
a9a86c743e Merge remote-tracking branch 'origin/master' 2018-10-15 18:18:40 +02:00
Caesar2011
ba3b07b8b4 Save Instance State more precise 2018-10-15 18:18:30 +02:00
Caesar2011
f70e774896 Canteen List Updated 2018-10-15 18:17:33 +02:00
Joshua
75e53601c9 download zip fehler behoben 2018-10-15 17:47:22 +02:00
Caesar2011
124b34dc2f removed Last_Fragment 2 2018-10-15 17:26:24 +02:00
Caesar2011
1fa442dbd9 removed Last_Fragment 2018-10-15 17:13:07 +02:00
Caesar2011
e1de122001 Merge remote-tracking branch 'origin/master' 2018-10-15 13:52:34 +02:00
Caesar2011
1130b57022 Canteen Plan Fixed 2 2018-10-15 13:52:07 +02:00
Joshua
b244778c4a download mit Links funktionieren jetzt auch 2018-10-15 13:47:09 +02:00
Caesar2011
f97e293ef3 Screen Rotating when watching Canteen Plan fixed 2018-10-15 13:23:53 +02:00
Caesar2011
b67f7edd04 New regex method 2018-10-15 13:19:26 +02:00
Joshua
34a104a8a3 Download kann nun ausgewählt werden ob Datei erneut herunter geladen werden soll, wenn bereits vorhanden.
Es wird nun ein Fehler angezeigt wenn der Download fehl schlug.
Wenn die App keine schreibrechte hat, fragt sie nach welchen.
Ordner von Modulename zu FU-Modulename umbenant
2018-10-12 18:20:07 +02:00
Joshua
59027b59fe Downloads von PDFs jetzt auch möglich :D 2018-10-11 15:14:18 +02:00
Joshua
11c65b6292 Resourcen auto login prüfung 2018-10-11 14:17:29 +02:00
Joshua
7ad374dd5c Ressourcen Download implementiert, allerdings funktioneren bisher nur txt Dateien Problem los, bei PDFs sieht man irgentwie nur leere Folien 2018-10-10 18:10:52 +02:00
Joshua
834b259f61 Strings für Resourcen ins deutsche übersetzt 2018-10-01 18:01:24 +02:00
Joshua
4cecb7a1c7 Bilder für das Dateisystem hinzugefügt 2018-09-28 00:58:33 +02:00
Caesar2011
c79977140f resource alert dialog 2018-09-16 19:41:01 +02:00
Caesar2011
e21b2e99f2 Resource 1st version... Visible 2018-09-16 18:59:55 +02:00
Joshua
95cef20bb8 Resource in KVV aktualliesiert 2018-09-16 17:13:22 +02:00
Joshua
7b4fd9dfab Weekview design geändert 2018-09-16 14:51:34 +02:00
Joshua
662f9bc240 Nach einen erneuten Login wird der Nutzer nun auf das FRAGMENT weitergeleitet, das er vor der Aktualisierung gesehen hat. Allerdings beim Kalender auf das Aktuelle Datum und bei einem Modul immer zur Modul Übersicht.
Deutsche Strings für Kantine erweitert
2018-08-15 17:34:48 +02:00
Joshua
14fa440237 Merge remote-tracking branch 'origin/master' 2018-08-15 12:26:48 +02:00
Caesar2011
ac123abb5f Fixed attrs 2018-08-15 01:42:07 +02:00
Caesar2011
68f6a32aa8 Log removed 2018-08-15 01:40:26 +02:00
Caesar2011
2210138785 Bug fixing and Canteen grouping 2018-08-15 01:36:49 +02:00
Caesar2011
e04fe4ec5f Implemented Better Canteen Meals (Custom CardView) 2018-08-15 00:05:41 +02:00
Joshua
230376f785 Merge remote-tracking branch 'origin/master' 2018-08-13 14:57:39 +02:00
Caesar2011
4a1fc3e28b Implemented Cards for Canteen 2018-08-12 00:53:51 +02:00
Caesar2011
f0bd6d1454 Fixed WeekView canvas under API 28 (Android P) 2018-08-11 18:48:50 +02:00
Joshua
1094298761 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	.idea/misc.xml
#	app/build.gradle
#	app/src/main/java/de/sebse/fuplanner/fragments/LoginFragment.java
#	app/src/main/java/de/sebse/fuplanner/fragments/ScheduleFragment.java
#	app/src/main/res/values-de/arrays.xml
#	app/src/main/res/values-de/strings.xml
#	app/src/main/res/values/arrays.xml
#	app/src/main/res/values/preferences.xml
#	app/src/main/res/xml/preferences.xml
#	build.gradle
2018-08-10 14:00:21 +02:00
Caesar2011
8acac1ee8b Bug Fixes (Double Click, Login Fragment Title) 2018-08-09 23:00:16 +02:00
Joshua
652e07cf9c deutsche Übersetzung Einstellungen Katine 2018-08-07 17:04:41 +02:00
Caesar2011
28fe6951f2 Implemented Preference for Canteen Price 2018-08-02 23:42:02 +02:00
Caesar2011
85bc13ab0a Refresh Failed Canteen List 2018-08-02 19:00:09 +02:00
Caesar2011
2bf56d4c1e Rescource Clean Up 2018-08-02 17:23:53 +02:00
Joshua
d8d98e0082 Ressourcen hinzugefügt 2018-08-02 16:37:12 +02:00
Joshua
ecf14c5f21 URLs bei Ankündigunen werden jetzt angezeigt 2018-08-02 15:02:52 +02:00
Caesar2011
b5f642bd36 Implemented Settings screen (WIP) 2018-08-02 01:19:12 +02:00
Caesar2011
2d84c87695 Code cleanup 2018-08-02 00:19:40 +02:00
Caesar2011
66b6d82a83 New icon! 2018-08-01 23:20:30 +02:00
Caesar2011
bdae34a9d3 New icon! 2018-08-01 19:23:16 +02:00
Joshua
05d9e76900 Strings Berabetet close -> closed und neuer close 2018-08-01 17:58:30 +02:00
Caesar2011
cabb78ab87 fixed grade separator 2018-08-01 00:44:24 +02:00
Caesar2011
7a07577cd4 Schedule more colorful 2018-08-01 00:33:04 +02:00
Caesar2011
b3fadb4110 Implemented Event Click Listener 2018-08-01 00:26:15 +02:00
Caesar2011
d8e3a68344 Implemented event location 2018-07-31 22:45:55 +02:00
Joshua
2598e11ac5 String Fehler durch Umbennenung beseitigt und deutsch angepasst 2018-07-31 15:11:34 +02:00
Caesar2011
ef404dd816 Unused logs removed 2018-07-30 23:36:45 +02:00
Caesar2011
601759ebb1 Log in button in navigation menu implemented 2018-07-30 23:29:37 +02:00
Caesar2011
279594bf7d Log in button in navigation menu implemented 2018-07-30 22:54:42 +02:00
Caesar2011
f0d1826b1f Minor clean up 2018-07-30 22:37:38 +02:00
Caesar2011
640b0b63b4 Fixed Mensa when logged out 2018-07-30 22:36:04 +02:00
Caesar2011
8175239cff Code cleanup 2018-07-30 21:53:54 +02:00
Caesar2011
7b67834ed8 Added colors to schedule 2018-07-30 20:47:05 +02:00
Caesar2011
04755b8829 Data policy uploaded 2018-07-30 18:11:38 +02:00
Caesar2011
5b6c2f3705 Corrected string recources 2018-07-30 18:07:42 +02:00
Joshua
f0f8fdcb5a Kalender: doppel Klick zum vor und zurück blättern der Wochen eingefügt
Deutsche übersetzung erweitert
2018-07-30 17:23:40 +02:00
Caesar2011
d3b41508bb Prices more beautiful 2018-07-30 01:11:47 +02:00
Caesar2011
cb2c2daa14 Implemented Canteen Plan 2018-07-30 00:59:18 +02:00
Caesar2011
a827977d6c Fixed NPE 2018-07-29 18:33:43 +02:00
Caesar2011
386894f919 Implemented Option Menu 2018-07-29 14:16:58 +02:00
Caesar2011
28bd628efb Share Intent 2018-07-29 11:56:11 +02:00
Caesar2011
1f1ba082d2 Small Layout Changes 2018-07-29 04:05:18 +02:00
Caesar2011
500edb1a4c Merge branch 'login_callback_refactoring' 2018-07-29 03:44:34 +02:00
Caesar2011
24e72bf760 Login Restructuring Complete 2018-07-29 03:40:03 +02:00
Caesar2011
67b248e2e8 Fixed Month Loading Problem 2018-07-29 00:41:09 +02:00
Caesar2011
728d715041 Login Refactoring 2018-07-28 19:49:14 +02:00
Caesar2011
9939d6542a Overview Listener Implemented 2018-07-28 14:47:58 +02:00
Caesar2011
f1d1e1c5dc Fixed gradebook layout 2018-07-27 18:37:35 +02:00
Caesar2011
f529f7aca5 Canteen NAvigation Menu and Google Auth fix 2018-07-27 18:24:00 +02:00
Caesar2011
b34e4d5664 Reformatted SortedModuleList to SortedListModule 2018-07-27 17:44:44 +02:00
Caesar2011
2e0190b378 Moved Types from tools package to Types 2018-07-27 16:05:48 +02:00
Caesar2011
d8cad9d52f Removed Google Firebase 2018-07-26 16:59:12 +02:00
Caesar2011
2cae890a01 Fixed module refresh 2018-07-26 16:49:38 +02:00
Caesar2011
bc72127273 Added Google Firebase 2018-07-23 00:51:35 +02:00
Caesar2011
e7089e7b79 Implemented Canteen Browser 2018-07-23 00:32:05 +02:00
Caesar2011
5cd5bc6a1a Even faster login! 2018-07-19 10:33:47 +02:00
Caesar2011
2e8ba9f179 Login stabilized 2018-07-19 10:20:23 +02:00
Caesar2011
d0f54fc2d7 Module ID is now string, not int;Module Update function added 2018-07-19 09:59:48 +02:00
Caesar2011
3f93f77c46 Fast Login Successful! 2018-07-19 00:06:48 +02:00
Caesar2011
119ec3874c Refactored MainActivity #2 2018-07-17 17:56:00 +02:00
Caesar2011
223a418526 Refactored MAinActivity 2018-07-17 17:49:21 +02:00
Caesar2011
6f3816e469 Merge remote-tracking branch 'origin/master' 2018-07-15 20:11:35 +02:00
Caesar2011
792807154e Login speed improved and network usage reduced 2018-07-15 20:11:04 +02:00
Joshua
eb72dcf8c1 Aktuelle Prozentzahl in der Notenübersich hinzugefügt (Ohne Beschreibungstext) 2018-07-14 23:28:38 +02:00
Caesar2011
f052e18d63 Better gradebook item visualization 2018-07-14 14:24:23 +02:00
Caesar2011
c74fd9b3fc German translation 2018-07-14 13:06:47 +02:00
Caesar2011
c9cd4592ce Removed unused TODOs and warnings in fragments-package #2 2018-07-14 00:25:37 +02:00
Caesar2011
023a90ec4f Removed unused TODOs and warnings in fragments-package 2018-07-14 00:23:28 +02:00
Caesar2011
f850bdad31 Added Gradebook 2018-07-14 00:14:10 +02:00
Caesar2011
6c297bda9c Added Event List 2018-07-13 23:44:06 +02:00
Caesar2011
75683960ca WeekView warnings fixed 2018-07-13 20:51:17 +02:00
Caesar2011
e40743df93 Removed unused imports 2018-07-13 20:04:06 +02:00
Caesar2011
dea643da75 Date time formatting according to localization 2018-07-13 20:01:24 +02:00
Caesar2011
22f0fb5930 Activity header name customizing (Schedule) 2018-07-13 16:55:54 +02:00
Caesar2011
a4940481fa Activity header name customizing 2018-07-13 15:47:50 +02:00
Caesar2011
8a8db8fb4c Weeview default event color property fixed 2018-07-13 15:01:50 +02:00
Caesar2011
6b2b7e623b Fixed NavigationView module selection 2018-07-13 14:54:10 +02:00
Caesar2011
3acc27c6f1 Added schedule fragment listener 2018-07-13 14:40:44 +02:00
Caesar2011
a0a76109a1 Added default event color property to WeekView (untested) 2018-07-13 13:39:35 +02:00
Caesar2011
8ed48e40bb Schedule coloring 2018-07-12 18:22:32 +02:00
Joshua
a40b7d7d52 Kalender Farben geändert 2018-07-12 18:03:15 +02:00
Caesar2011
3448551ad1 Merge remote-tracking branch 'origin/master' 2018-07-12 18:01:43 +02:00
Caesar2011
616d1e1b33 Color Editing 2018-07-12 18:01:32 +02:00
Joshua
7b98e9b643 Deutsches Sprachpacket bearbeitet 2018-07-12 16:17:11 +02:00
Joshua
5073d9fd87 WeekView Bib eigenkreation 2018-07-12 15:45:59 +02:00
Caesar2011
41b0a88f25 Comment removed 2018-07-10 11:58:36 +02:00
Caesar2011
0550659ecf Comment removed 2018-07-10 10:58:09 +02:00
Caesar2011
f864065a65 Merge remote-tracking branch 'origin/master' 2018-07-10 10:56:29 +02:00
Caesar2011
2cde3e83d5 Added instructions to assignments 2018-07-10 10:56:14 +02:00
Joshua
31f4342726 Deutsche Strings hinzugefügt2 2018-07-10 10:51:34 +02:00
Joshua
33a08dd47d Merge remote-tracking branch 'origin/master'
# Conflicts:
#	app/src/main/res/values/strings.xml
2018-07-10 10:47:56 +02:00
Joshua
692da14a38 Deutsche Strings hinzugefügt 2018-07-10 10:44:23 +02:00
Caesar2011
0f0efa4d57 Fixed item selection in menu when selecting a course 2018-07-08 18:34:03 +02:00
Caesar2011
cdcbdd74b6 Renamed class and function 2018-07-08 16:13:16 +02:00
Caesar2011
d353353d3a Offline login implemented 2018-07-08 15:54:44 +02:00
Caesar2011
b49a3d01da Added assignment page 2018-07-06 00:27:32 +02:00
Caesar2011
d492d13842 Sorted assignments 2018-07-06 00:15:45 +02:00
Caesar2011
4ca7654f94 Added Announcement detail page 2018-07-05 23:35:44 +02:00
Caesar2011
264240b35d Refesh on error fixed 2018-07-05 12:19:50 +02:00
Caesar2011
21aab66d9a Removed unused logger 2018-07-05 01:19:14 +02:00
Caesar2011
be953ddfa3 Removed unused imports 2018-07-05 00:27:14 +02:00
Caesar2011
6cdb61e2bb Serialization working, Auto-save and load 2018-07-05 00:20:37 +02:00
Caesar2011
1d1b1e7db9 Comments removed 2018-07-04 20:53:21 +02:00
Caesar2011
c91ec96578 ScheduleFragment init working; ScheduleFragment cleaned up 2018-07-04 19:34:34 +02:00
Caesar2011
e334461a1c Renamed forceOverride to forceRefresh; refeshing added to public api 2018-07-04 19:16:37 +02:00
Caesar2011
c186e85533 Modules list cached, implemented offline load and forceOverride-refresh 2018-07-04 18:35:05 +02:00
Joshua
b924c8be2b Stundenplan wird nun angezeit, aber noch probleme mit dem refresh 2018-07-04 17:47:10 +02:00
Caesar2011
a7044c2408 Event list working 2018-07-03 17:21:57 +02:00
Caesar2011
45d436c5fb Does not work - Event list preparing 2018-07-03 17:01:11 +02:00
Caesar2011
b342e79e11 Moved KVV types to sub package 2018-07-03 14:38:34 +02:00
Caesar2011
11c3f9157b Added refesh to module overview 2018-07-03 14:29:03 +02:00
Joshua
2b34167724 Fragment für Stundenplan hinzugefügt 2018-07-03 14:26:08 +02:00
Joshua
b82f0fd3f2 Merge remote-tracking branch 'origin/master' 2018-06-12 18:35:30 +02:00
Joshua
3bf87dccc2 Gradebook hinzugefügt 2018-06-12 18:34:42 +02:00
Caesar2011
abb9f754a9 Made Module List more beautiful 2018-06-12 17:39:37 +02:00
Joshua
c997667596 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailOverviewAdapter.java
#	app/src/main/java/de/sebse/fuplanner/services/KVV/Assignment.java
2018-06-12 16:57:58 +02:00
Joshua
281722b404 Aufgaben URLs für PDFs hinzugefügt 2018-06-12 16:53:40 +02:00
Caesar2011
d72c87d0b1 Added event order 2018-06-12 16:38:50 +02:00
Caesar2011
6fb1c013e8 German Language File added 2018-06-12 16:14:39 +02:00
Caesar2011
da5f969c4a Merge remote-tracking branch 'origin/master' 2018-06-12 09:04:19 +02:00
Caesar2011
9795c8e148 Fixed Overview Event Showing 2018-06-12 09:04:05 +02:00
Joshua
9b405fcd3f Merge remote-tracking branch 'origin/master' 2018-06-07 19:01:34 +02:00
Joshua
80adac818f Herunterladen der Assignment Hinzugefügt 2018-06-07 19:00:36 +02:00
Caesar2011
3dc54200af Event URL pulling 2018-06-07 12:07:14 +02:00
Caesar2011
ff4dea25e3 Added Upcoming Events to KVVModuleList 2018-06-07 11:10:36 +02:00
Caesar2011
ca97103f9a Added Upcoming Events to ModDetailsOverviewAdapter 2018-06-07 10:29:39 +02:00
Caesar2011
fd06ec4c11 Fixed ModDetailsOverviewAdapter with Assignments 2018-06-07 10:09:52 +02:00
Caesar2011
cc2aa4e591 Fixed Login and Logger improved 2018-06-06 16:57:54 +02:00
Caesar2011
2a4fe8d2e1 Fixed Login and Logger improved 2018-06-06 16:57:01 +02:00
Caesar2011
5c4bd50379 Module Detail Overview Implementation #2 2018-06-06 14:45:43 +02:00
Caesar2011
c45d859a51 Module Detail Overview Implementation #2 2018-06-06 14:20:38 +02:00
Caesar2011
7a1b6b66d7 Module Detail Overview Implementation #1 2018-05-16 23:43:47 +02:00
Caesar2011
55f06830b0 UpgradeModule removed 2018-05-10 18:45:01 +02:00
Caesar2011
c9a5f9972d Just a commit while restucturing with API 2018-05-03 22:41:29 +02:00
Caesar2011
b573312710 Added module detail fragment 2018-04-24 22:18:04 +02:00
Caesar2011
3d02129cb8 Initial commit 2018-04-24 09:48:17 +02:00
196 changed files with 5486 additions and 2176 deletions

4
.gitignore vendored
View File

@@ -1,8 +1,8 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
/.idea/*
/.idea - PC/*
.DS_Store
/build
/captures

View File

@@ -1,29 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<Objective-C-extensions>
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" fileNamingConvention="NONE" />
<pair source="c" header="h" fileNamingConvention="NONE" />
</extensions>
</Objective-C-extensions>
</code_scheme>
</component>

18
.idea/gradle.xml generated
View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

29
.idea/misc.xml generated
View File

@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="5">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK" />
</project>

9
.idea/modules.xml generated
View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/FUPlanner.iml" filepath="$PROJECT_DIR$/.idea/FUPlanner.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
</modules>
</component>
</project>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

7
.idea/vcs.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@@ -2,14 +2,14 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 28
buildToolsVersion '27.0.3'
buildToolsVersion '28.0.3'
defaultConfig {
applicationId "de.sebse.fuplanner"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
versionCode 10
versionName "1.2.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
@@ -27,28 +27,28 @@ android {
dependencies {
implementation 'com.android.support:recyclerview-v7:28.0.0-rc01'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation fileTree(include: ['*.jar'], dir: 'libs')
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0-beta02', {
exclude group: 'com.android.support', module: 'support-annotations'
})
implementation 'com.android.support:appcompat-v7:28.0.0-rc01'
implementation 'com.android.support:preference-v7:28.0.0-rc01'
implementation 'com.android.support:design:28.0.0-rc01'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
implementation 'com.android.volley:volley:1.0.0'
implementation 'androidx.preference:preference:1.0.0'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
implementation 'com.android.volley:volley:1.1.0'
//noinspection GradleDependency
implementation 'com.google.android.gms:play-services-auth:15.0.0'
implementation 'com.android.support:support-v4:28.0.0-rc01'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.12'
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0-rc01'
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'org.jbundle.util.osgi.wrapped:org.jbundle.util.osgi.wrapped.org.apache.http.client:4.1.2'
implementation 'com.android.support:support-v4:28.0.0-rc01'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'org.jetbrains:annotations-java5:15.0'
//implementation 'com.github.quivr:android-week-view:2.0.2'//com.github.alamkanak:android-week-view:1.2.6
implementation 'com.ms-square:expandableTextView:0.1.4'
// https://github.com/bignerdranch/expandable-recycler-view
implementation 'com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1'
implementation 'com.github.Cutta:TagView:1.3'
implementation files('libs/jericho-html-3.4.jar')
}

View File

@@ -1,12 +1,13 @@
package de.sebse.fuplanner;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import static org.junit.Assert.*;
/**

View File

@@ -4,6 +4,9 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
@@ -21,6 +24,15 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.my.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
</application>
</manifest>

BIN
app/src/main/ic_bio-web.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
app/src/main/ic_msc-web.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -2,24 +2,28 @@ package de.sebse.fuplanner;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.design.widget.NavigationView;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.android.volley.NetworkResponse;
import com.google.android.material.navigation.NavigationView;
import java.util.HashMap;
import java.util.Iterator;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import de.sebse.fuplanner.fragments.CanteensFragment;
import de.sebse.fuplanner.fragments.LoginFragment;
import de.sebse.fuplanner.fragments.ModulesFragment;
@@ -30,17 +34,23 @@ import de.sebse.fuplanner.fragments.canteen.DaySwitcherFragment;
import de.sebse.fuplanner.fragments.moddetails.ModDetailFragment;
import de.sebse.fuplanner.services.Canteen.CanteenBrowser;
import de.sebse.fuplanner.services.Canteen.types.Canteen;
import de.sebse.fuplanner.services.GoogleAuth.Credentials;
import de.sebse.fuplanner.services.GoogleAuth.GoogleAuth;
import de.sebse.fuplanner.services.KVV.KVV;
import de.sebse.fuplanner.services.KVV.KVVListener;
import de.sebse.fuplanner.services.KVV.types.LoginToken;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.MainAcitivityListener;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.NewAsyncQueue;
import de.sebse.fuplanner.tools.RequestPermissionsResultListener;
import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkError;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
public class MainActivity extends AppCompatActivity
implements MainAcitivityListener,
implements MainActivityListener, KVVListener,
NavigationView.OnNavigationItemSelectedListener,
LoginFragment.OnLoginFragmentInteractionListener,
ModulesFragment.OnModulesFragmentInteractionListener,
CanteensFragment.OnCanteensFragmentInteractionListener {
@@ -66,16 +76,18 @@ public class MainActivity extends AppCompatActivity
private int fragmentPage = FRAGMENT_NONE;
private String fragmentData = "";
private CanteenBrowser mCanteenBrowser;
private boolean mOfflineMode = false;
private HashMap<String, RequestPermissionsResultListener> permissionListeners = new HashMap<>();
private boolean mOfflineBanner;
private NewAsyncQueue mQueue = new NewAsyncQueue();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int newFragmentPage = FRAGMENT_NONE;
String newFragmentData = "";
int desiredPage = getDefaultFragmentAfterLogin();
String desiredData = "";
if (savedInstanceState != null) {
newFragmentPage = savedInstanceState.getInt(ARG_FRAGMENT_PAGE, fragmentPage);
newFragmentData = savedInstanceState.getString(ARG_FRAGMENT_STATUS, fragmentData);
desiredPage = savedInstanceState.getInt(ARG_FRAGMENT_PAGE, desiredPage);
desiredData = savedInstanceState.getString(ARG_FRAGMENT_STATUS, desiredData);
}
setContentView(R.layout.activity_main);
@@ -92,25 +104,12 @@ public class MainActivity extends AppCompatActivity
mNavigationView.setNavigationItemSelectedListener(this);
mFragmentManager = getSupportFragmentManager();
LoginToken loginToken = getKVV().easyLogin();
if (newFragmentPage != FRAGMENT_LOGIN && newFragmentPage != FRAGMENT_STARTUP && newFragmentPage != FRAGMENT_NONE) {
if (loginToken != null)
toLoginState(loginToken, newFragmentPage, newFragmentData);
else
checkAndDoLogin();
} else {
if (loginToken != null)
toLoginState(loginToken, getDefaultFragmentAfterLogin(), "");
else
checkAndDoLogin();
if (!getKVV().account().restoreOnlineLogin()) {
desiredPage = FRAGMENT_LOGIN;
desiredData = "";
}
/*this.getCanteenBrowser().getCanteens(success -> {
Canteen canteen = success.get(0);
this.getCanteenBrowser().getCanteen(canteen, success1 -> {
this.getCanteenBrowser().getDay(canteen.get(0), log::d, log::e, true);
}, log::e, true);
}, log::e, true);*/
updateNavigation();
changeFragment(desiredPage, desiredData);
}
@Override
@@ -162,7 +161,6 @@ public class MainActivity extends AppCompatActivity
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
switch (id) {
case R.id.nav_modules:
changeFragment(FRAGMENT_MODULES);
@@ -170,7 +168,7 @@ public class MainActivity extends AppCompatActivity
case R.id.nav_schedule:
changeFragment(FRAGMENT_SCHEDULE);
break;
case R.id.nav_dining:
case R.id.nav_canteens:
changeFragment(FRAGMENT_CANTEENS);
break;
case R.id.nav_settings:
@@ -184,16 +182,16 @@ public class MainActivity extends AppCompatActivity
startActivity(sendIntent);
break;
case R.id.nav_logout:
this.getKVV().logout();
getKVV().account().logout(true);
getKVV().modules().list().delete();
this.getGoogleAuth().getLoginState(credentials -> {
if (credentials != null) {
this.getGoogleAuth().deleteLoginState(credentials.getUsername(), credentials.getPassword());
}
this.toLogoutState();
});
break;
}
}
DrawerLayout drawer = findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
@@ -214,11 +212,25 @@ public class MainActivity extends AppCompatActivity
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt(ARG_FRAGMENT_PAGE, fragmentPage);
savedInstanceState.putString(ARG_FRAGMENT_STATUS, fragmentData);
if (fragmentPage != FRAGMENT_STARTUP && fragmentPage != FRAGMENT_NONE && fragmentPage != FRAGMENT_LOGIN) {
Fragment fragment = mFragmentManager.findFragmentByTag(String.valueOf(fragmentPage));
savedInstanceState.putInt(ARG_FRAGMENT_PAGE, fragmentPage);
if (fragment instanceof ModDetailFragment) {
savedInstanceState.putString(ARG_FRAGMENT_STATUS, ((ModDetailFragment) fragment).getData());
} else {
savedInstanceState.putString(ARG_FRAGMENT_STATUS, fragmentData);
}
}
super.onSaveInstanceState(savedInstanceState);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
for (RequestPermissionsResultListener listener: permissionListeners.values()) {
listener.callback(requestCode, permissions, grantResults);
}
}
/* --------------------------------------------*/
/* --------------------------------------------*/
/* --------------------------------------------*/
@@ -230,9 +242,17 @@ public class MainActivity extends AppCompatActivity
return this.mGoogleAuth;
}
/*@Deprecated
public de.sebse.fuplanner.services.KVV.KVV getKVV() {
if (this.mKVV == null) {
this.mKVV = new de.sebse.fuplanner.services.KVV.KVV(this);
}
return this.mKVV;
}*/
public KVV getKVV() {
if (this.mKVV == null) {
this.mKVV = new KVV(this);
this.mKVV = new KVV(this, this);
}
return this.mKVV;
}
@@ -249,40 +269,21 @@ public class MainActivity extends AppCompatActivity
}
private void toLogoutState() {
setOfflineBanner(false);
setRefreshFailedBanner(false);
updateNavigation();
changeFragment(FRAGMENT_LOGIN);
setOfflineBanner(true);
}
private void toLoginState(LoginToken loginToken, int newFragment, String newData) {
if (loginToken == null) {
toLogoutState();
} else {
toLoginState(loginToken.getFullname(), loginToken.getEmail(), newFragment, newData, true);
}
}
private void toLoginState(String fullname, String email, int newFragment, String newData, boolean onlineMode) {
changeFragment(newFragment, newData);
private void toLoginState(String fullName, String email, int newFragment, boolean onlineMode) {
setOfflineBanner(!onlineMode);
updateNavigation();
View header = mNavigationView.getHeaderView(0);
((TextView) header.findViewById(R.id.login_name)).setText(fullname);
((TextView) header.findViewById(R.id.login_name)).setText(fullName);
((TextView) header.findViewById(R.id.login_mail)).setText(email);
setOfflineBanner(onlineMode);
}
private void checkAndDoLogin() {
changeFragment(FRAGMENT_STARTUP);
getGoogleAuth().getLoginState(credentials -> {
if (credentials == null || credentials.getUsername() == null || credentials.getPassword() == null) {
toLogoutState();
return;
}
this.getKVV().login(credentials.getUsername(), credentials.getPassword(), success -> toLoginState(success, getDefaultFragmentAfterLogin(), ""),
error -> {
log.e(error);
toLogoutState();
});
});
changeFragment(newFragment);
}
private void changeFragment(int newFragment) {
@@ -290,27 +291,34 @@ public class MainActivity extends AppCompatActivity
}
private void changeFragment(int newFragment, String newData) {
onTitleTextChange(R.string.courses);
if (mFragmentManager.isStateSaved())
return;
if (newFragment == FRAGMENT_CANTEENS_DETAILS && newData.equals(""))
newFragment = FRAGMENT_CANTEENS;
if (newFragment == FRAGMENT_MODULES_DETAILS && newData.equals(""))
newFragment = FRAGMENT_MODULES;
onTitleTextChange(R.string.app_name);
Fragment fragment;
switch (newFragment) {
case FRAGMENT_MODULES:
fragment = ModulesFragment.newInstance();
break;
case FRAGMENT_MODULES_DETAILS:
fragment = ModDetailFragment.newInstance(newData);
break;
case FRAGMENT_MODULES:
fragment = ModulesFragment.newInstance();
break;
case FRAGMENT_LOGIN:
fragment = LoginFragment.newInstance();
break;
case FRAGMENT_SCHEDULE:
fragment = ScheduleFragment.newInstance();
break;
case FRAGMENT_CANTEENS:
fragment = CanteensFragment.newInstance();
break;
case FRAGMENT_CANTEENS_DETAILS:
fragment = DaySwitcherFragment.newInstance(Integer.parseInt(newData));
break;
case FRAGMENT_CANTEENS:
fragment = CanteensFragment.newInstance();
break;
case FRAGMENT_PREFERENCES:
fragment = PrefsFragment.newInstance();
break;
@@ -324,39 +332,100 @@ public class MainActivity extends AppCompatActivity
fragmentTransaction.commit();
if (newFragment == FRAGMENT_STARTUP) {
findViewById(R.id.app_bar_layout).setVisibility(View.GONE);
findViewById(R.id.app_bar_include).setVisibility(View.GONE);
} else {
findViewById(R.id.app_bar_layout).setVisibility(View.VISIBLE);
findViewById(R.id.app_bar_include).setVisibility(View.VISIBLE);
}
// switch to logout
if ((fragmentPage != FRAGMENT_STARTUP && fragmentPage != FRAGMENT_LOGIN) && (newFragment == FRAGMENT_STARTUP || newFragment == FRAGMENT_LOGIN)) {
View header = mNavigationView.getHeaderView(0);
//header.findViewById(R.id.imageView).setVisibility(View.GONE);
header.findViewById(R.id.login_name).setVisibility(View.GONE);
header.findViewById(R.id.login_mail).setVisibility(View.GONE);
header.findViewById(R.id.btn_login_page).setVisibility(View.VISIBLE);
header.findViewById(R.id.btn_login_page).setOnClickListener(v -> {
this.fragmentPage = newFragment;
this.fragmentData = newData;
invalidateOptionsMenu();
}
private void setOfflineBanner(boolean visible) {
View offline_header = findViewById(R.id.offline_msg);
offline_header.setVisibility(visible ? View.VISIBLE : View.GONE);
mOfflineBanner = visible;
}
private void setRefreshFailedBanner(boolean refreshFailed) {
View viewNoConnection = findViewById(R.id.no_connection_msg);
if (!mOfflineBanner && refreshFailed)
viewNoConnection.setVisibility(View.VISIBLE);
else
viewNoConnection.setVisibility(View.GONE);
}
private void setNavigationSelection() {
MenuItem item;
switch (fragmentPage) {
case FRAGMENT_MODULES_DETAILS:
getKVV().modules().list().find(fragmentData, success -> {
int size = mNavigationView.getMenu().size();
//noinspection ConstantConditions
String title = success == null ? null : success.title;
for (int k = 0; k < size; k++) {
mNavigationView.getMenu().getItem(k).setChecked(mNavigationView.getMenu().getItem(k).getTitle().equals(title));
}
}, log::e);
break;
case FRAGMENT_MODULES:
item = mNavigationView.getMenu().findItem(R.id.nav_modules);
if (item != null)
item.setChecked(true);
break;
case FRAGMENT_SCHEDULE:
item = mNavigationView.getMenu().findItem(R.id.nav_schedule);
if (item != null)
item.setChecked(true);
break;
case FRAGMENT_CANTEENS_DETAILS:
getCanteenBrowser().getCanteens(success -> {
int size = mNavigationView.getMenu().size();
Canteen canteen = success.getCanteen(Integer.parseInt(fragmentData));
//noinspection ConstantConditions
String title = canteen == null ? null : canteen.getName();
for (int k = 0; k < size; k++) {
mNavigationView.getMenu().getItem(k).setChecked(mNavigationView.getMenu().getItem(k).getTitle().equals(title));
}
}, log::e);
break;
case FRAGMENT_CANTEENS:
mNavigationView.getMenu().findItem(R.id.nav_canteens).setChecked(true);
break;
case FRAGMENT_PREFERENCES:
mNavigationView.getMenu().findItem(R.id.nav_settings).setChecked(true);
break;
default: // FRAGMENT_STARTUP / FRAGMENT_LOGIN
break;
}
}
private void setNavigationHeader(boolean isLoggedIn) {
View header = mNavigationView.getHeaderView(0);
int login = isLoggedIn ? View.VISIBLE : View.GONE;
int btn = !isLoggedIn ? View.VISIBLE : View.GONE;
header.findViewById(R.id.login_name).setVisibility(login);
header.findViewById(R.id.login_mail).setVisibility(login);
View viewBtn = header.findViewById(R.id.btn_login_page);
viewBtn.setVisibility(btn);
if (!viewBtn.hasOnClickListeners())
viewBtn.setOnClickListener(v -> {
DrawerLayout drawer = findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
}
changeFragment(FRAGMENT_LOGIN);
});
mNavigationView.getMenu().clear();
mNavigationView.inflateMenu(R.menu.activity_main_drawer);
afterAnyMenuInflate();
} // switch to login
else if ((fragmentPage == FRAGMENT_STARTUP || fragmentPage == FRAGMENT_LOGIN || fragmentPage == FRAGMENT_NONE) && (newFragment != FRAGMENT_STARTUP && newFragment != FRAGMENT_LOGIN && newFragment != FRAGMENT_CANTEENS && newFragment != FRAGMENT_CANTEENS_DETAILS)) {
View header = mNavigationView.getHeaderView(0);
//header.findViewById(R.id.imageView).setVisibility(View.VISIBLE);
header.findViewById(R.id.login_name).setVisibility(View.VISIBLE);
header.findViewById(R.id.login_mail).setVisibility(View.VISIBLE);
header.findViewById(R.id.btn_login_page).setVisibility(View.GONE);
mNavigationView.getMenu().clear();
mNavigationView.inflateMenu(R.menu.activity_main_drawer_login);
mNavigationView.setCheckedItem(R.id.nav_modules);
afterAnyMenuInflate();
getKVV().getModuleList(success -> {
}
private void afterAnyMenuInflate(boolean isLoggedIn, Runnable done) {
int MAX_COUNT = isLoggedIn ? 2 : 1;
final int[] count = {0};
if (isLoggedIn) {
getKVV().modules().list().recv(success -> {
int i = 0;
for (Iterator<Modules.Module> it = success.latestSemesterIterator(); it.hasNext(); ) {
Modules.Module module = it.next();
@@ -367,55 +436,12 @@ public class MainActivity extends AppCompatActivity
});
i++;
}
}, log::e);
if (++count[0] == MAX_COUNT) done.run();
}, msg -> {
if (++count[0] == MAX_COUNT) done.run();
log.e(msg);
});
}
if (newFragment == FRAGMENT_MODULES_DETAILS) {
getKVV().getModule(newData, success -> {
int size = mNavigationView.getMenu().size();
//noinspection ConstantConditions
String title = success == null ? null : success.title;
for (int k = 0; k < size; k++) {
mNavigationView.getMenu().getItem(k).setChecked(mNavigationView.getMenu().getItem(k).getTitle().equals(title));
}
}, log::e);
}
if (newFragment == FRAGMENT_CANTEENS_DETAILS) {
getCanteenBrowser().getCanteens(success -> {
int size = mNavigationView.getMenu().size();
Canteen canteen = success.getCanteen(Integer.parseInt(newData));
//noinspection ConstantConditions
String title = canteen == null ? null : canteen.getName();
for (int k = 0; k < size; k++) {
mNavigationView.getMenu().getItem(k).setChecked(mNavigationView.getMenu().getItem(k).getTitle().equals(title));
}
}, log::e);
}
this.fragmentPage = newFragment;
this.fragmentData = newData;
invalidateOptionsMenu();
}
private void setOfflineBanner(boolean onlineMode) {
View offline_header = findViewById(R.id.offline_msg);
if (onlineMode)
offline_header.setVisibility(View.GONE);
else
offline_header.setVisibility(View.VISIBLE);
mOfflineMode = !onlineMode;
}
private void setRefreshFailedBanner(boolean refreshFailed) {
View viewNoConnection = findViewById(R.id.no_connection_msg);
if (!mOfflineMode && refreshFailed)
viewNoConnection.setVisibility(View.VISIBLE);
else
viewNoConnection.setVisibility(View.GONE);
}
private void afterAnyMenuInflate() {
getCanteenBrowser().getCanteens(success -> {
int i = 0;
for (Canteen canteen: success) {
@@ -426,7 +452,27 @@ public class MainActivity extends AppCompatActivity
});
i++;
}
}, log::e);
if (++count[0] == MAX_COUNT) done.run();
}, msg -> {
if (++count[0] == MAX_COUNT) done.run();
log.e(msg);
});
}
private void updateNavigation() {
mQueue.add(() -> {
boolean isLoggedIn = getKVV().account().isLoggedIn();
setNavigationHeader(isLoggedIn);
mNavigationView.getMenu().clear();
if (isLoggedIn)
mNavigationView.inflateMenu(R.menu.activity_main_drawer_login);
else
mNavigationView.inflateMenu(R.menu.activity_main_drawer);
afterAnyMenuInflate(isLoggedIn, () -> {
setNavigationSelection();
mQueue.next();
});
});
}
@@ -435,32 +481,90 @@ public class MainActivity extends AppCompatActivity
public void onLoginFragmentInteraction(LoginToken loginToken, boolean onlineMode) {
toLoginState(loginToken.getFullname(), loginToken.getEmail(), getDefaultFragmentAfterLogin(), "", onlineMode);
}
@Override
public void onModulesFragmentInteraction(final String itemID) {
changeFragment(FRAGMENT_MODULES_DETAILS, itemID);
}
@Override
public void onCanteensFragmentInteraction(final int itemID) {
changeFragment(FRAGMENT_CANTEENS_DETAILS, String.valueOf(itemID));
}
@Override
public void onTitleTextChange(String newTitle) {
setTitle(newTitle);
}
@Override
public void onTitleTextChange(@StringRes int titleId) {
setTitle(titleId);
}
public void loginTokenInvalid(boolean doPrecheck) {
getKVV().invalidate();
checkAndDoLogin();
}
public void refreshFailed(boolean isFailed) {
@Override
public void onRefreshCompleted(boolean isFailed) {
setRefreshFailedBanner(isFailed);
}
@Override
public void addRequestPermissionsResultListener(RequestPermissionsResultListener listener, String id) {
permissionListeners.put(id, listener);
}
@Override
public void removeRequestPermissionsResultListener(String id) {
permissionListeners.remove(id);
}
@Override
public void showToast(@StringRes int msgStringRes) {
showToast(getString(msgStringRes));
}
@Override
public void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
@Override
public void getCredentials(NetworkCallback<Credentials> callback, NetworkErrorCallback error) {
getGoogleAuth().getLoginState(credentials -> {
if (credentials == null || credentials.getUsername() == null || credentials.getPassword() == null) {
error.onError(new NetworkError(200100, 403, "No Google Login available!"));
} else {
callback.onResponse(credentials);
}
});
}
@Override
public void onLogin(LoginToken token, boolean enteringOnlineMode) {
toLoginState(token.getFullName(), token.getEmail(), getDefaultFragmentAfterLogin(), enteringOnlineMode);
}
@Override
public void onLogout() {
toLogoutState();
}
@Override
public void onModuleListChange() {
updateNavigation();
}
@Override
public void onKVVNetworkResponse(NetworkResponse error) {
setRefreshFailedBanner(error != null);
}
}

View File

@@ -1,16 +1,15 @@
package de.sebse.fuplanner.fragments;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.fragments.CanteensFragment.OnCanteensFragmentInteractionListener;
import de.sebse.fuplanner.services.Canteen.types.Canteen;
import de.sebse.fuplanner.services.Canteen.types.Canteens;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.ui.ItemViewHolder;
/**

View File

@@ -2,19 +2,19 @@ package de.sebse.fuplanner.fragments;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import de.sebse.fuplanner.MainActivity;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.Canteen.CanteenBrowser;
import de.sebse.fuplanner.tools.MainAcitivityListener;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger;
/**
@@ -28,6 +28,7 @@ public class CanteensFragment extends Fragment {
private final Logger log = new Logger(this);
private CanteensAdapter adapter;
private SwipeRefreshLayout swipeLayout;
private MainActivityListener mMainActivityListener;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
@@ -69,6 +70,8 @@ public class CanteensFragment extends Fragment {
CanteenBrowser browser = ((MainActivity) getActivity()).getCanteenBrowser();
browser.getCanteens(success -> {
adapter.setCanteens(success);
//if (mMainActivityListener != null)
// mMainActivityListener.refreshNavigation();
swipeLayout.setRefreshing(false);
}, error -> {
log.e(error.toString());
@@ -87,16 +90,18 @@ public class CanteensFragment extends Fragment {
throw new RuntimeException(context.toString()
+ " must implement OnCanteensFragmentInteractionListener");
}
if (context instanceof MainAcitivityListener)
((MainAcitivityListener) context).onTitleTextChange(R.string.canteens);
else
throw new RuntimeException(context.toString() + "must implement MainActivityListener");
if (context instanceof MainActivityListener) {
mMainActivityListener = (MainActivityListener) context;
mMainActivityListener.onTitleTextChange(R.string.canteens);
} else
throw new RuntimeException(context.toString() + " must implement MainActivityListener");
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
mMainActivityListener = null;
}
public interface OnCanteensFragmentInteractionListener {

View File

@@ -3,36 +3,27 @@ package de.sebse.fuplanner.fragments;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import java.io.IOException;
import de.sebse.fuplanner.MainActivity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.GoogleAuth.GoogleAuth;
import de.sebse.fuplanner.services.KVV.KVV;
import de.sebse.fuplanner.services.KVV.types.LoginToken;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.MainAcitivityListener;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger;
/**
* A simple {@link Fragment} subclass.
* Activities that contain this fragment must implement the
* {@link LoginFragment.OnLoginFragmentInteractionListener} interface
* to handle interaction events.
* Use the {@link LoginFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class LoginFragment extends Fragment {
private OnLoginFragmentInteractionListener mListener;
private final Logger log = new Logger(this);
@Nullable private MainActivityListener mActivityListener;
public LoginFragment() {
// Required empty public constructor
@@ -56,21 +47,11 @@ public class LoginFragment extends Fragment {
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_login, container, false);
try {
Context context = getContext();
if (context != null) {
Modules modules = Modules.load(context);
if (modules != null) {
Button offline_btn = v.findViewById(R.id.btn_offline);
offline_btn.setVisibility(View.VISIBLE);
offline_btn.setText(v.getResources().getString(R.string.enter_offline_mode, modules.getToken().getUsername()));
offline_btn.setOnClickListener(v1 -> mListener.onLoginFragmentInteraction(modules.getToken(), false));
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
if (mActivityListener != null && mActivityListener.getKVV().account().isOfflineStoredAvailable()) {
Button offline_btn = v.findViewById(R.id.btn_offline);
offline_btn.setVisibility(View.VISIBLE);
offline_btn.setText(v.getResources().getString(R.string.enter_offline_mode, mActivityListener.getKVV().modules().list().getUsername()));
offline_btn.setOnClickListener(v1 -> mActivityListener.getKVV().account().doOfflineLogin());
}
View btn_login = v.findViewById(R.id.btn_login);
@@ -83,27 +64,29 @@ public class LoginFragment extends Fragment {
EditText input_usr = ((View) view.getParent()).findViewById(R.id.input_username);
EditText input_pwd = ((View) view.getParent()).findViewById(R.id.input_password);
if (input_usr != null) {
if (input_pwd != null) {
if (LoginFragment.this.getActivity() == null) {
log.e("Login fragment has no activity!");
return;
String username = input_usr.getText().toString();
String password = input_pwd.getText().toString();
mActivityListener.getKVV().account().doOnlineLogin(username, password, success -> {
progressDialog.dismiss();
mActivityListener.getGoogleAuth().setLoginState(username, password);
input_usr.setError(null);
input_pwd.setError(null);
}, error -> {
progressDialog.dismiss();
// Invalid password
if (mActivityListener != null) {
if (error.getCode() == 100131) {
mActivityListener.showToast(R.string.invalid_credentials);
input_usr.setError(input_usr.getResources().getString(R.string.invalid_credentials));
input_pwd.setError(input_pwd.getResources().getString(R.string.invalid_credentials));
} else {
mActivityListener.showToast(v.getResources().getString(R.string.error_occurred_code, error.getCode()));
}
String username = input_usr.getText().toString();
String password = input_pwd.getText().toString();
KVV kvv = ((MainActivity) getActivity()).getKVV();
GoogleAuth gauth = ((MainActivity) getActivity()).getGoogleAuth();
kvv.login(username, password, success -> {
progressDialog.dismiss();
gauth.setLoginState(username, password);
if (mListener != null)
mListener.onLoginFragmentInteraction(success, true);
}, error -> {
progressDialog.dismiss();
log.e("Error on KVV login!", error);
});
}
}
log.e("Error on KVV login!", error);
});
});
return v;
@@ -114,35 +97,16 @@ public class LoginFragment extends Fragment {
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnLoginFragmentInteractionListener) {
mListener = (OnLoginFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnLoginFragmentInteractionListener");
}
if (context instanceof MainAcitivityListener)
((MainAcitivityListener) context).onTitleTextChange(R.string.courses);
else
throw new RuntimeException(context.toString() + "must implement MainActivityListener");
if (context instanceof MainActivityListener) {
mActivityListener = (MainActivityListener) context;
mActivityListener.onTitleTextChange(R.string.log_in);
} else
throw new RuntimeException(context.toString() + " must implement MainActivityListener");
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
* <p>
* See the Android Training lesson <a href=
* "http://developer.android.com/training/basics/fragments/communicating.html"
* >Communicating with Other Fragments</a> for more information.
*/
public interface OnLoginFragmentInteractionListener {
void onLoginFragmentInteraction(LoginToken loginToken, boolean onlineMode);
mActivityListener = null;
}
}

View File

@@ -1,66 +1,115 @@
package de.sebse.fuplanner.fragments;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.fragments.ModulesFragment.OnModulesFragmentInteractionListener;
import de.sebse.fuplanner.services.KVV.types.Lecturer;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.services.KVV.types.Semester;
import de.sebse.fuplanner.tools.ui.CustomViewHolder;
import de.sebse.fuplanner.tools.ui.ItemViewHolder;
import de.sebse.fuplanner.tools.ui.StringViewHolder;
/**
* {@link RecyclerView.Adapter} that can display a {@link Modules.Module} and makes a call to the
* specified {@link OnModulesFragmentInteractionListener}.
*/
class ModulesAdapter extends RecyclerView.Adapter<ItemViewHolder> {
class ModulesAdapter extends RecyclerView.Adapter<CustomViewHolder> {
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 2;
private Modules mValues;
private final OnModulesFragmentInteractionListener mListener;
private final ArrayList<Pair<Integer, Object>> mPositionalData;
ModulesAdapter(OnModulesFragmentInteractionListener listener) {
mValues = null;
mListener = listener;
mPositionalData = new ArrayList<>();
}
public void setModules(Modules modules) {
mValues = modules;
mPositionalData.clear();
Semester lastSemester = null;
for (Modules.Module module : mValues) {
Semester semester = module.semester;
if (semester == null)
continue;
if (!semester.equals(lastSemester)) {
mPositionalData.add(new Pair<>(TYPE_HEADER, semester));
lastSemester = semester;
}
mPositionalData.add(new Pair<>(TYPE_ITEM, module));
}
this.notifyDataSetChanged();
}
@Override
public int getItemViewType(int position) {
return mPositionalData.get(position).first;
}
@NonNull
@Override
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_items, parent, false);
return new ItemViewHolder(view);
public CustomViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_caption, parent, false);
return new StringViewHolder(view);
} else {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_items, parent, false);
return new ItemViewHolder(view);
}
}
@Override
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) {
if (mValues == null)
return;
Modules.Module module = mValues.getByIndex(holder.getAdapterPosition());
holder.mTitle.setText(module.title);
holder.mSubLeft.setText(module.semester);
holder.mSubRight.setText(module.type);
holder.mView.setOnClickListener(v -> {
if (null != mListener) {
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
mListener.onModulesFragmentInteraction(module.getID());
public void onBindViewHolder(@NonNull CustomViewHolder holder, int position) {
Pair<Integer, Object> pair = mPositionalData.get(holder.getAdapterPosition());
if (pair.first == TYPE_HEADER) {
StringViewHolder sHolder = (StringViewHolder) holder;
String localizedSemester;
Semester semester = (Semester) pair.second;
if (semester.getType() == Semester.SEM_WS)
localizedSemester = holder.mView.getResources().getString(R.string.winter_semester, semester.getYear(), semester.getYear()+1);
else
localizedSemester = holder.mView.getResources().getString(R.string.summer_semester, semester.getYear());
sHolder.mString.setText(localizedSemester);
} else if (pair.first == TYPE_ITEM) {
ItemViewHolder iHolder = (ItemViewHolder) holder;
Modules.Module module = ((Modules.Module) pair.second);
iHolder.mTitle.setText(module.title);
StringBuilder lecturers = new StringBuilder();
for (Lecturer lecturer: module.lecturer) {
if (!lecturer.isResponsible())
continue;
if (lecturers.length() > 0)
lecturers.append(", ");
lecturers.append(lecturer.getNameShort());
}
});
iHolder.mSubLeft.setText(lecturers);
iHolder.mSubRight.setText(module.type);
iHolder.mView.setOnClickListener(v -> {
if (mListener != null) {
mListener.onModulesFragmentInteraction(module.getID());
}
});
}
}
@Override
public int getItemCount() {
if (mValues != null) {
return mValues.size();
}
return 0;
return mPositionalData.size();
}
}

View File

@@ -2,19 +2,18 @@ package de.sebse.fuplanner.fragments;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import de.sebse.fuplanner.MainActivity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.KVV;
import de.sebse.fuplanner.tools.MainAcitivityListener;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger;
/**
@@ -28,6 +27,7 @@ public class ModulesFragment extends Fragment {
private final Logger log = new Logger(this);
private ModulesAdapter adapter;
private SwipeRefreshLayout swipeLayout;
@Nullable private MainActivityListener mMainActivityListener;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
@@ -65,10 +65,11 @@ public class ModulesFragment extends Fragment {
}
private void refresh(boolean forceRefresh) {
if (getActivity() != null) {
KVV kvv = ((MainActivity) getActivity()).getKVV();
kvv.getModuleList(success -> {
if (mMainActivityListener != null) {
mMainActivityListener.getKVV().modules().list().recv(success -> {
adapter.setModules(success);
//if (mMainActivityListener != null)
// mMainActivityListener.refreshNavigation();
swipeLayout.setRefreshing(false);
}, error -> {
log.e(error.toString());
@@ -87,16 +88,19 @@ public class ModulesFragment extends Fragment {
throw new RuntimeException(context.toString()
+ " must implement OnModulesFragmentInteractionListener");
}
if (context instanceof MainAcitivityListener)
((MainAcitivityListener) context).onTitleTextChange(R.string.courses);
if (context instanceof MainActivityListener) {
mMainActivityListener = (MainActivityListener) context;
mMainActivityListener.onTitleTextChange(R.string.courses);
}
else
throw new RuntimeException(context.toString() + "must implement MainActivityListener");
throw new RuntimeException(context.toString() + " must implement MainActivityListener");
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
mMainActivityListener = null;
}
public interface OnModulesFragmentInteractionListener {

View File

@@ -2,9 +2,9 @@ package de.sebse.fuplanner.fragments;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceFragmentCompat;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.tools.logging.Logger;

View File

@@ -4,8 +4,6 @@ import android.app.AlertDialog;
import android.content.Context;
import android.graphics.RectF;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -15,12 +13,13 @@ import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.KVV;
import de.sebse.fuplanner.services.KVV.types.Event;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.DateUtils;
import de.sebse.fuplanner.tools.MainAcitivityListener;
import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.ui.weekview.MonthLoader;
import de.sebse.fuplanner.tools.ui.weekview.WeekView;
@@ -29,14 +28,14 @@ import de.sebse.fuplanner.tools.ui.weekview.WeekViewEvent;
/**
* A simple {@link Fragment} subclass.
* Activities that contain this fragment must implement the
* {@link MainAcitivityListener} interface
* {@link MainActivityListener} interface
* to handle interaction events.
* Use the {@link ScheduleFragment#newInstance} factory method to
* create an instance of this fragment.
*
*/
public class ScheduleFragment extends Fragment implements MonthLoader.MonthChangeListener, WeekView.ScrollListener, WeekView.DoubleTapListener, WeekView.EventClickListener {
private MainAcitivityListener mListener;
private MainActivityListener mListener;
private WeekView mWeekView;
private final Logger log = new Logger(this);
private Modules mModules = null;
@@ -59,24 +58,22 @@ public class ScheduleFragment extends Fragment implements MonthLoader.MonthChang
}
public void invalidate(boolean forceRefresh) {
if (mListener != null) {
KVV kvv = mListener.getKVV();
kvv.getModuleList((Modules success) -> {
mModules = success;
final int[] i = {0};
for (Modules.Module module: mModules) {
kvv.getModuleEvents(module, success1 -> {
i[0]++;
if (i[0] >= mModules.size()) {
if (mWeekView != null) {
mWeekView.invalidate();
mWeekView.notifyDatasetChanged();
}
if (mListener == null) return;
mListener.getKVV().modules().list().recv(modules -> {
mModules = modules;
final int[] i = {0};
for (Modules.Module module: mModules) {
mListener.getKVV().modules().events().recv(module, success1 -> {
i[0]++;
if (i[0] >= mModules.size()) {
if (mWeekView != null) {
mWeekView.invalidate();
mWeekView.notifyDatasetChanged();
}
}, log::e, forceRefresh);
}
}, log::e, forceRefresh);
}
}
}, log::e, forceRefresh);
}
}, log::e, forceRefresh);
}
@Override
@@ -99,8 +96,8 @@ public class ScheduleFragment extends Fragment implements MonthLoader.MonthChang
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof MainAcitivityListener) {
mListener = (MainAcitivityListener) context;
if (context instanceof MainActivityListener) {
mListener = (MainActivityListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
@@ -144,7 +141,7 @@ public class ScheduleFragment extends Fragment implements MonthLoader.MonthChang
public void onFirstVisibleDayChanged(Calendar newFirstVisibleDay, Calendar oldFirstVisibleDay) {
Calendar newLastVisibleDay = (Calendar) newFirstVisibleDay.clone();
newLastVisibleDay.add(Calendar.HOUR, 24*mWeekView.getNumberOfVisibleDays());
mListener.onTitleTextChange(getResources().getString(R.string.date_scale, DateUtils.getModifiedDate(newFirstVisibleDay.getTimeInMillis()), DateUtils.getModifiedDate(newLastVisibleDay.getTimeInMillis())));
mListener.onTitleTextChange(getResources().getString(R.string.date_scale, UtilsDate.getModifiedDate(newFirstVisibleDay.getTimeInMillis()), UtilsDate.getModifiedDate(newLastVisibleDay.getTimeInMillis())));
}
@@ -157,9 +154,11 @@ public class ScheduleFragment extends Fragment implements MonthLoader.MonthChang
Calendar firstVisibleDay = mWeekView.getFirstVisibleDay();
Calendar c = Calendar.getInstance();
c.set(firstVisibleDay.get(Calendar.YEAR), firstVisibleDay.get(Calendar.MONTH), firstVisibleDay.get(Calendar.DAY_OF_MONTH), 0,0 );
c.add(Calendar.DATE, 2); // die Grenze beim tippen, wann ver nach vorne und wann nach hinten springt
// Limit when to go a week backwards and when to the next week
c.add(Calendar.DATE, 2);
if (c.getTimeInMillis() > time.getTimeInMillis()){//nach links blättern
// skip to next week
if (c.getTimeInMillis() > time.getTimeInMillis()){
firstVisibleDay.add(Calendar.DATE, -1);
while (firstVisibleDay.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
firstVisibleDay.add(Calendar.DATE, -1);
@@ -172,6 +171,7 @@ public class ScheduleFragment extends Fragment implements MonthLoader.MonthChang
}
}
mWeekView.goToDate(firstVisibleDay);
onFirstVisibleDayChanged(firstVisibleDay, mWeekView.getLastVisibleDay());
}
@Override
@@ -180,24 +180,21 @@ public class ScheduleFragment extends Fragment implements MonthLoader.MonthChang
String moduleId = idParts[0];
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext());
if (mListener != null) {
KVV kvv = mListener.getKVV();
kvv.getModuleList((Modules success) -> {
Modules.Module module = success.get(moduleId);
String moduleName = module.title;
alertDialogBuilder
.setTitle(event.getName())
.setMessage(
getResources().getString(R.string.module_name, moduleName) + "\n" +
getResources().getString(R.string.location_name, event.getLocation()) + "\n" +
getResources().getString(R.string.date_scale, DateUtils.getModifiedTime(getContext(), event.getStartTime().getTimeInMillis()), DateUtils.getModifiedTime(getContext(), event.getEndTime().getTimeInMillis()+1))
)
.setCancelable(true)
.setNeutralButton(R.string.close, (dialog, id) -> dialog.cancel());
if (mListener == null) return;
mListener.getKVV().modules().list().find(moduleId, module -> {
String moduleName = module.title;
alertDialogBuilder
.setTitle(event.getName())
.setMessage(
getResources().getString(R.string.module_name, moduleName) + "\n" +
getResources().getString(R.string.location_name, event.getLocation()) + "\n" +
getResources().getString(R.string.date_scale, UtilsDate.getModifiedTime(getContext(), event.getStartTime().getTimeInMillis()), UtilsDate.getModifiedTime(getContext(), event.getEndTime().getTimeInMillis()+1))
)
.setCancelable(true)
.setNeutralButton(R.string.close, (dialog, id) -> dialog.cancel());
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}, log::e);
}
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}, log::e);
}
}

View File

@@ -1,12 +1,12 @@
package de.sebse.fuplanner.fragments;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import de.sebse.fuplanner.R;
/**

View File

@@ -1,11 +1,11 @@
package de.sebse.fuplanner.fragments.canteen;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import de.sebse.fuplanner.services.Canteen.types.Canteen;
import de.sebse.fuplanner.tools.DateUtils;
import de.sebse.fuplanner.tools.UtilsDate;
class DaySwitcherAdapter extends FragmentStatePagerAdapter {
private Canteen mCanteen = null;
@@ -39,7 +39,7 @@ class DaySwitcherAdapter extends FragmentStatePagerAdapter {
// Returns the page title for the top indicator
@Override
public CharSequence getPageTitle(int position) {
return DateUtils.getModifiedDate(mCanteen.get(position).getCalendar().getTimeInMillis());
return UtilsDate.getModifiedDate(mCanteen.get(position).getCalendar().getTimeInMillis());
}
}

View File

@@ -2,18 +2,18 @@ package de.sebse.fuplanner.fragments.canteen;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.viewpager.widget.ViewPager;
import de.sebse.fuplanner.MainActivity;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.Canteen.CanteenBrowser;
import de.sebse.fuplanner.services.Canteen.types.Canteen;
import de.sebse.fuplanner.tools.MainAcitivityListener;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
@@ -21,7 +21,7 @@ import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
/**
* A simple {@link Fragment} subclass.
* Activities that contain this fragment must implement the
* {@link MainAcitivityListener} interface
* {@link MainActivityListener} interface
* to handle interaction events.
* Use the {@link DaySwitcherFragment#newInstance} factory method to
* create an instance of this fragment.
@@ -32,9 +32,8 @@ public class DaySwitcherFragment extends Fragment implements DaySwitcherListener
// Parameters
private int mCanteenId;
private MainAcitivityListener mListener;
private MainActivityListener mListener;
private final Logger log = new Logger(this);
private ViewPager mViewPager;
private DaySwitcherAdapter adapterViewPager;
public DaySwitcherFragment() {
@@ -78,11 +77,11 @@ public class DaySwitcherFragment extends Fragment implements DaySwitcherListener
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_mod_detail, container, false);
mViewPager = v.findViewById(R.id.vpPager);
ViewPager mViewPager = v.findViewById(R.id.vpPager);
adapterViewPager = new DaySwitcherAdapter(getChildFragmentManager());
mViewPager.setAdapter(adapterViewPager);
refresh(false);
refresh();
return v;
}
@@ -90,8 +89,8 @@ public class DaySwitcherFragment extends Fragment implements DaySwitcherListener
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof MainAcitivityListener) {
mListener = (MainAcitivityListener) context;
if (context instanceof MainActivityListener) {
mListener = (MainActivityListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement MainActivityListener");
@@ -104,8 +103,8 @@ public class DaySwitcherFragment extends Fragment implements DaySwitcherListener
mListener = null;
}
private void refresh(boolean forceRefresh) {
refresh(forceRefresh, null, null);
private void refresh() {
refresh(false, null, null);
}
private void refresh(boolean forceRefresh, NetworkCallback<Canteen> callback, NetworkErrorCallback errorCallback) {
@@ -113,6 +112,7 @@ public class DaySwitcherFragment extends Fragment implements DaySwitcherListener
CanteenBrowser browser = ((MainActivity) getActivity()).getCanteenBrowser();
browser.getCanteens(canteens -> {
Canteen canteen = canteens.getCanteen(mCanteenId);
canteen.cleanUpDays();
adapterViewPager.setModule(canteen);
browser.getCanteen(canteen, success -> {
adapterViewPager.setModule();

View File

@@ -4,16 +4,28 @@ import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.recyclerview.widget.RecyclerView;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.Canteen.types.Day;
import de.sebse.fuplanner.services.Canteen.types.Meal;
import de.sebse.fuplanner.tools.Preferences;
import de.sebse.fuplanner.tools.ui.ItemViewHolder;
import de.sebse.fuplanner.tools.ui.MealViewHolder;
import de.sebse.fuplanner.tools.ui.StringViewHolder;
class MealAdapter extends BaseExpandableListAdapter {
class MealAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private String[] CATEGORY_KEYS = new String[]{"Essen", "Aktionen", "Beilagen", "Desserts", "Salate", "Suppen", "Vorspeisen"};
@StringRes
private int[] CATEGORY_VALS = new int[]{R.string.meals, R.string.special_meals, R.string.side_dishes, R.string.desserts, R.string.salads, R.string.soups, R.string.starters};
@StringRes
private int CATEGORY_OTHER = R.string.others;
private ArrayList<Object> matches = new ArrayList<>();
private Day mDay = null;
private final Context mContext;
@@ -22,105 +34,104 @@ class MealAdapter extends BaseExpandableListAdapter {
mContext = context;
}
@NonNull
@Override
public String getChild(int groupPosition, int childPosititon) {
StringBuilder sb = new StringBuilder();
sb.append("\n\n");
for (String s : this.getGroup(groupPosition).getNotes())
{
sb.append(s);
sb.append("\n\n");
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
if (viewType == 0) {
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.list_canteen_items, viewGroup, false);
return new MealViewHolder(view);
} else {
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.list_all_caption, viewGroup, false);
return new StringViewHolder(view);
}
return sb.toString();
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public View getChildView(int groupPosition, final int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
final String childText = getChild(groupPosition, childPosition);
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_string, parent, false);
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder.getItemViewType() == 0) {
MealViewHolder viewHolder = ((MealViewHolder) holder);
viewHolder.reset();
Meal meal = getMeal(position);
viewHolder.mTitle.setText(meal.getName());
String value;
switch (Preferences.getString(mContext, R.array.pref_price_group)) {
case "student":
if (meal.getPriceStdnt() < 0)
value = mContext.getResources().getString(R.string.no_price_available);
else
value = mContext.getString(R.string.price, meal.getPriceStdnt());
break;
case "employee":
if (meal.getPriceEmply() < 0)
value = mContext.getResources().getString(R.string.no_price_available);
else
value = mContext.getString(R.string.price, meal.getPriceEmply());
break;
case "other":
if (meal.getPriceOther() < 0)
value = mContext.getResources().getString(R.string.no_price_available);
else
value = mContext.getString(R.string.price, meal.getPriceOther());
break;
default:
String value1;
if (meal.getPriceStdnt() < 0)
value1 = " -/- ";
else
value1 = mContext.getString(R.string.price, meal.getPriceStdnt());
String value2;
if (meal.getPriceEmply() < 0)
value2 = " -/- ";
else
value2 = mContext.getString(R.string.price, meal.getPriceEmply());
String value3;
if (meal.getPriceOther() < 0)
value3 = " -/- ";
else
value3 = mContext.getString(R.string.price, meal.getPriceOther());
value = mContext.getString(R.string.prices, value1, value2, value3);
}
viewHolder.mSubTitle.setText(value);
StringBuilder string = new StringBuilder();
List<String> notes = meal.getNotes();
for (int i1 = 0, notesSize = notes.size(); i1 < notesSize; i1++) {
if (i1 != 0)
string.append("\n");
String s = notes.get(i1);
string.append(" - ").append(s);
}
viewHolder.mNotes.setText(string.toString());
viewHolder.mIconVegan.setVisibility(meal.getVegan() == Meal.VEGAN_VEGAN ? View.VISIBLE : View.GONE);
viewHolder.mIconVegetarian.setVisibility(meal.getVegan() == Meal.VEGAN_VEGETARIAN ? View.VISIBLE : View.GONE);
viewHolder.mIconBio.setVisibility((meal.getCertificates() & Meal.CERT_BIO) != 0 ? View.VISIBLE : View.GONE);
viewHolder.mIconMsc.setVisibility((meal.getCertificates() & Meal.CERT_MSC) != 0 ? View.VISIBLE : View.GONE);
} else {
StringViewHolder viewHolder = ((StringViewHolder) holder);
viewHolder.mString.setText(getHeading(position));
}
StringViewHolder itemHolder = new StringViewHolder(convertView);
itemHolder.mString.setText(childText);
return convertView;
}
@Override
public int getChildrenCount(int groupPosition) {
return 1;
public int getItemCount() {
return matches.size();
}
@Override
public Meal getGroup(int groupPosition) {
public int getItemViewType(int position) {
return matches.get(position) instanceof String ? 1 : 0;
}
private Meal getMeal(int position) {
if (this.mDay != null)
return this.mDay.get(groupPosition);
return this.mDay.get((Integer) matches.get(position));
else
return null;
}
@Override
public int getGroupCount() {
if (this.mDay != null)
return this.mDay.size();
else
return 0;
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
Meal meal = getGroup(groupPosition);
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_items, parent, false);
}
ItemViewHolder itemHolder = new ItemViewHolder(convertView);
itemHolder.mTitle.setText(meal.getName());
itemHolder.mSubLeft.setText(meal.getCategory());
String value;
switch (Preferences.getString(mContext, R.array.pref_price_group)) {
case "student":
value = mContext.getString(R.string.price, meal.getPriceStdnt());
break;
case "employee":
value = mContext.getString(R.string.price, meal.getPriceEmply());
break;
case "other":
value = mContext.getString(R.string.price, meal.getPriceOther());
break;
default:
value = mContext.getString(R.string.prices, meal.getPriceStdnt(), meal.getPriceEmply(), meal.getPriceOther());
}
itemHolder.mSubRight.setText(value);
return convertView;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return false;
private String getHeading(int position) {
return (String) matches.get(position);
}
public void setDay(Day day) {
@@ -129,6 +140,37 @@ class MealAdapter extends BaseExpandableListAdapter {
}
public void setDay() {
HashMap<String, ArrayList<Integer>> map = new HashMap<>();
for (int i = 0; i < this.mDay.size(); i++) {
String category = this.mDay.get(i).getCategory();
boolean found = false;
for (String category_key : CATEGORY_KEYS) {
if (category_key.equals(category)) {
found = true;
break;
}
}
if (!found) category = "---";
ArrayList<Integer> list = map.get(category);
if (list == null) {
list = new ArrayList<>();
map.put(category, list);
}
list.add(i);
}
matches.clear();
for (int i = 0; i < CATEGORY_KEYS.length; i++) {
ArrayList<Integer> list = map.get(CATEGORY_KEYS[i]);
if (list != null) {
matches.add(mContext.getString(CATEGORY_VALS[i]));
matches.addAll(list);
}
}
ArrayList<Integer> list = map.get("---");
if (list != null) {
matches.add(mContext.getString(CATEGORY_OTHER));
matches.addAll(list);
}
this.notifyDataSetChanged();
}
}

View File

@@ -3,14 +3,14 @@ package de.sebse.fuplanner.fragments.canteen;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import de.sebse.fuplanner.MainActivity;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.Canteen.CanteenBrowser;
@@ -43,9 +43,9 @@ public class MealFragment extends Fragment {
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param canteenId Item position in module list.
* @param dayPosition Item position in module list.
* @return A new instance of fragment ModDetailAnnounceFragment.
* @param canteenId ID od current canteen.
* @param dayPosition day to show.
* @return A new instance of fragment MealFragment.
*/
public static MealFragment newInstance(int canteenId, int dayPosition) {
MealFragment fragment = new MealFragment();
@@ -69,9 +69,9 @@ public class MealFragment extends Fragment {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_expandable_list_view, container, false);
View view = inflater.inflate(R.layout.fragment_recycler_view, container, false);
// Set the adapter
ExpandableListView expandableListView = view.findViewById(R.id.list);
RecyclerView expandableListView = view.findViewById(R.id.list);
adapter = new MealAdapter(getContext());
expandableListView.setAdapter(adapter);

View File

@@ -1,9 +1,9 @@
package de.sebse.fuplanner.fragments.moddetails;
import android.content.Context;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import de.sebse.fuplanner.R;
@@ -38,6 +38,8 @@ class ModDetailAdapter extends FragmentStatePagerAdapter {
return ModDetailEventFragment.newInstance(mItemPos);
case ModulePart.GRADEBOOK:
return ModDetailGradebookFragment.newInstance(mItemPos);
case ModulePart.RESOURCES:
return ModDetailResourceFragment.newInstance(mItemPos);
default:
return null;
}
@@ -57,6 +59,8 @@ class ModDetailAdapter extends FragmentStatePagerAdapter {
return this.mContext.getResources().getString(R.string.events);
case ModulePart.GRADEBOOK:
return this.mContext.getResources().getString(R.string.gradebook);
case ModulePart.RESOURCES:
return this.mContext.getResources().getString(R.string.resources);
default:
return "";
}

View File

@@ -1,107 +1,38 @@
package de.sebse.fuplanner.fragments.moddetails;
import android.content.res.Resources;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import java.util.ArrayList;
import com.cunoraz.tagview.Tag;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.ui.Download;
import de.sebse.fuplanner.services.KVV.types.Announcement;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.DateUtils;
import de.sebse.fuplanner.tools.ui.ItemViewHolder;
import de.sebse.fuplanner.tools.ui.StringViewHolder;
import de.sebse.fuplanner.tools.Regex;
import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.ui.AnnouncementViewHolder;
import de.sebse.fuplanner.tools.ui.CustomViewHolder;
class ModDetailAnnounceAdapter extends BaseExpandableListAdapter {
class ModDetailAnnounceAdapter extends RecyclerView.Adapter<CustomViewHolder> {
private Modules.Module mModule = null;
@Nullable private Modules.Module mModule = null;
private Logger log = new Logger(this);
@NonNull private Download.OnDownloadRequestInterface requestInterface;
@Override
public String getChild(int groupPosition, int childPosititon) {
StringBuilder s = new StringBuilder(this.getGroup(groupPosition).getBody());
ArrayList<String> urls = this.getGroup(groupPosition).getUrls();
for (int j =0; j<urls.size(); j++){
s.append("\n");
s.append(urls.get(j));
}
return s.toString();
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public View getChildView(int groupPosition, final int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
final String childText = getChild(groupPosition, childPosition);
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_string, parent, false);
}
StringViewHolder itemHolder = new StringViewHolder(convertView);
itemHolder.mString.setText(childText);
return convertView;
}
@Override
public int getChildrenCount(int groupPosition) {
return 1;
}
@Override
public Announcement getGroup(int groupPosition) {
if (this.mModule != null && this.mModule.announcements != null)
return this.mModule.announcements.get(groupPosition);
else
return null;
}
@Override
public int getGroupCount() {
if (this.mModule != null && this.mModule.announcements != null)
return this.mModule.announcements.size();
else
return 0;
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
Announcement announce = getGroup(groupPosition);
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_items, parent, false);
}
ItemViewHolder itemHolder = new ItemViewHolder(convertView);
itemHolder.mTitle.setText(announce.getTitle());
itemHolder.mSubLeft.setText(announce.getCreatedBy());
itemHolder.mSubRight.setText(DateUtils.getModifiedDateTime(parent.getContext(), announce.getCreatedOn()));
return convertView;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return false;
ModDetailAnnounceAdapter(@NonNull Download.OnDownloadRequestInterface requestInterface) {
this.requestInterface = requestInterface;
}
public void setModule(Modules.Module module) {
@@ -112,4 +43,87 @@ class ModDetailAnnounceAdapter extends BaseExpandableListAdapter {
public void setModule() {
this.notifyDataSetChanged();
}
@NonNull
public CustomViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
if (viewType == 0) {
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.list_announcement_items, viewGroup, false);
return new AnnouncementViewHolder(view);
} else {
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.list_all_no_items, viewGroup, false);
return new CustomViewHolder(view);
}
}
@Override
public void onBindViewHolder(@NonNull CustomViewHolder customHolder, int position) {
if (customHolder instanceof AnnouncementViewHolder) {
AnnouncementViewHolder holder = (AnnouncementViewHolder) customHolder;
holder.reset();
Announcement item = getAnnouncement(position);
holder.mTitle.setText(item.getTitle());
holder.mSubTitle.setText(UtilsDate.getModifiedDateTime(holder.mView.getContext(), item.getCreatedOn()));
holder.mTagGroup.removeAll();
List<String> notes = item.getUrls();
if (!notes.isEmpty()) {
holder.mTagGroup.setVisibility(View.VISIBLE);
for (int i = 0, notesSize = notes.size(); i < notesSize; i++) {
String name = urlToName(notes.get(i), i, holder.mView.getResources());
Tag tag = new Tag(name);
tag.id = i;
tag.layoutColor = ContextCompat.getColor(holder.mView.getContext(), R.color.colorFUBlue);
holder.mTagGroup.addTag(tag);
}
holder.mTagGroup.setOnTagClickListener((tag, i) -> {
String s = notes.get(i);
if (s != null) {
String name = urlToName(s, i, holder.mView.getResources());
requestInterface.request(name, s);
}
});
} else {
holder.mTagGroup.setVisibility(View.GONE);
}
holder.mNotes.setText(item.getBody());
}
}
private String urlToName(String url, int index, Resources res) {
try {
return URLDecoder.decode(Regex.regex("/([^/]*)$", url), "UTF-8");
} catch (NoSuchFieldException e) {
e.printStackTrace();
return res.getString(R.string.attachment_nr, index);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return res.getString(R.string.attachment_nr, index);
}
}
@Override
public int getItemCount() {
if (mModule != null && mModule.announcements != null)
return Math.max(mModule.announcements.size(), 1);
else
return 0;
}
@Override
public int getItemViewType(int position) {
if (mModule != null && mModule.announcements != null && mModule.announcements.size() == 0)
return 1;
else
return 0;
}
private Announcement getAnnouncement(int index) {
if (mModule != null && mModule.announcements != null)
return mModule.announcements.get(index);
else
return null;
}
}

View File

@@ -1,19 +1,22 @@
package de.sebse.fuplanner.fragments.moddetails;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import de.sebse.fuplanner.MainActivity;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.KVV;
import de.sebse.fuplanner.services.KVV.ui.Download;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger;
/**
@@ -21,13 +24,15 @@ import de.sebse.fuplanner.tools.logging.Logger;
* Use the {@link ModDetailAnnounceFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class ModDetailAnnounceFragment extends Fragment {
public class ModDetailAnnounceFragment extends Fragment implements Download.OnDownloadRequestInterface {
private static final String ARG_POSITION = "itemPosition";
private String mItemPos;
private final Logger log = new Logger(this);
private ModDetailAnnounceAdapter adapter;
private SwipeRefreshLayout swipeLayout;
private Download download;
@Nullable private MainActivityListener mListener;
public ModDetailAnnounceFragment() {
@@ -61,10 +66,10 @@ public class ModDetailAnnounceFragment extends Fragment {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_expandable_list_view, container, false);
View view = inflater.inflate(R.layout.fragment_recycler_view, container, false);
// Set the adapter
ExpandableListView expandableListView = view.findViewById(R.id.list);
adapter = new ModDetailAnnounceAdapter();
RecyclerView expandableListView = view.findViewById(R.id.list);
adapter = new ModDetailAnnounceAdapter(this);
expandableListView.setAdapter(adapter);
// Getting SwipeContainerLayout
@@ -77,22 +82,50 @@ public class ModDetailAnnounceFragment extends Fragment {
}
private void refresh(boolean forceRefresh) {
if (getActivity() != null) {
KVV kvv = ((MainActivity) getActivity()).getKVV();
kvv.getModule(mItemPos, (Modules.Module module) -> {
adapter.setModule(module);
kvv.getModuleAnnouncements(module, success1 -> {
adapter.setModule();
swipeLayout.setRefreshing(false);
}, error -> {
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
}, error -> {
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
if (mListener == null)
return;
mListener.getKVV().modules().announcements().recv(mItemPos, success -> {
adapter.setModule(success);
swipeLayout.setRefreshing(false);
}, error -> {
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
}
@Override
public void request(String title, String url) {
if (mListener == null)
return;
mListener.getKVV().modules().list().find(mItemPos, (Modules.Module module) -> {
String folderName = "FU-"+module.title.replaceAll("[:*<>|/\"\\\\]", "-");
folderName += "/Assignment";
getDownload().openDownloadDialog(title, url, folderName);
}, log::e);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof MainActivityListener) {
this.mListener = ((MainActivityListener) context);
this.mListener.addRequestPermissionsResultListener(getDownload().getRequestPermissionsResultListener(), "ModDetailAnnounceFragment");
} else
throw new RuntimeException(context.toString() + " must implement MainActivityListener");
}
@Override
public void onDetach() {
super.onDetach();
if (this.mListener != null) {
this.mListener.removeRequestPermissionsResultListener("ModDetailAnnounceFragment");
this.mListener = null;
}
}
Download getDownload() {
if (download == null)
download = new Download(this::getContext, () -> (MainActivity) getActivity());
return download;
}
}

View File

@@ -1,110 +1,38 @@
package de.sebse.fuplanner.fragments.moddetails;
import android.content.res.Resources;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import com.cunoraz.tagview.Tag;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.ui.Download;
import de.sebse.fuplanner.services.KVV.types.Assignment;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.DateUtils;
import de.sebse.fuplanner.tools.ui.ItemViewHolder;
import de.sebse.fuplanner.tools.ui.StringViewHolder;
import de.sebse.fuplanner.tools.Regex;
import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.ui.AnnouncementViewHolder;
import de.sebse.fuplanner.tools.ui.CustomViewHolder;
class ModDetailAssignmentAdapter extends BaseExpandableListAdapter {
class ModDetailAssignmentAdapter extends RecyclerView.Adapter<CustomViewHolder> {
private Modules.Module mModule = null;
@Nullable private Modules.Module mModule = null;
private Logger log = new Logger(this);
@NonNull private Download.OnDownloadRequestInterface requestInterface;
@Override
public String getChild(int groupPosition, int childPosititon) {
StringBuilder sb = new StringBuilder();
sb.append(this.getGroup(groupPosition).getInstructions());
sb.append("\n\n");
for (String s : this.getGroup(groupPosition).getUrls())
{
sb.append(s);
sb.append("\n\n");
}
return sb.toString();
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public View getChildView(int groupPosition, final int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
final String childText = getChild(groupPosition, childPosition);
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_string, parent, false);
}
StringViewHolder itemHolder = new StringViewHolder(convertView);
itemHolder.mString.setText(childText);
return convertView;
}
@Override
public int getChildrenCount(int groupPosition) {
return 1;
}
@Override
public Assignment getGroup(int groupPosition) {
if (this.mModule != null && this.mModule.assignments != null)
return this.mModule.assignments.get(groupPosition);
else
return null;
}
@Override
public int getGroupCount() {
if (this.mModule != null && this.mModule.assignments != null)
return this.mModule.assignments.size();
else
return 0;
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
Assignment assignment = getGroup(groupPosition);
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_items, parent, false);
}
ItemViewHolder itemHolder = new ItemViewHolder(convertView);
itemHolder.mTitle.setText(assignment.getTitle());
if(assignment.isOpen())
itemHolder.mSubLeft.setText(itemHolder.mView.getResources().getText(R.string.open));
else
itemHolder.mSubLeft.setText(itemHolder.mView.getResources().getText(R.string.closed));
itemHolder.mSubRight.setText(DateUtils.getModifiedDateTime(parent.getContext(), assignment.getDueDate()));
return convertView;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return false;
ModDetailAssignmentAdapter(@NonNull Download.OnDownloadRequestInterface requestInterface) {
this.requestInterface = requestInterface;
}
public void setModule(Modules.Module module) {
@@ -115,4 +43,87 @@ class ModDetailAssignmentAdapter extends BaseExpandableListAdapter {
public void setModule() {
this.notifyDataSetChanged();
}
@NonNull
public CustomViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
if (viewType == 0) {
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.list_announcement_items, viewGroup, false);
return new AnnouncementViewHolder(view);
} else {
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.list_all_no_items, viewGroup, false);
return new CustomViewHolder(view);
}
}
@Override
public void onBindViewHolder(@NonNull CustomViewHolder customHolder, int position) {
if (customHolder instanceof AnnouncementViewHolder) {
AnnouncementViewHolder holder = (AnnouncementViewHolder) customHolder;
holder.reset();
Assignment item = getAssignment(position);
holder.mTitle.setText(item.getTitle());
holder.mSubTitle.setText(UtilsDate.getModifiedDateTime(holder.mView.getContext(), item.getDueDate()));
holder.mTagGroup.removeAll();
List<String> notes = item.getUrls();
if (!notes.isEmpty()) {
holder.mTagGroup.setVisibility(View.VISIBLE);
for (int i = 0, notesSize = notes.size(); i < notesSize; i++) {
String name = urlToName(notes.get(i), i, holder.mView.getResources());
Tag tag = new Tag(name);
tag.id = i;
tag.layoutColor = ContextCompat.getColor(holder.mView.getContext(), R.color.colorFUBlue);
holder.mTagGroup.addTag(tag);
}
holder.mTagGroup.setOnTagClickListener((tag, i) -> {
String s = notes.get(i);
if (s != null) {
String name = urlToName(s, i, holder.mView.getResources());
requestInterface.request(name, s);
}
});
} else {
holder.mTagGroup.setVisibility(View.GONE);
}
holder.mNotes.setText(item.getInstructions());
}
}
private String urlToName(String url, int index, Resources res) {
try {
return URLDecoder.decode(Regex.regex("/([^/]*)$", url), "UTF-8");
} catch (NoSuchFieldException e) {
e.printStackTrace();
return res.getString(R.string.attachment_nr, index);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return res.getString(R.string.attachment_nr, index);
}
}
@Override
public int getItemCount() {
if (mModule != null && mModule.assignments != null)
return Math.max(mModule.assignments.size(), 1);
else
return 0;
}
@Override
public int getItemViewType(int position) {
if (mModule != null && mModule.assignments != null && mModule.assignments.size() == 0)
return 1;
else
return 0;
}
private Assignment getAssignment(int index) {
if (mModule != null && mModule.assignments != null)
return mModule.assignments.get(index);
else
return null;
}
}

View File

@@ -1,19 +1,22 @@
package de.sebse.fuplanner.fragments.moddetails;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import de.sebse.fuplanner.MainActivity;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.KVV;
import de.sebse.fuplanner.services.KVV.ui.Download;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger;
/**
@@ -21,13 +24,15 @@ import de.sebse.fuplanner.tools.logging.Logger;
* Use the {@link ModDetailAssignmentFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class ModDetailAssignmentFragment extends Fragment {
public class ModDetailAssignmentFragment extends Fragment implements Download.OnDownloadRequestInterface {
private static final String ARG_POSITION = "itemPosition";
private String mItemPos;
private final Logger log = new Logger(this);
private ModDetailAssignmentAdapter adapter;
private SwipeRefreshLayout swipeLayout;
private Download download;
@Nullable private MainActivityListener mListener;
public ModDetailAssignmentFragment() {
@@ -61,10 +66,10 @@ public class ModDetailAssignmentFragment extends Fragment {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_expandable_list_view, container, false);
View view = inflater.inflate(R.layout.fragment_recycler_view, container, false);
// Set the adapter
ExpandableListView expandableListView = view.findViewById(R.id.list);
adapter = new ModDetailAssignmentAdapter();
RecyclerView expandableListView = view.findViewById(R.id.list);
adapter = new ModDetailAssignmentAdapter(this);
expandableListView.setAdapter(adapter);
// Getting SwipeContainerLayout
@@ -77,22 +82,51 @@ public class ModDetailAssignmentFragment extends Fragment {
}
private void refresh(boolean forceRefresh) {
if (getActivity() != null) {
KVV kvv = ((MainActivity) getActivity()).getKVV();
kvv.getModule(mItemPos, (Modules.Module module) -> {
adapter.setModule(module);
kvv.getModuleAssignments(module, success1 -> {
adapter.setModule();
swipeLayout.setRefreshing(false);
}, error -> {
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
}, error -> {
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
if (mListener == null)
return;
mListener.getKVV().modules().assignments().recv(mItemPos, success -> {
adapter.setModule(success);
swipeLayout.setRefreshing(false);
}, error -> {
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
}
@Override
public void request(String title, String url) {
log.d(title, url, mListener);
if (mListener == null)
return;
mListener.getKVV().modules().list().find(mItemPos, (Modules.Module module) -> {
String folderName = "FU-"+module.title.replaceAll("[:*<>|/\"\\\\]", "-");
folderName += "/Assignment";
getDownload().openDownloadDialog(title, url, folderName);
}, log::e);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof MainActivityListener) {
this.mListener = ((MainActivityListener) context);
this.mListener.addRequestPermissionsResultListener(getDownload().getRequestPermissionsResultListener(), "ModDetailAssignmentFragment");
} else
throw new RuntimeException(context.toString() + " must implement MainActivityListener");
}
@Override
public void onDetach() {
super.onDetach();
if (this.mListener != null) {
this.mListener.removeRequestPermissionsResultListener("ModDetailAssignmentFragment");
this.mListener = null;
}
}
Download getDownload() {
if (download == null)
download = new Download(this::getContext, () -> (MainActivity) getActivity());
return download;
}
}

View File

@@ -1,31 +1,43 @@
package de.sebse.fuplanner.fragments.moddetails;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.util.Pair;
import android.content.res.Resources;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.types.Event;
import de.sebse.fuplanner.services.KVV.types.EventList;
import de.sebse.fuplanner.services.KVV.types.GroupedEvents;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.DateUtils;
import de.sebse.fuplanner.tools.Triplet;
import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.ui.CustomViewHolder;
import de.sebse.fuplanner.tools.ui.ItemViewHolder;
import de.sebse.fuplanner.tools.ui.ListViewHolder;
class ModDetailEventAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
class ModDetailEventAdapter extends RecyclerView.Adapter<CustomViewHolder> {
private static final String VALUE_LECTURE = "Class section - Lecture";
private static final String VALUE_TUTORIAL = "Class section - Small Group";
private static final String VALUE_EXAM = "Exam";
private static final String VALUE_DEADLINE = "Deadline";
private static final String VALUE_OTHER = "Other";
private static final String[] VALUES_GROUPED = {VALUE_LECTURE, VALUE_TUTORIAL};
private static final String[] VALUES_UNGROUPED = {VALUE_EXAM, VALUE_DEADLINE};
private static final int SECTION_UPCOMING = 0;
private static final int SECTION_PAST = 1;
private static final int TYPE_NONE = 0;
private static final int TYPE_GROUPED = 1;
private static final int TYPE_UNGROUPED = 2;
private Modules.Module mValue;
private final ArrayList<Pair<Integer, Integer>> mPositionalData;
private final ArrayList<Triplet<Integer, String, Object>> mPositionalData;
private final Logger log = new Logger(this);
ModDetailEventAdapter() {
mValue = null;
@@ -39,36 +51,55 @@ class ModDetailEventAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
}
public void setModule() {
mPositionalData.clear();
addPositionalListData(getUpcomingEventsCount(), SECTION_UPCOMING);
mPositionalData.add(new Pair<>(TYPE_HEADER, SECTION_PAST));
addPositionalListData(getPastEventsCount(), SECTION_PAST);
this.notifyDataSetChanged();
}
private void addPositionalListData(int count, int category) {
for (int i = 0; i < count; i++) {
mPositionalData.add(new Pair<>(TYPE_ITEM, category+1024*i));
LinkedHashMap<String, GroupedEvents> listsGrouped = new LinkedHashMap<>();
LinkedHashMap<String, EventList> listsUngrouped = new LinkedHashMap<>();
for (String value : VALUES_GROUPED) listsGrouped.put(value, new GroupedEvents());
for (String value : VALUES_UNGROUPED) listsUngrouped.put(value, new EventList());
listsUngrouped.put(VALUE_OTHER, new EventList());
for (int i = 0; i < getEventsCount(); i++) {
assert mValue.events != null;
Event event = mValue.events.get(i);
String type = event.getType();
GroupedEvents groupedEvents = listsGrouped.get(type);
if (groupedEvents != null) {
groupedEvents.add(event);
} else {
EventList ungroupedEvents = listsUngrouped.get(type);
if (ungroupedEvents == null) {
ungroupedEvents = listsUngrouped.get(VALUE_OTHER);
}
assert ungroupedEvents != null;
ungroupedEvents.add(event);
}
}
mPositionalData.clear();
for (Map.Entry<String, GroupedEvents> value: listsGrouped.entrySet()) {
if (value.getValue().getGroups().size() > 0)
mPositionalData.add(new Triplet<>(TYPE_GROUPED, value.getKey(), value.getValue()));
}
for (Map.Entry<String, EventList> value: listsUngrouped.entrySet()) {
if (value.getValue().size() > 0)
mPositionalData.add(new Triplet<>(TYPE_UNGROUPED, value.getKey(), value.getValue()));
}
if (mPositionalData.size() == 0)
mPositionalData.add(new Triplet<>(TYPE_NONE, null, null));
this.notifyDataSetChanged();
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
public CustomViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view;
switch (viewType) {
case TYPE_HEADER:
case TYPE_NONE:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_caption, parent, false);
return new HeaderViewHolder(view);
case TYPE_ITEM:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_items, parent, false);
return new ItemViewHolder(view);
.inflate(R.layout.list_all_no_items, parent, false);
return new CustomViewHolder(view);
default:
//noinspection ConstantConditions
return null;
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_cardview_list, parent, false);
return new ListViewHolder(view);
}
}
@@ -81,51 +112,46 @@ class ModDetailEventAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
}
@Override
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) {
public void onBindViewHolder(@NonNull final CustomViewHolder holder, int position) {
if (mValue == null || position > mPositionalData.size())
return;
Pair<Integer, Integer> data = mPositionalData.get(position);
switch (data.first) {
case TYPE_HEADER:
HeaderViewHolder h = (HeaderViewHolder) holder;
switch (data.second) {
case SECTION_PAST:
h.mCaption.setText(R.string.past_events);
break;
case SECTION_UPCOMING:
h.mCaption.setText(R.string.upcoming_events);
break;
}
break;
case TYPE_ITEM:
int section = data.second % 1024;
int index = data.second / 1024;
ItemViewHolder i = (ItemViewHolder) holder;
Event event = null;
switch (section) {
case SECTION_UPCOMING:
event = mValue.events.getUpcoming(index);
break;
case SECTION_PAST:
event = mValue.events.getPast(index);
break;
}
//noinspection ConstantConditions
i.mTitle.setText(event.getTitle());
i.mSubLeft.setText(event.getType());
String start, end;
if (DateUtils.dateEquals(event.getStartDate(), System.currentTimeMillis()))
start = DateUtils.getModifiedTime(i.mView.getContext(), event.getStartDate());
else
start = DateUtils.getModifiedDateTime(i.mView.getContext(), event.getStartDate());
if (DateUtils.dateEquals(event.getStartDate(), event.getEndDate()))
end = DateUtils.getModifiedTime(i.mView.getContext(), event.getEndDate());
else
end = DateUtils.getModifiedDateTime(i.mView.getContext(), event.getEndDate());
i.mSubRight.setText(i.mView.getResources().getString(R.string.date_scale,
start, end
));
break;
Triplet<Integer, String, Object> data = mPositionalData.get(position);
if (data.first != TYPE_NONE) {
ListViewHolder h;
ModDetailEventAdapterInner adapter;
h = (ListViewHolder) holder;
Resources resources = h.mView.getResources();
adapter = new ModDetailEventAdapterInner();
String title;
switch (data.second) {
case VALUE_LECTURE:
title = resources.getString(R.string.lecture);
break;
case VALUE_TUTORIAL:
title = resources.getString(R.string.tutorial);
break;
case VALUE_EXAM:
title = resources.getString(R.string.exam);
break;
case VALUE_DEADLINE:
title = resources.getString(R.string.deadline);
break;
case VALUE_OTHER:
title = resources.getString(R.string.others);
break;
default:
title = data.second;
}
h.mString.setText(title);
switch (data.first) {
case TYPE_GROUPED:
adapter.setData((GroupedEvents) data.third, h.mView.getContext());
break;
case TYPE_UNGROUPED:
adapter.setData((EventList) data.third, h.mView.getContext());
break;
}
h.mList.setAdapter(adapter);
}
}
@@ -134,38 +160,9 @@ class ModDetailEventAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
return mPositionalData.size();
}
private int getUpcomingEventsCount() {
private int getEventsCount() {
if (mValue.events != null)
return mValue.events.sizeUpcoming();
return mValue.events.size();
return 0;
}
private int getPastEventsCount() {
if (mValue.events != null)
return mValue.events.sizePast();
return 0;
}
class HeaderViewHolder extends CustomViewHolder {
final TextView mCaption;
HeaderViewHolder(View view) {
super(view);
mCaption = view.findViewById(R.id.caption);
}
@Override
public String toString() {
return super.toString() + " '" + mCaption.getText() + "'";
}
}
}

View File

@@ -0,0 +1,104 @@
package de.sebse.fuplanner.fragments.moddetails;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.types.Event;
import de.sebse.fuplanner.services.KVV.types.EventList;
import de.sebse.fuplanner.services.KVV.types.GroupedEvents;
import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.ui.ItemViewHolder;
class ModDetailEventAdapterInner extends RecyclerView.Adapter<ItemViewHolder> {
private final ArrayList<Entry> mPositionalData;
ModDetailEventAdapterInner() {
mPositionalData = new ArrayList<>();
}
public void setData(GroupedEvents events, final Context context) {
mPositionalData.clear();
for (GroupedEvents.Group group : events.getGroups()) {
long firstDateTime = group.getFirstDate()+group.getStartTime();
long lastDateTime = group.getLastDate()+group.getStartTime()+group.getDuration();
String start, end, weekday, startTime, endTime;
StringBuilder excepts = null;
start = UtilsDate.getModifiedDate(firstDateTime);
end = UtilsDate.getModifiedDate(lastDateTime);
weekday = UtilsDate.getModifiedDate(context, firstDateTime, "E");
startTime = UtilsDate.getModifiedTime(context, firstDateTime);
endTime = UtilsDate.getModifiedTime(context, lastDateTime);
for (long skippedDate : group.getSkippedDates()) {
if (excepts == null) {
excepts = new StringBuilder(UtilsDate.getModifiedDate(skippedDate));
} else {
excepts.append(", ").append(UtilsDate.getModifiedDate(skippedDate));
}
}
mPositionalData.add(new Entry(
context.getString(R.string.event_scale, weekday, startTime, endTime),
context.getString(R.string.date_scale, start, end),
excepts != null ? context.getString(R.string.except_list, excepts.toString()) : ""));
}
this.notifyDataSetChanged();
}
public void setData(EventList events, final Context context) {
mPositionalData.clear();
for (Event event : events) {
String date;
String start, end;
if (UtilsDate.dateEquals(event.getStartDate(), System.currentTimeMillis()))
start = UtilsDate.getModifiedTime(context, event.getStartDate());
else
start = UtilsDate.getModifiedDateTime(context, event.getStartDate());
if (UtilsDate.dateEquals(event.getStartDate(), event.getEndDate()))
end = UtilsDate.getModifiedTime(context, event.getEndDate());
else
end = UtilsDate.getModifiedDateTime(context, event.getEndDate());
date = context.getString(R.string.date_scale, start, end);
mPositionalData.add(new Entry(event.getTitle(), date, ""));
}
this.notifyDataSetChanged();
}
@NonNull
@Override
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view;
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_items, parent, false);
return new ItemViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull final ItemViewHolder holder, int position) {
holder.mTitle.setText(mPositionalData.get(position).title);
holder.mSubLeft.setText(mPositionalData.get(position).subtitleLeft);
holder.mSubRight.setText(mPositionalData.get(position).subtitleRight);
}
@Override
public int getItemCount() {
return mPositionalData.size();
}
private class Entry {
private String title;
private String subtitleLeft;
private String subtitleRight;
private Entry(String title, String subtitleLeft, String subtitleRight) {
this.title = title;
this.subtitleLeft = subtitleLeft;
this.subtitleRight = subtitleRight;
}
}
}

View File

@@ -3,19 +3,18 @@ package de.sebse.fuplanner.fragments.moddetails;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import de.sebse.fuplanner.MainActivity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.KVV;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger;
/**
@@ -30,6 +29,7 @@ public class ModDetailEventFragment extends Fragment {
private final Logger log = new Logger(this);
private ModDetailEventAdapter adapter;
private SwipeRefreshLayout swipeLayout;
@Nullable private MainActivityListener mListener;
public ModDetailEventFragment() {
@@ -81,22 +81,31 @@ public class ModDetailEventFragment extends Fragment {
}
private void refresh(boolean forceRefresh) {
if (getActivity() != null) {
KVV kvv = ((MainActivity) getActivity()).getKVV();
kvv.getModule(mItemPos, (Modules.Module module) -> {
adapter.setModule(module);
kvv.getModuleEvents(module, success1 -> {
adapter.setModule();
swipeLayout.setRefreshing(false);
}, error -> {
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
}, error -> {
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
}
if (mListener == null)
return;
mListener.getKVV().modules().events().recv(mItemPos, success -> {
adapter.setModule(success);
swipeLayout.setRefreshing(false);
}, error -> {
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof MainActivityListener) {
this.mListener = ((MainActivityListener) context);
} else
throw new RuntimeException(context.toString() + " must implement MainActivityListener");
}
@Override
public void onDetach() {
super.onDetach();
if (this.mListener != null) {
this.mListener = null;
}
}
}

View File

@@ -2,22 +2,22 @@ package de.sebse.fuplanner.fragments.moddetails;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.viewpager.widget.ViewPager;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.MainAcitivityListener;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger;
/**
* A simple {@link Fragment} subclass.
* Activities that contain this fragment must implement the
* {@link MainAcitivityListener} interface
* {@link MainActivityListener} interface
* to handle interaction events.
* Use the {@link ModDetailFragment#newInstance} factory method to
* create an instance of this fragment.
@@ -28,9 +28,10 @@ public class ModDetailFragment extends Fragment implements ModDetailListener {
// Parameters
private String mItemPos;
private MainAcitivityListener mListener;
private MainActivityListener mListener;
private final Logger log = new Logger(this);
private ViewPager mViewPager;
private String mPageRestoreRequest = null;
public ModDetailFragment() {
// Required empty public constructor
@@ -46,7 +47,11 @@ public class ModDetailFragment extends Fragment implements ModDetailListener {
public static Fragment newInstance(String itemPosition) {
ModDetailFragment fragment = new ModDetailFragment();
Bundle args = new Bundle();
args.putString(ARG_POSITION, itemPosition);
if (!itemPosition.contains("."))
args.putString(ARG_POSITION, itemPosition+"."+0);
else
args.putString(ARG_POSITION, itemPosition);
fragment.setArguments(args);
return fragment;
}
@@ -55,11 +60,19 @@ public class ModDetailFragment extends Fragment implements ModDetailListener {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mItemPos = getArguments().getString(ARG_POSITION);
String itemPosition = getArguments().getString(ARG_POSITION);
if (!itemPosition.contains(".")) {
mItemPos = itemPosition;
mPageRestoreRequest = null;
} else {
String[] split = itemPosition.split("\\.", 2);
mItemPos = split[0];
mPageRestoreRequest = split[1];
}
}
if (mListener != null) {
mListener.onTitleTextChange(R.string.courses);
mListener.getKVV().getModuleList(success -> {
mListener.getKVV().modules().list().recv(success -> {
Modules.Module module = success.get(mItemPos);
if (mListener != null && module != null)
mListener.onTitleTextChange(module.title);
@@ -76,14 +89,16 @@ public class ModDetailFragment extends Fragment implements ModDetailListener {
mViewPager = v.findViewById(R.id.vpPager);
ModDetailAdapter adapterViewPager = new ModDetailAdapter(getChildFragmentManager(), mItemPos, getContext());
mViewPager.setAdapter(adapterViewPager);
if (mPageRestoreRequest != null)
mViewPager.setCurrentItem(Integer.parseInt(mPageRestoreRequest));
return v;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof MainAcitivityListener) {
mListener = (MainAcitivityListener) context;
if (context instanceof MainActivityListener) {
mListener = (MainActivityListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement MainActivityListener");
@@ -100,4 +115,8 @@ public class ModDetailFragment extends Fragment implements ModDetailListener {
public void gotoFragmentPart(int part, int index) {
mViewPager.setCurrentItem(ModulePart.getPageByPart(part), true);
}
public String getData() {
return mItemPos+"."+mViewPager.getCurrentItem();
}
}

View File

@@ -1,8 +1,6 @@
package de.sebse.fuplanner.fragments.moddetails;
import android.annotation.SuppressLint;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
@@ -11,10 +9,12 @@ import android.widget.TextView;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.types.Gradebook;
import de.sebse.fuplanner.services.KVV.types.Grade;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.ui.CustomViewHolder;
import de.sebse.fuplanner.tools.ui.StringViewHolder;
class ModDetailGradebookAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_TOTAL = 0;
@@ -58,11 +58,11 @@ class ModDetailGradebookAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
case TYPE_TOTAL:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_caption, parent, false);
return new HeaderViewHolder(view);
return new StringViewHolder(view);
case TYPE_GRADE:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_modetails_gradebook, parent, false);
return new GradebookViewHolder(view);
.inflate(R.layout.list_moddetails_gradebook, parent, false);
return new GradeViewHolder(view);
default:
//noinspection ConstantConditions
return null;
@@ -85,13 +85,13 @@ class ModDetailGradebookAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
Pair<Integer, Integer> data = mPositionalData.get(position);
switch (data.first) {
case TYPE_TOTAL:
HeaderViewHolder h = (HeaderViewHolder) holder;
h.mCaption.setText(h.mView.getResources().getString(R.string.current_percentage, mValue.getGradebookPercent()*100));
StringViewHolder h = (StringViewHolder) holder;
h.mString.setText(h.mView.getResources().getString(R.string.current_percentage, mValue.getGradebookPercent()*100));
break;
case TYPE_GRADE:
int index = data.second / 1024;
GradebookViewHolder i = (GradebookViewHolder) holder;
Gradebook gradebook = mValue.gradebook.get(index);
GradeViewHolder i = (GradeViewHolder) holder;
Grade gradebook = mValue.gradebook.get(index);
i.mTitle.setText(gradebook.getItemName());
i.mGrade.setText(String.valueOf(gradebook.getPoints()));
@@ -118,27 +118,13 @@ class ModDetailGradebookAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
class HeaderViewHolder extends CustomViewHolder {
final TextView mCaption;
HeaderViewHolder(View view) {
super(view);
mCaption = view.findViewById(R.id.caption);
}
@Override
public String toString() {
return super.toString() + " '" + mCaption.getText() + "'";
}
}
private class GradebookViewHolder extends RecyclerView.ViewHolder {
private class GradeViewHolder extends RecyclerView.ViewHolder {
private final TextView mGrade;
private final TextView mGradeMax;
private final TextView mTitle;
GradebookViewHolder(View view) {
GradeViewHolder(View view) {
super(view);
mTitle = view.findViewById(R.id.title);
mGrade = view.findViewById(R.id.grade);

View File

@@ -3,19 +3,18 @@ package de.sebse.fuplanner.fragments.moddetails;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import de.sebse.fuplanner.MainActivity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.KVV;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger;
/**
@@ -30,6 +29,7 @@ public class ModDetailGradebookFragment extends Fragment {
private final Logger log = new Logger(this);
private ModDetailGradebookAdapter adapter;
private SwipeRefreshLayout swipeLayout;
@Nullable private MainActivityListener mListener;
public ModDetailGradebookFragment() {
@@ -81,22 +81,31 @@ public class ModDetailGradebookFragment extends Fragment {
}
private void refresh(boolean forceRefresh) {
if (getActivity() != null) {
KVV kvv = ((MainActivity) getActivity()).getKVV();
kvv.getModule(mItemPos, (Modules.Module module) -> {
adapter.setModule(module);
kvv.getModuleGradebook(module, success1 -> {
adapter.setModule();
swipeLayout.setRefreshing(false);
}, error -> {
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
}, error -> {
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
}
if (mListener == null)
return;
mListener.getKVV().modules().gradebook().recv(mItemPos, success -> {
adapter.setModule(success);
swipeLayout.setRefreshing(false);
}, error -> {
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof MainActivityListener) {
this.mListener = ((MainActivityListener) context);
} else
throw new RuntimeException(context.toString() + " must implement MainActivityListener");
}
@Override
public void onDetach() {
super.onDetach();
if (this.mListener != null) {
this.mListener = null;
}
}
}

View File

@@ -1,27 +1,31 @@
package de.sebse.fuplanner.fragments.moddetails;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.content.Intent;
import android.net.Uri;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.ms.square.android.expandabletextview.ExpandableTextView;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.types.Announcement;
import de.sebse.fuplanner.services.KVV.types.Assignment;
import de.sebse.fuplanner.services.KVV.types.Event;
import de.sebse.fuplanner.services.KVV.types.Lecturer;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.DateUtils;
import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.ui.CustomViewHolder;
import de.sebse.fuplanner.tools.ui.ItemViewHolder;
import de.sebse.fuplanner.tools.ui.MailViewHolder;
import de.sebse.fuplanner.tools.ui.StringViewHolder;
class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int MAX_ITEMS_PER_PREVIEW = 2;
@@ -30,6 +34,7 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
private static final int TYPE_DESCRIPTION = 1;
private static final int TYPE_ITEM = 2;
private static final int TYPE_SHOW_MORE = 3;
private static final int TYPE_MAIL = 4;
@Nullable private final ModDetailListener mListener;
@@ -52,6 +57,10 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
mPositionalData.clear();
mPositionalData.add(new Pair<>(TYPE_HEADER, ModulePart.DESCRIPTION));
mPositionalData.add(new Pair<>(TYPE_DESCRIPTION, null));
mPositionalData.add(new Pair<>(TYPE_HEADER, ModulePart.LECTURERS));
for (int i = 0; i < mValue.lecturer.size(); i++) {
mPositionalData.add(new Pair<>(TYPE_MAIL, ModulePart.LECTURERS+1024*i));
}
mPositionalData.add(new Pair<>(TYPE_HEADER, ModulePart.ANNOUNCEMENT));
addPositionalListData(getAnnounceCount(), ModulePart.ANNOUNCEMENT);
mPositionalData.add(new Pair<>(TYPE_HEADER, ModulePart.ASSIGNMENT));
@@ -77,7 +86,7 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
case TYPE_HEADER:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_caption, parent, false);
return new HeaderViewHolder(view);
return new StringViewHolder(view);
case TYPE_DESCRIPTION:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_moddetails_description, parent, false);
@@ -90,6 +99,10 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_show_more, parent, false);
return new CustomViewHolder(view);
case TYPE_MAIL:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_mails, parent, false);
return new MailViewHolder(view);
default:
//noinspection ConstantConditions
return null;
@@ -111,19 +124,22 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
Pair<Integer, Object> data = mPositionalData.get(position);
switch (data.first) {
case TYPE_HEADER:
HeaderViewHolder h = (HeaderViewHolder) holder;
StringViewHolder h = (StringViewHolder) holder;
switch ((Integer) data.second) {
case ModulePart.DESCRIPTION:
h.mCaption.setText(R.string.description);
h.mString.setText(R.string.description);
break;
case ModulePart.LECTURERS:
h.mString.setText(h.mView.getResources().getString(R.string.lecturers));
break;
case ModulePart.ANNOUNCEMENT:
h.mCaption.setText(h.mView.getResources().getString(R.string.announcements_count, getAnnounceCount()));
h.mString.setText(h.mView.getResources().getString(R.string.announcements_count, getAnnounceCount()));
break;
case ModulePart.ASSIGNMENT:
h.mCaption.setText(h.mView.getResources().getString(R.string.assignments_count, getAssignmentCount()));
h.mString.setText(h.mView.getResources().getString(R.string.assignments_count, getAssignmentCount()));
break;
case ModulePart.EVENT:
h.mCaption.setText(h.mView.getResources().getString(R.string.upcoming_events_count, getEventsCount()));
h.mString.setText(h.mView.getResources().getString(R.string.upcoming_events_count, getEventsCount()));
break;
}
break;
@@ -141,7 +157,7 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
Announcement announce = mValue.announcements.get(index);
i.mTitle.setText(announce.getTitle());
i.mSubLeft.setText(announce.getCreatedBy());
i.mSubRight.setText(DateUtils.getModifiedDateTime(i.mView.getContext(), announce.getCreatedOn()));
i.mSubRight.setText(UtilsDate.getModifiedDateTime(i.mView.getContext(), announce.getCreatedOn()));
i.mView.setOnClickListener(view -> {
if (mListener != null) mListener.gotoFragmentPart(section, index);
});
@@ -154,7 +170,7 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
i.mSubLeft.setText(i.mView.getResources().getText(R.string.open));
else
i.mSubLeft.setText(i.mView.getResources().getText(R.string.closed));
i.mSubRight.setText(DateUtils.getModifiedDateTime(i.mView.getContext(), assignment.getDueDate()));
i.mSubRight.setText(UtilsDate.getModifiedDateTime(i.mView.getContext(), assignment.getDueDate()));
i.mView.setOnClickListener(view -> {
if (mListener != null) mListener.gotoFragmentPart(section, index);
});
@@ -165,14 +181,14 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
i.mTitle.setText(event.getTitle());
i.mSubLeft.setText(event.getType());
String start, end;
if (DateUtils.dateEquals(event.getStartDate(), System.currentTimeMillis()))
start = DateUtils.getModifiedTime(i.mView.getContext(), event.getStartDate());
if (UtilsDate.dateEquals(event.getStartDate(), System.currentTimeMillis()))
start = UtilsDate.getModifiedTime(i.mView.getContext(), event.getStartDate());
else
start = DateUtils.getModifiedDateTime(i.mView.getContext(), event.getStartDate());
if (DateUtils.dateEquals(event.getStartDate(), event.getEndDate()))
end = DateUtils.getModifiedTime(i.mView.getContext(), event.getEndDate());
start = UtilsDate.getModifiedDateTime(i.mView.getContext(), event.getStartDate());
if (UtilsDate.dateEquals(event.getStartDate(), event.getEndDate()))
end = UtilsDate.getModifiedTime(i.mView.getContext(), event.getEndDate());
else
end = DateUtils.getModifiedDateTime(i.mView.getContext(), event.getEndDate());
end = UtilsDate.getModifiedDateTime(i.mView.getContext(), event.getEndDate());
i.mSubRight.setText(i.mView.getResources().getString(R.string.date_scale,
start, end
));
@@ -182,6 +198,26 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
break;
}
break;
case TYPE_MAIL:
section = ((Integer) data.second) % 1024;
index = ((Integer) data.second) / 1024;
MailViewHolder m = (MailViewHolder) holder;
switch (section) {
case ModulePart.LECTURERS:
Lecturer lecturer = mValue.lecturer.get(index);
m.mTitle.setText(lecturer.getName());
m.mSubLeft.setText(lecturer.getMail());
m.mIcon.setOnClickListener(view -> {
String defaultText = m.mView.getResources().getString(R.string.mail_default_text,
lecturer.getName());
Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
emailIntent.setData(Uri.parse("mailto:"+lecturer.getMail()));
emailIntent.putExtra(Intent.EXTRA_SUBJECT, mValue.title);
emailIntent.putExtra(Intent.EXTRA_TEXT, defaultText);
m.mView.getContext().startActivity(Intent.createChooser(emailIntent, mValue.title));
});
break;
}
case TYPE_SHOW_MORE:
CustomViewHolder c = (CustomViewHolder) holder;
c.mView.setOnClickListener(view -> {
@@ -221,19 +257,7 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
class HeaderViewHolder extends CustomViewHolder {
final TextView mCaption;
HeaderViewHolder(View view) {
super(view);
mCaption = view.findViewById(R.id.caption);
}
@Override
public String toString() {
return super.toString() + " '" + mCaption.getText() + "'";
}
}
class DescriptionViewHolder extends CustomViewHolder {
final ExpandableTextView mText;
@@ -243,6 +267,7 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
mText = view.findViewById(R.id.expand_text_view);//.findViewById(R.id.description);
}
@NonNull
@Override
public String toString() {
return super.toString() + " '" + mText.getText() + "'";

View File

@@ -3,20 +3,18 @@ package de.sebse.fuplanner.fragments.moddetails;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import de.sebse.fuplanner.MainActivity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.KVV;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger;
/**
@@ -31,7 +29,8 @@ public class ModDetailOverviewFragment extends Fragment {
private final Logger log = new Logger(this);
private ModDetailOverviewAdapter adapter;
private SwipeRefreshLayout swipeLayout;
@Nullable private ModDetailListener mListener;
@Nullable private ModDetailListener mDetailListener;
@Nullable private MainActivityListener mListener;
public ModDetailOverviewFragment() {
@@ -70,7 +69,7 @@ public class ModDetailOverviewFragment extends Fragment {
Context context = view.getContext();
RecyclerView recyclerView = view.findViewById(R.id.list);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
adapter = new ModDetailOverviewAdapter(mListener);
adapter = new ModDetailOverviewAdapter(mDetailListener);
recyclerView.setAdapter(adapter);
// Getting SwipeContainerLayout
@@ -82,19 +81,15 @@ public class ModDetailOverviewFragment extends Fragment {
return view;
}
private void refresh(boolean forceRefresh) {
if (getActivity() != null) {
KVV kvv = ((MainActivity) getActivity()).getKVV();
kvv.getModule(mItemPos, (Modules.Module module) -> {
adapter.setModule(module);
kvv.getModuleDetails(module, pair -> {
adapter.setModule();
if (pair.second)
swipeLayout.setRefreshing(false);
}, error -> {
if (mListener != null) {
mListener.getKVV().modules().details().recv(mItemPos, pair -> {
adapter.setModule(pair.first);
if (pair.second)
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
}, error -> {
swipeLayout.setRefreshing(false);
log.e(error);
@@ -104,15 +99,27 @@ public class ModDetailOverviewFragment extends Fragment {
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof MainActivityListener) {
this.mListener = ((MainActivityListener) context);
} else
throw new RuntimeException(context.toString() + " must implement MainActivityListener");
super.onAttach(context);
Fragment parentFragment = getParentFragment();
if (parentFragment != null) {
if (parentFragment instanceof ModDetailListener) {
mListener = (ModDetailListener) parentFragment;
mDetailListener = (ModDetailListener) parentFragment;
} else {
throw new RuntimeException(context.toString()
+ " must implement ModDetailListener");
throw new RuntimeException(context.toString() + " must implement ModDetailListener");
}
} else log.w("No parent fragment!");
}
@Override
public void onDetach() {
super.onDetach();
this.mListener = null;
}
}

View File

@@ -0,0 +1,34 @@
package de.sebse.fuplanner.fragments.moddetails;
import java.util.ArrayList;
import java.util.List;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.services.KVV.types.Resource;
import de.sebse.fuplanner.tools.ui.treeview.TreeNode;
import de.sebse.fuplanner.tools.ui.treeview.TreeViewAdapter;
import de.sebse.fuplanner.tools.ui.treeview.TreeViewBinder;
class ModDetailResourceAdapter extends TreeViewAdapter {
private Modules.Module mValue;
public ModDetailResourceAdapter(List<? extends TreeViewBinder> viewBinders) {
super(viewBinders);
mValue = null;
}
public void setModule(Modules.Module module) {
mValue = module;
this.setModule();
}
public void setModule() {
if (mValue == null || mValue.resources == null) {
return;
}
List<TreeNode> nodes = new ArrayList<>();
for (Resource res: mValue.resources) {
nodes.add(res.getTreeNode());
}
refresh(nodes);
}
}

View File

@@ -0,0 +1,163 @@
package de.sebse.fuplanner.fragments.moddetails;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import java.util.Arrays;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import de.sebse.fuplanner.MainActivity;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.ui.Download;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.services.KVV.types.Resource;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.ui.treeview.DirectoryNodeBinder;
import de.sebse.fuplanner.tools.ui.treeview.FileNodeBinder;
import de.sebse.fuplanner.tools.ui.treeview.TreeNode;
import de.sebse.fuplanner.tools.ui.treeview.TreeViewAdapter;
/**
* A simple {@link Fragment} subclass.
* Use the {@link ModDetailResourceFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class ModDetailResourceFragment extends Fragment {
private static final String ARG_POSITION = "itemPosition";
private String mItemPos;
private final Logger log = new Logger(this);
private ModDetailResourceAdapter adapter;
private SwipeRefreshLayout swipeLayout;
private Download download;
@Nullable private MainActivityListener mListener;
public ModDetailResourceFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param itemPosition Item position in module list.
* @return A new instance of fragment ModDetailAnnounceFragment.
*/
public static ModDetailResourceFragment newInstance(String itemPosition) {
ModDetailResourceFragment fragment = new ModDetailResourceFragment();
Bundle args = new Bundle();
args.putString(ARG_POSITION, itemPosition);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mItemPos = getArguments().getString(ARG_POSITION);
}
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_recycler_view, container, false);
// Set the adapter
Context context = view.getContext();
RecyclerView recyclerView = view.findViewById(R.id.list);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
adapter = new ModDetailResourceAdapter(Arrays.asList(new FileNodeBinder(), new DirectoryNodeBinder()));
adapter.setOnTreeNodeListener(new TreeViewAdapter.OnTreeNodeListener() {
@Override
public boolean onClick(TreeNode node, RecyclerView.ViewHolder holder) {
if (!node.isLeaf()) {
// Update and toggle the node.
onToggle(!node.isExpand(), holder);
} else if (node.getContent() instanceof Resource.File && ModDetailResourceFragment.this.mListener != null) { // if leaf is file
ModDetailResourceFragment.this.mListener.getKVV().modules().resources().recv(mItemPos, (Modules.Module module) -> {
String folderName = "FU-"+module.title.replaceAll("[:*<>|/\"\\\\]", "-");
Resource.File file = (Resource.File) node.getContent();
getDownload().openDownloadDialog(file, folderName);
}, log::e);
}
return false;
}
@Override
public void onToggle(boolean isExpand, RecyclerView.ViewHolder holder) {
DirectoryNodeBinder.ViewHolder dirViewHolder = (DirectoryNodeBinder.ViewHolder) holder;
final ImageView ivArrow = dirViewHolder.getIvArrow();
int rotateDegree = isExpand ? 90 : -90;
ivArrow.animate().rotationBy(rotateDegree).start();
}
});
recyclerView.setAdapter(adapter);
// Getting SwipeContainerLayout
swipeLayout = view.findViewById(R.id.swipe_container);
// Adding Listener
swipeLayout.setOnRefreshListener(() -> refresh(true));
refresh(false);
return view;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof MainActivityListener) {
this.mListener = ((MainActivityListener) context);
this.mListener.addRequestPermissionsResultListener(getDownload().getRequestPermissionsResultListener(), "ModDetailResourceFragment");
} else
throw new RuntimeException(context.toString() + " must implement MainActivityListener");
}
@Override
public void onDetach() {
super.onDetach();
if (this.mListener != null) {
this.mListener.removeRequestPermissionsResultListener("ModDetailResourceFragment");
this.mListener = null;
}
}
private void refresh(boolean forceRefresh) {
if (mListener == null)
return;
mListener.getKVV().modules().assignments().recv(mItemPos, success -> {
adapter.setModule(success);
swipeLayout.setRefreshing(false);
}, error -> {
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
}
Download getDownload() {
if (download == null)
download = new Download(this::getContext, () -> (MainActivity) getActivity());
return download;
}
}

View File

@@ -7,7 +7,9 @@ class ModulePart {
static final int ASSIGNMENT = 3;
static final int EVENT = 4;
static final int GRADEBOOK = 5;
private static final int[] pages = new int[]{OVERVIEW, ANNOUNCEMENT, ASSIGNMENT, EVENT, GRADEBOOK};
static final int RESOURCES = 6;
static final int LECTURERS = 7;
private static final int[] pages = new int[]{OVERVIEW, ANNOUNCEMENT, ASSIGNMENT, GRADEBOOK, RESOURCES, EVENT};
static int getPageCount() {
return pages.length;

View File

@@ -13,7 +13,7 @@ import de.sebse.fuplanner.services.Canteen.types.Canteen;
import de.sebse.fuplanner.services.Canteen.types.Canteens;
import de.sebse.fuplanner.services.Canteen.types.Day;
import de.sebse.fuplanner.tools.AsyncQueue;
import de.sebse.fuplanner.tools.MainAcitivityListener;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.network.HTTPService;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkError;
@@ -23,15 +23,15 @@ public class CanteenBrowser extends HTTPService {
private Canteens canteens;
private final AsyncQueue queue = new AsyncQueue();
private final Context context;
private MainAcitivityListener mListener;
private MainActivityListener mListener;
public CanteenBrowser(Context context) {
super(context);
this.context = context;
if (context instanceof MainAcitivityListener)
if (context instanceof MainActivityListener)
mListener = (MainActivity) context;
else
throw new RuntimeException(context.toString() + "must implement MainActivityListener");
throw new RuntimeException(context.toString() + " must implement MainActivityListener");
try {
this.canteens = Canteens.load(context);
} catch (IOException e) {
@@ -65,7 +65,7 @@ public class CanteenBrowser extends HTTPService {
}
private void upgradeCanteens(final NetworkCallback<Canteens> callback, final NetworkErrorCallback errorCallback) {
get("https://openmensa.org/api/v2/canteens", null, response -> {
get("https://openmensa.org/api/v2/canteens?near[lat]=52.449743&near[lng]=13.282245&near[dist]=7", null, response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(201101, 403, "No canteen list retrieved!"));
@@ -192,9 +192,9 @@ public class CanteenBrowser extends HTTPService {
double priceEmply = 0;
double priceOther = 0;
if (prices != null) {
priceStdnt = prices.getDouble("students");
priceEmply = prices.getDouble("employees");
priceOther = prices.getDouble("others");
priceOther = prices.optDouble("others", -1);
priceEmply = prices.optDouble("employees", priceOther);
priceStdnt = prices.optDouble("students", priceEmply);
}
JSONArray noteArray = meal.getJSONArray("notes");
String[] notes = new String[noteArray.length()];
@@ -206,6 +206,7 @@ public class CanteenBrowser extends HTTPService {
}
} catch (JSONException e) {
e.printStackTrace();
log.d(body);
errorCallback.onError(new NetworkError(201302, 403, "Cannot parse meal list!"));
return;
}
@@ -224,17 +225,14 @@ public class CanteenBrowser extends HTTPService {
private<T> NetworkCallback<T> saveOnCallback(NetworkCallback<T> callback, boolean forceRefresh){
return (success -> {
if (forceRefresh)
mListener.refreshFailed(false);
mListener.onRefreshCompleted(false);
callback.onResponse(success);
});
}
private NetworkErrorCallback errorOnCallback(NetworkErrorCallback errorCallback){
return (error -> {
if (error.getHttpStatus() == 401 || error.getHttpStatus() == 403)
mListener.loginTokenInvalid(false);
else
mListener.refreshFailed(true);
mListener.onRefreshCompleted(true);
errorCallback.onError(error);
});
}

View File

@@ -1,6 +1,6 @@
package de.sebse.fuplanner.services.Canteen.types;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import java.io.Serializable;
import java.util.Calendar;
@@ -50,6 +50,17 @@ public class Canteen implements Serializable, Iterable<Day> {
}
}
public void cleanUpDays() {
SortedListDay newList = new SortedListDay();
Calendar cal = Calendar.getInstance();
for (Day day : list) {
if (Canteen.calendarToKey(day.getCalendar()).compareTo(Canteen.calendarToKey(cal)) >= 0) {
newList.add(day);
}
}
list = newList;
}
public int size() {
return this.list.size();
}

View File

@@ -1,7 +1,7 @@
package de.sebse.fuplanner.services.Canteen.types;
import android.content.Context;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -12,7 +12,7 @@ import java.io.Serializable;
import java.util.Iterator;
public class Canteens implements Serializable, Iterable<Canteen> {
public static final int[] availableCanteens = {27, 28, 42};
public static final int[] availableCanteens = {27, 42, 813, 810, 811, 812, 821};
private static final String FILE_NAME = "CanteensSaving";
private SortedListCanteen list = new SortedListCanteen();
@@ -68,7 +68,7 @@ public class Canteens implements Serializable, Iterable<Canteen> {
@Override
public void remove() {
throw new UnsupportedOperationException("You are not alloed to remove an entry!");
throw new UnsupportedOperationException("You are not allowed to remove an entry!");
}
};
}

View File

@@ -1,6 +1,6 @@
package de.sebse.fuplanner.services.Canteen.types;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import java.io.Serializable;
import java.util.Calendar;

View File

@@ -6,14 +6,14 @@ import java.util.Collections;
import java.util.List;
public class Meal implements Serializable {
public static final int LIGHT_NONE = 0;
public static final int LIGHT_GREEN = 1;
public static final int LIGHT_YELLOW = 2;
public static final int LIGHT_RED = 3;
public static final int VEGAN_NONE = 0;
public static final int VEGAN_VEGETERIAN = 1;
private static final int LIGHT_NONE = 0;
private static final int LIGHT_GREEN = 1;
private static final int LIGHT_YELLOW = 2;
private static final int LIGHT_RED = 3;
private static final int VEGAN_NONE = 0;
public static final int VEGAN_VEGETARIAN = 1;
public static final int VEGAN_VEGAN = 2;
public static final int CERT_NONE = 0b0000;
private static final int CERT_NONE = 0b0000;
public static final int CERT_BIO = 0b0001;
public static final int CERT_MSC = 0b0010;
@@ -42,7 +42,7 @@ public class Meal implements Serializable {
for (String note : notes) {
switch (note.toLowerCase()) {
case "vegetarisch":
vegan = VEGAN_VEGETERIAN;
vegan = VEGAN_VEGETARIAN;
break;
case "vegan":
vegan = VEGAN_VEGAN;

View File

@@ -2,8 +2,6 @@ package de.sebse.fuplanner.services.GoogleAuth;
import android.content.Intent;
import android.content.IntentSender;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.widget.Toast;
@@ -17,6 +15,9 @@ import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.api.ResolvableApiException;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import static android.app.Activity.RESULT_OK;
/**
@@ -28,6 +29,7 @@ public class GoogleAuth {
private static final String TAG = "GoogleAuth";
private final FragmentActivity activity;
private static final String FU_PLANNER_PROVIDER = "FUPlanner";
private CredentialsClient mCredentialsClient;
private boolean mIsResolving;
@Nullable
@@ -60,6 +62,7 @@ public class GoogleAuth {
connect();
CredentialRequest request = new CredentialRequest.Builder()
.setPasswordLoginSupported(true)
.setAccountTypes(FU_PLANNER_PROVIDER)
.build();

View File

@@ -1,195 +1,47 @@
package de.sebse.fuplanner.services.KVV;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Pair;
import java.io.IOException;
import java.util.ArrayList;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import de.sebse.fuplanner.services.KVV.types.LoginToken;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.MainAcitivityListener;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
/**
* Created by sebastian on 29.10.17.
*/
public class KVV {
private final Context context;
private LoginToken lastToken;
private boolean isLoginPending = true;
private final ArrayList<LastTokenCallback> updatingList;
private final HashMap<String, Object> addons = new HashMap<>();
private final MainAcitivityListener mListener;
private final KVVListener mListener;
private final Context mContext;
public KVV(Context context) {
mListener = (MainAcitivityListener) context;
this.context = context;
this.updatingList = new ArrayList<>();
public KVV(KVVListener listener, Context context) {
this.mListener = listener;
this.mContext = context;
}
public LoginToken easyLogin() {
KVVLogin login = new KVVLogin(this.context);
lastToken = login.easyLogin();
this.endUpdate();
return lastToken;
@NotNull
public Login account() {
return (Login) addAndGet("account", () -> new Login(mListener, mContext));
}
public void login(@NonNull String username, @NonNull String password, final NetworkCallback<LoginToken> callback, NetworkErrorCallback error) {
KVVLogin login = new KVVLogin(this.context);
login.login(username, password, success -> {
lastToken = success;
this.endUpdate();
try {
login.saveOffline();
} catch (IOException e) {
e.printStackTrace();
}
callback.onResponse(success);
}, error1 -> {
this.endUpdate();
error.onError(error1);
});
@NotNull
public Modules modules() {
return (Modules) addAndGet("module", () -> new Modules(account(), mListener, mContext));
}
public void logout() {
KVVModuleList modules = (KVVModuleList) addons.get("modules");
if (modules != null) {
modules.deleteModulesOffline(this.context);
@NotNull
private Object addAndGet(@NotNull String addon, @NotNull ModuleCreatorInterface creatorInterface) {
Object o = addons.get(addon);
if (o == null) {
o = creatorInterface.create();
addons.put(addon, o);
}
invalidate();
return o;
}
public void invalidate() {
if (lastToken != null) {
new KVVLogin(context).deleteOffline();
lastToken = null;
}
addons.clear();
this.isLoginPending = true;
}
public void getModule(String id, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error) {
getModule(id, callback, error, false);
}
public void getModule(String id, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error, boolean forceRefresh) {
getModulePart(modules -> modules.getModule(id, saveOnCallback(modules, callback, forceRefresh), errorOnCallback(error), forceRefresh));
}
public void getModuleList(final NetworkCallback<Modules> callback, final NetworkErrorCallback error) {
getModuleList(callback, error, false);
}
public void getModuleList(final NetworkCallback<Modules> callback, final NetworkErrorCallback error, boolean forceRefresh) {
getModulePart(modules -> modules.getModuleList(saveOnCallback(modules, callback, forceRefresh), errorOnCallback(error), forceRefresh));
}
public void getModuleDetails(Modules.Module module, final NetworkCallback<Pair<Modules.Module, Boolean>> callback, final NetworkErrorCallback error) {
getModuleDetails(module, callback, error, false);
}
public void getModuleDetails(Modules.Module module, final NetworkCallback<Pair<Modules.Module, Boolean>> callback, final NetworkErrorCallback error, boolean forceRefresh) {
getModulePart(modules -> modules.getModuleDetails(module, saveOnCallback(modules, callback, forceRefresh), errorOnCallback(error), forceRefresh));
}
public void getModuleAnnouncements(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error) {
getModuleAnnouncements(module, callback, error, false);
}
public void getModuleAnnouncements(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error, boolean forceRefresh) {
getModulePart(modules -> modules.getAnnouncements(module, saveOnCallback(modules, callback, forceRefresh), errorOnCallback(error), forceRefresh));
}
public void getModuleAssignments(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error) {
getModuleAssignments(module, callback, error, false);
}
public void getModuleAssignments(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error, boolean forceRefresh) {
getModulePart(modules -> modules.getAssignments(module, saveOnCallback(modules, callback, forceRefresh), errorOnCallback(error), forceRefresh));
}
public void getModuleEvents(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error) {
getModuleEvents(module, callback, error, false);
}
public void getModuleEvents(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error, boolean forceRefresh) {
getModulePart(modules -> modules.getEvents(module, saveOnCallback(modules, callback, forceRefresh), errorOnCallback(error), forceRefresh));
}
public void getModuleGradebook(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error) {
getModuleGradebook(module, callback, error, false);
}
public void getModuleGradebook(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error, boolean forceRefresh) {
getModulePart(modules -> modules.getGradebook(module, saveOnCallback(modules, callback, forceRefresh), errorOnCallback(error), forceRefresh));
}
public void getModuleResources(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error) {
getModuleResources(module, callback, error, false);
}
public void getModuleResources(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback error, boolean forceRefresh) {
getModulePart(modules -> modules.getResources(module, saveOnCallback(modules, callback, forceRefresh), errorOnCallback(error), forceRefresh));
}
private void getModulePart(ModListFunction func) {
this.getLastToken(token -> {
KVVModuleList modules = (KVVModuleList) addons.get("modules");
if (modules == null) {
modules = new KVVModuleList(this.context, token);
addons.put("modules", modules);
}
func.apply(modules);
});
}
private<T> NetworkCallback<T> saveOnCallback(KVVModuleList modules, NetworkCallback<T> callback, boolean forceRefresh){
return (success -> {
try {
modules.saveModulesOffline(this.context);
} catch (IOException e) {
e.printStackTrace();
}
if (forceRefresh)
mListener.refreshFailed(false);
callback.onResponse(success);
});
}
private NetworkErrorCallback errorOnCallback(NetworkErrorCallback errorCallback){
return (error -> {
if (error.getHttpStatus() == 401 || error.getHttpStatus() == 403)
mListener.loginTokenInvalid(false);
else
mListener.refreshFailed(true);
errorCallback.onError(error);
});
}
@FunctionalInterface
interface ModListFunction {
void apply(KVVModuleList mod);
}
private void getLastToken(LastTokenCallback lastTokenCallback) {
if (this.isLoginPending) {
this.updatingList.add(lastTokenCallback);
} else {
lastTokenCallback.onReceived(this.lastToken);
}
}
private void endUpdate() {
this.isLoginPending = false;
for (LastTokenCallback s: this.updatingList) {
s.onReceived(this.lastToken);
}
private interface ModuleCreatorInterface {
@NotNull Object create();
}
}

View File

@@ -0,0 +1,20 @@
package de.sebse.fuplanner.services.KVV;
import com.android.volley.NetworkResponse;
import de.sebse.fuplanner.services.GoogleAuth.Credentials;
import de.sebse.fuplanner.services.KVV.types.LoginToken;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
public interface KVVListener {
void getCredentials(NetworkCallback<Credentials> callback, NetworkErrorCallback error);
void onLogin(LoginToken token, boolean enteringOnlineMode);
void onLogout();
void onModuleListChange();
void onKVVNetworkResponse(NetworkResponse error);
}

View File

@@ -1,517 +0,0 @@
package de.sebse.fuplanner.services.KVV;
import android.content.Context;
import android.util.Pair;
import net.htmlparser.jericho.Source;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.MatchResult;
import de.sebse.fuplanner.services.KVV.types.Announcement;
import de.sebse.fuplanner.services.KVV.types.Assignment;
import de.sebse.fuplanner.services.KVV.types.Event;
import de.sebse.fuplanner.services.KVV.types.Gradebook;
import de.sebse.fuplanner.services.KVV.types.Lecturer;
import de.sebse.fuplanner.services.KVV.types.LoginToken;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.services.KVV.types.AssignmentList;
import de.sebse.fuplanner.services.KVV.types.Resource;
import de.sebse.fuplanner.tools.AsyncQueue;
import de.sebse.fuplanner.services.KVV.types.EventList;
import de.sebse.fuplanner.tools.Regex;
import de.sebse.fuplanner.tools.network.HTTPService;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkError;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
/**
* Created by sebastian on 29.10.17.
*/
class KVVModuleList extends HTTPService {
private final LoginToken token;
private Modules moduleList;
private final AsyncQueue queueModuleDetails = new AsyncQueue();
KVVModuleList(Context context, LoginToken token) {
super(context);
this.token = token;
try {
Modules modules = Modules.load(context);
if (token == null || token.isSameUser(modules.getToken()))
this.moduleList = modules;
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public void getModuleList(final NetworkCallback<Modules> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
queueModuleDetails.add("list", () -> {
if (this.moduleList != null && !forceRefresh) {
callback.onResponse(this.moduleList);
queueModuleDetails.next("list");
return;
}
this.getModuleListUpgrade(success -> {
if (this.moduleList == null)
this.moduleList = success;
else
this.moduleList.updateList(success);
callback.onResponse(this.moduleList);
queueModuleDetails.next("list");
}, queueModuleDetails.check("list", errorCallback));
});
}
private void getModuleListUpgrade(final NetworkCallback<Modules> callback, final NetworkErrorCallback errorCallback) {
if (token == null) {
errorCallback.onError(new NetworkError(101105, 500, "Currently running in offline mode!"));
return;
}
get("https://kvv.imp.fu-berlin.de/direct/site.json", token.getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(101101, 403, "No module list retrieved!"));
return;
}
Modules modules = new Modules(token);
try {
JSONObject json = new JSONObject(body);
JSONArray sites = json.getJSONArray("site_collection");
for (int i = 0; i < sites.length(); i++) {
JSONObject site = sites.getJSONObject(i);
String semester = site.getJSONObject("props").getString("term_eid");
HashSet<String> lvNumbers = new HashSet<>();
for (MatchResult matchResult : Regex.allMatches("[0-9]+", site.getJSONObject("props").getString("kvv_lvnumbers"))) {
lvNumbers.add(matchResult.group());
}
String title = site.getString("entityTitle");
HashSet<Lecturer> lecturers = new HashSet<>();
for (String lecturer : site.getJSONObject("props").getString("kvv_lecturers").split("#")) {
if (lecturer.length() > 2)
lecturers.add(new Lecturer(lecturer));
}
String type = site.getJSONObject("props").getString("kvv_coursetype");
String description = site.getString("description");
description = new Source(description).getRenderer().toString();
String id = site.getString("id");
modules.addModule(semester, lvNumbers, title, lecturers, type, description, id);
}
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101102, 403, "Cannot parse module list!"));
return;
} catch (NoSuchFieldException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101103, 403, "Cannot parse module list!"));
return;
}
// Empty module *may be* because token is invalid -> check
if (modules.size() == 0)
testLogin(token, token -> callback.onResponse(modules), errorCallback);
else
callback.onResponse(modules);
}, error -> errorCallback.onError(new NetworkError(101104, error.networkResponse.statusCode, "Cannot get module list!")));
}
public void deleteModulesOffline(Context context) {
if (this.moduleList != null)
this.moduleList.delete(context);
}
public void saveModulesOffline(Context context) throws IOException {
if (this.moduleList != null)
this.moduleList.save(context);
}
public void getModule(String id, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
this.getModuleList(success -> callback.onResponse(success.get(id)), errorCallback, forceRefresh);
}
public void getModuleDetails(Modules.Module module, final NetworkCallback<Pair<Modules.Module, Boolean>> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
AtomicInteger returns = new AtomicInteger(0);
AtomicReference<NetworkError> lastError = new AtomicReference<>(null);
final AtomicInteger items = new AtomicInteger(0);
NetworkCallback<Modules.Module> successCb = success -> {
returns.getAndIncrement();
callback.onResponse(Pair.create(module, false));
if (returns.get() == items.get()) {
callback.onResponse(Pair.create(module, true));
if (lastError.get() != null)
errorCallback.onError(lastError.get());
}
};
NetworkErrorCallback errorCb = error -> {
lastError.set(error);
returns.getAndIncrement();
if (returns.get() == items.get()) {
callback.onResponse(Pair.create(module, true));
if (lastError.get() != null)
errorCallback.onError(lastError.get());
}
};
Runnable[] methods = {
() -> this.getAssignments(module, successCb, errorCb, forceRefresh),
() -> this.getEvents(module, successCb, errorCb, forceRefresh),
() -> this.getAnnouncements(module, successCb, errorCb, forceRefresh),
() -> this.getGradebook(module, successCb, errorCb, forceRefresh),
() -> this.getResources(module, successCb, errorCb, forceRefresh)
};
items.set(methods.length);
for (Runnable method: methods) {
method.run();
}
}
public void getAnnouncements(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
queueModuleDetails.add(module.getID(), () -> {
if (module.announcements != null && !forceRefresh) {
callback.onResponse(module);
queueModuleDetails.next(module.getID());
return;
}
getAnnouncementsUpgrade(module.getID(), success -> {
module.announcements = success;
callback.onResponse(module);
queueModuleDetails.next(module.getID());
}, queueModuleDetails.check(module.getID(), errorCallback));
});
}
private void getAnnouncementsUpgrade(String ID, final NetworkCallback<ArrayList<Announcement>> callback, final NetworkErrorCallback errorCallback) {
if (token == null) {
errorCallback.onError(new NetworkError(101204, 500, "Currently running in offline mode!"));
return;
}
get(String.format("https://kvv.imp.fu-berlin.de/direct/announcement/site/%s.json?n=999999&d=999999999", ID), token.getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(101201, 403, "No announcements retrieved!"));
return;
}
ArrayList<Announcement> announcements = new ArrayList<>();
try {
JSONObject json = new JSONObject(body);
JSONArray sites = json.getJSONArray("announcement_collection");
for (int i = 0; i < sites.length(); i++) {
JSONObject site = sites.getJSONObject(i);
String id = site.getString("announcementId");
String title = site.getString("title");
String text = site.getString("body");
text = new Source(text).getRenderer().toString();
String createdBy = site.getString("createdByDisplayName");
long createdOn = site.getLong("createdOn");
//PDFs links rausziehen
JSONArray attachments = site.getJSONArray("attachments");
ArrayList<String> urls = new ArrayList<>();
for (int j =0; j<attachments.length(); j++){
urls.add(attachments.getJSONObject(j).optString("url",null));
}
announcements.add(new Announcement(id, title, text, createdBy, createdOn, urls));
}
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101202, 403, "Cannot parse announcements!"));
return;
}
// Empty announcements *may be* because token is invalid -> check
if (announcements.size() == 0)
testLogin(token, token -> callback.onResponse(announcements), errorCallback);
else
callback.onResponse(announcements);
}, error -> errorCallback.onError(new NetworkError(101203, error.networkResponse.statusCode, "Cannot get announcements!")));
}
public void getAssignments(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
queueModuleDetails.add(module.getID(), () -> {
if (module.assignments != null && !forceRefresh) {
callback.onResponse(module);
queueModuleDetails.next(module.getID());
return;
}
getAssignmentsUpgrade(module.getID(), success -> {
module.assignments = success;
callback.onResponse(module);
queueModuleDetails.next(module.getID());
}, queueModuleDetails.check(module.getID(), errorCallback));
});
}
private void getAssignmentsUpgrade(String ID, final NetworkCallback<AssignmentList> callback, final NetworkErrorCallback errorCallback) {
if (token == null) {
errorCallback.onError(new NetworkError(101304, 500, "Currently running in offline mode!"));
return;
}
get(String.format("https://kvv.imp.fu-berlin.de/direct/assignment/site/%s.json", ID), token.getCookies(), response ->{
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(101301, 403, "No assignments retrieved!"));
return;
}
AssignmentList assignments = new AssignmentList();
try {
JSONObject json = new JSONObject(body);
JSONArray sites = json.getJSONArray("assignment_collection");
for (int i = 0; i < sites.length(); i++) {
JSONObject site = sites.getJSONObject(i);
String id = site.getString("id");
String title = site.getString("title");
String instructions = site.getString("instructions");
instructions = new Source(instructions).getRenderer().toString();
long dueTime = site.getJSONObject("dueTime").getLong("time");
String gradebookItemName = site.optString("gradebookItemName", null);
String gradeScale = site.getString("gradeScale");
JSONArray attachments = site.getJSONArray("attachments");
ArrayList<String> urls = new ArrayList<>();
for (int j = 0; j<attachments.length(); j++){
urls.add(attachments.getJSONObject(j).getString("url"));
}
assignments.add(0, new Assignment(id, title, dueTime, gradebookItemName, gradeScale, urls, instructions));
}
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101302, 403, "Cannot parse announcements!"));
return;
}
// Empty assignments *may be* because token is invalid -> check
if (assignments.size() == 0)
testLogin(token, token -> callback.onResponse(assignments), errorCallback);
else
callback.onResponse(assignments);
}, error -> errorCallback.onError(new NetworkError(101303, error.networkResponse.statusCode, "Cannot get assignments!")));
}
public void getEvents(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
queueModuleDetails.add(module.getID(), () -> {
if (module.events != null && !forceRefresh) {
callback.onResponse(module);
queueModuleDetails.next(module.getID());
return;
}
getEventsUpgrade(module.getID(), success -> {
module.events = success;
callback.onResponse(module);
queueModuleDetails.next(module.getID());
}, queueModuleDetails.check(module.getID(), errorCallback));
});
}
private void getEventsUpgrade(String ID, final NetworkCallback<EventList> callback, final NetworkErrorCallback errorCallback) {
if (token == null) {
errorCallback.onError(new NetworkError(101404, 500, "Currently running in offline mode!"));
return;
}
get(String.format("https://kvv.imp.fu-berlin.de/direct/calendar/site/%s.json?detailed=true", ID), token.getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(101401, 403, "No calendar retrieved!"));
return;
}
EventList events = new EventList();
try {
JSONObject json = new JSONObject(body);
JSONArray sites = json.getJSONArray("calendar_collection");
for (int i = 0; i < sites.length(); i++) {
JSONObject site = sites.getJSONObject(i);
String id = site.getString("eventId");
String type = site.getString("type");
String title = site.getString("title");
String siteId = site.getString("siteId");
long duration = site.getLong("duration");
long firstTime = site.getJSONObject("firstTime").getLong("time");
String location = site.getString("location");
events.add(new Event(id, type, title, duration, firstTime, siteId, location));
}
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101402, 403, "Cannot parse calendar entries!"));
return;
}
events.sort();
// Empty events *may be* because token is invalid -> check
if (events.size() == 0)
testLogin(token, token -> callback.onResponse(events), errorCallback);
else
callback.onResponse(events);
}, error -> errorCallback.onError(new NetworkError(101403, error.networkResponse.statusCode, "Cannot get calendar entries!")));
}
public void getGradebook(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
queueModuleDetails.add(module.getID(), () -> {
if (module.gradebook != null && !forceRefresh) {
callback.onResponse(module);
queueModuleDetails.next(module.getID());
return;
}
getGradebookUpgrade(module.getID(), success -> {
module.gradebook = success;
callback.onResponse(module);
queueModuleDetails.next(module.getID());
}, queueModuleDetails.check(module.getID(), errorCallback));
});
}
private void getGradebookUpgrade(String ID, final NetworkCallback<ArrayList<Gradebook>> callback, final NetworkErrorCallback errorCallback) {
if (token == null) {
errorCallback.onError(new NetworkError(101504, 500, "Currently running in offline mode!"));
return;
}
get(String.format("https://kvv.imp.fu-berlin.de/direct/gradebook/site/%s.json", ID ), token.getCookies(), response ->{
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(101501, 403, "No assignments retrieved!"));
return;
}
ArrayList<Gradebook> gradebook = new ArrayList<>();
try {
JSONObject json = new JSONObject(body);
JSONArray sites = json.getJSONArray("assignments");
for (int i = 0; i < sites.length(); i++) {
JSONObject site = sites.getJSONObject(i);
double grade = site.optDouble("grade", 0);
String itemName = site.optString("itemName", null);
double maxPoints = site.optDouble("points", -1);
gradebook.add(0, new Gradebook(itemName, grade, maxPoints));
}
}catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101502, 403, "Cannot parse gradebook for announcements!"));
return;
}
callback.onResponse(gradebook);
}, error -> errorCallback.onError(new NetworkError(101503, error.networkResponse.statusCode, "Cannot get gradebook for assignments!")));
}
public void getResources(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
queueModuleDetails.add(module.getID(), () -> {
if (module.resources != null && !forceRefresh) {
callback.onResponse(module);
queueModuleDetails.next(module.getID());
return;
}
getResourcesUpgrade(module.getID(), success -> {
module.resources = success;
callback.onResponse(module);
queueModuleDetails.next(module.getID());
}, queueModuleDetails.check(module.getID(), errorCallback));
});
}
private void getResourcesUpgrade(String ID, final NetworkCallback<ArrayList<Resource>> callback, final NetworkErrorCallback errorCallback) {
if (token == null) {
errorCallback.onError(new NetworkError(101604, 500, "Currently running in offline mode!"));
return;
}
get(String.format("https://kvv.imp.fu-berlin.de/direct/content/site/%s.json", ID ), token.getCookies(), response ->{
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(101601, 403, "No resources retrieved!"));
return;
}
ArrayList<Resource> resources = new ArrayList<>();
try {
JSONObject json = new JSONObject(body);
JSONArray sites = json.getJSONArray("content_collection");
for (int i = 0; i < sites.length(); i++) {
JSONObject site = sites.getJSONObject(i);
String author = site.getString("author");
String title = site.getString("title");
long modifiedDate = site.getLong("modifiedDate");
String url = site.getString("url");
resources.add(new Resource(author, title, modifiedDate, url));
}
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101602, 403, "Cannot parse resources!"));
return;
}
// Empty resources *may be* because token is invalid -> check
if (resources.size() == 0)
testLogin(token, token -> callback.onResponse(resources), errorCallback);
else
callback.onResponse(resources);
callback.onResponse(resources);
}, error -> errorCallback.onError(new NetworkError(101603, error.networkResponse.statusCode, "Cannot get resources!")));
}
// TODO Better, more elegant solution than duplicate code KVVLogin
private void testLogin(LoginToken loginToken, NetworkCallback<LoginToken> callback, NetworkErrorCallback errorCallback) {
get(String.format("https://kvv.imp.fu-berlin.de/direct/profile/%s.json", loginToken.getUsername()), loginToken.getCookies(), response -> {
String body = response.getParsed();
try {
JSONObject json = new JSONObject(body);
String displayName = json.getString("displayName");
String email = json.getString("email");
loginToken.setAdditionals(displayName, email);
callback.onResponse(loginToken);
} catch (JSONException e) {
errorCallback.onError(new NetworkError(100201, 403, "Cannot parse profile!"));
}
}, error -> errorCallback.onError(new NetworkError(100200, error.networkResponse.statusCode, "Testing login failed!")));
}
}

View File

@@ -1,11 +0,0 @@
package de.sebse.fuplanner.services.KVV;
import de.sebse.fuplanner.services.KVV.types.LoginToken;
/**
* Created by sebastian on 31.01.18.
*/
interface LastTokenCallback {
void onReceived(LoginToken token);
}

View File

@@ -2,72 +2,191 @@ package de.sebse.fuplanner.services.KVV;
import android.content.Context;
import org.jetbrains.annotations.NotNull;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import androidx.annotation.Nullable;
import de.sebse.fuplanner.services.KVV.types.LoginToken;
import de.sebse.fuplanner.tools.network.HTTPService;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkError;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
/**
* Created by sebastian on 24.10.17.
*/
public class Login extends HTTPService {
private KVVListener mListener;
@Nullable private LoginToken mToken;
private boolean mLoginPending = false;
private boolean mOnlineMode = false;
class KVVLogin extends HTTPService {
private final Context mContext;
private LoginToken loginToken;
Login(KVVListener listener, Context context) {
super(context);
this.mListener = listener;
}
KVVLogin(Context context) {
super(context, false);
this.mContext = context;
public void doOnlineLogin(@NotNull String username, @NotNull String password, NetworkCallback<LoginToken> callback, NetworkErrorCallback errorCallback) {
if (mLoginPending) {
errorCallback.onError(new NetworkError(100160, -1, "Login already pending!"));
}
mLoginPending = true;
doLogin(username, password, token -> {
testLoginToken(token, token2 -> {
setToken(token2, true);
mLoginPending = false;
callback.onResponse(token2);
}, error -> {
mLoginPending = false;
errorCallback.onError(error);
});
}, error -> {
mLoginPending = false;
errorCallback.onError(error);
});
}
public boolean restoreOnlineLogin() {
return restoreLogin(true);
}
public boolean doOfflineLogin() {
return restoreLogin(false);
}
private boolean restoreLogin(boolean enteringOnlineMode) {
if (mLoginPending || mToken != null)
return false;
mLoginPending = true;
boolean result = false;
try {
this.loginToken = LoginToken.load(context);
result = setToken(LoginToken.load(getContext()), enteringOnlineMode);
} catch (FileNotFoundException ignored) {
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
mLoginPending = false;
return result;
}
public LoginToken easyLogin() {
return this.loginToken;
public boolean isOfflineStoredAvailable() {
try {
LoginToken load = LoginToken.load(getContext());
return load != null;
} catch (FileNotFoundException ignored) {
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return false;
}
public void login(String username, String password, NetworkCallback<LoginToken> callback, NetworkErrorCallback errorCallback) {
if (this.loginToken != null) {
if (this.loginToken.getUsername().equals(username)) {
testLogin(this.loginToken, success -> callback.onResponse(this.loginToken), error -> {
this.loginToken = null;
login(username, password, callback, errorCallback);
});
} else {
this.loginToken = null;
login(username, password, callback, errorCallback);
public boolean logout(boolean delete) {
if (mLoginPending)
return false;
if (mToken == null)
return true;
if (delete)
mToken.delete(getContext());
mToken = null;
return handleCallbacks();
}
public boolean isLoginPending() {
return mLoginPending;
}
public boolean isLoggedIn() {
return mToken != null;
}
public boolean isInOfflineMode() {
return isLoggedIn() && !mOnlineMode;
}
public boolean isInOnlineMode() {
return isLoggedIn() && mOnlineMode;
}
void testLoginToken(@NotNull NetworkCallback<LoginToken> callback, @NotNull NetworkErrorCallback errorCallback) {
if (mToken == null) {
errorCallback.onError(new NetworkError(100173, -1, "Not logged in!"));
return;
}
testLoginToken(mToken, callback, errorCallback);
}
private void testLoginToken(@NotNull LoginToken token, @NotNull NetworkCallback<LoginToken> callback, @NotNull NetworkErrorCallback errorCallback) {
get(String.format("https://kvv.imp.fu-berlin.de/direct/profile/%s.json", token.getUsername()), token.getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(100172, 403, "Testing login failed!"));
return;
}
try {
JSONObject json = new JSONObject(body);
String displayName = json.getString("displayName");
String email = json.getString("email");
token.setAdditionals(displayName, email);
callback.onResponse(token);
} catch (JSONException e) {
errorCallback.onError(new NetworkError(100171, 403, "Cannot parse profile!"));
}
}, error -> errorCallback.onError(new NetworkError(100170, error.networkResponse.statusCode, "Testing login failed!")));
}
@Nullable public LoginToken getLoginToken() {
return mToken;
}
void refreshLogin(NetworkCallback<LoginToken> success, NetworkErrorCallback error) {
mListener.getCredentials(credentials -> {
doOnlineLogin(credentials.getUsername(), credentials.getPassword(), success, error);
}, e -> {
logout(false);
error.onError(e);
});
}
private boolean handleCallbacks() {
if (mToken != null) {
mListener.onLogin(mToken, mOnlineMode);
return true;
} else {
doLogin(username, password, token -> {
this.loginToken = token;
testLogin(this.loginToken, success -> callback.onResponse(this.loginToken), errorCallback);
}, errorCallback);
mListener.onLogout();
return false;
}
}
public void deleteOffline() {
if (this.loginToken != null)
this.loginToken.delete(mContext);
private boolean setToken(@Nullable LoginToken token, boolean enteringOnlineMode) {
if (token == null)
return false;
boolean isOnlyRefresh = mToken != null;
mToken = token;
if (enteringOnlineMode) {
try {
mToken.save(getContext());
} catch (IOException e) {
e.printStackTrace();
}
}
mOnlineMode = enteringOnlineMode;
return isOnlyRefresh || handleCallbacks();
}
public void saveOffline() throws IOException {
if (this.loginToken != null)
this.loginToken.save(mContext);
}
@@ -92,21 +211,6 @@ class KVVLogin extends HTTPService {
}, error);
}
private void testLogin(LoginToken loginToken, NetworkCallback<LoginToken> callback, NetworkErrorCallback errorCallback) {
get(String.format("https://kvv.imp.fu-berlin.de/direct/profile/%s.json", loginToken.getUsername()), loginToken.getCookies(), response -> {
String body = response.getParsed();
try {
JSONObject json = new JSONObject(body);
String displayName = json.getString("displayName");
String email = json.getString("email");
loginToken.setAdditionals(displayName, email);
callback.onResponse(loginToken);
} catch (JSONException e) {
errorCallback.onError(new NetworkError(100201, 403, "Cannot parse profile!"));
}
}, error -> errorCallback.onError(new NetworkError(100200, error.networkResponse.statusCode, "Testing login failed!")));
}
/*
GET https://kvv.imp.fu-berlin.de/portal/login
-> JSESSIONID 5c10406f-588c-4c16-96e9-c80d115417de.tomcat1
@@ -233,10 +337,16 @@ class KVVLogin extends HTTPService {
cookies.put("ROUTEID", ROUTEID);
cookies.put("_idp_session", _idp_session);
get("https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO", cookies, response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(100143, -1, "Error on getting SAML response!"));
return;
}
HashMap<String, String> object = new HashMap<>();
Pattern pattern = Pattern.compile("ss&#x3a;mem&#x3a;([0-9a-f]+)");
Matcher matcher = pattern.matcher(response.getParsed());
Matcher matcher = pattern.matcher(body);
if (!matcher.find()) {
errorCallback.onError(new NetworkError(100142, -1, "Error on getting SAML response!"));
return;
@@ -244,7 +354,7 @@ class KVVLogin extends HTTPService {
object.put("RelayState", "ss:mem:"+matcher.group(1));
pattern = Pattern.compile("name=\"SAMLResponse\" value=\"([0-9a-zA-Z+]+=*)");
matcher = pattern.matcher(response.getParsed());
matcher = pattern.matcher(body);
if (!matcher.find()) {
errorCallback.onError(new NetworkError(100141, -1, "Error on getting SAML response!"));
return;
@@ -332,5 +442,3 @@ class KVVLogin extends HTTPService {
return result;
}
}

View File

@@ -0,0 +1,81 @@
package de.sebse.fuplanner.services.KVV;
import android.content.Context;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
public class Modules {
private final HashMap<String, Part> mAddons = new HashMap<>();
private ModulesList mList = null;
private final Login mLogin;
private KVVListener mListener;
private final Context context;
Modules(Login login, KVVListener listener, Context context) {
this.mLogin = login;
this.mListener = listener;
this.context = context;
}
@NotNull
public ModulesDetails details() {
return (ModulesDetails) addAndGet("details", () -> {
PartModules[] parts = {announcements(), assignments(), events(), gradebook(), resources()};
return new ModulesDetails(mLogin, list(), context, parts);
});
}
@NotNull
public ModulesAnnouncements announcements() {
return (ModulesAnnouncements) addAndGet("announcements", () -> new ModulesAnnouncements(mLogin, list(), context));
}
@NotNull
public ModulesAssignments assignments() {
return (ModulesAssignments) addAndGet("assignments", () -> new ModulesAssignments(mLogin, list(), context));
}
@NotNull
public ModulesEvents events() {
return (ModulesEvents) addAndGet("events", () -> new ModulesEvents(mLogin, list(), context));
}
@NotNull
public ModulesGradebook gradebook() {
return (ModulesGradebook) addAndGet("gradebook", () -> new ModulesGradebook(mLogin, list(), context));
}
@NotNull
public ModulesResources resources() {
return (ModulesResources) addAndGet("resources", () -> new ModulesResources(mLogin, list(), context));
}
@NotNull
public ModulesList list() {
if (mList == null) {
mList = new ModulesList(mLogin, mListener, context);
mList.addErrorListener("Modules", error -> mListener.onKVVNetworkResponse(error.networkResponse));
mList.addSuccessListener("Modules", success -> mListener.onKVVNetworkResponse(null));
}
return mList;
}
@NotNull
private Part addAndGet(@NotNull String addon, @NotNull ModuleCreatorInterface creatorInterface) {
Part o = mAddons.get(addon);
if (o == null) {
o = creatorInterface.create();
o.addErrorListener("Modules", error -> mListener.onKVVNetworkResponse(error.networkResponse));
o.addSuccessListener("Modules", success -> mListener.onKVVNetworkResponse(null));
mAddons.put(addon, o);
}
return o;
}
private interface ModuleCreatorInterface {
@NotNull Part create();
}
}

View File

@@ -0,0 +1,91 @@
package de.sebse.fuplanner.services.KVV;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import de.sebse.fuplanner.services.KVV.types.Announcement;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkError;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
public class ModulesAnnouncements extends PartModules<ArrayList<Announcement>> {
ModulesAnnouncements(Login login, ModulesList list, Context context) {
super(login, list, context);
}
@Override
protected ArrayList<Announcement> getPart(Modules.Module module) {
return module.announcements;
}
@Override
protected boolean setPart(Modules.Module module, ArrayList<Announcement> part) {
boolean changed = module.announcements == null || module.announcements.hashCode() != part.hashCode();
module.announcements = part;
return changed;
}
@Override
protected void upgrade(final String ID, final NetworkCallback<ArrayList<Announcement>> callback, final NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginToken() == null) {
errorCallback.onError(new NetworkError(101204, 500, "Currently running in offline mode!"));
return;
}
super.get(String.format("https://kvv.imp.fu-berlin.de/direct/announcement/site/%s.json?n=999999&d=999999999", ID), mLogin.getLoginToken().getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(101201, 403, "No announcements retrieved!"));
return;
}
ArrayList<Announcement> announcements = new ArrayList<>();
JSONArray sites;
try {
JSONObject json = new JSONObject(body);
sites = json.getJSONArray("announcement_collection");
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101202, 403, "Cannot parse announcements!"));
return;
}
for (int i = 0; i < sites.length(); i++) {
try {
JSONObject site = sites.getJSONObject(i);
String id = site.getString("announcementId");
String title = site.getString("title");
String text = site.getString("body");
text = String.valueOf(fromHtml(text));
String createdBy = site.getString("createdByDisplayName");
long createdOn = site.getLong("createdOn");
// Extract attachment links
JSONArray attachments = site.getJSONArray("attachments");
ArrayList<String> urls = new ArrayList<>();
for (int j = 0; j < attachments.length(); j++) {
urls.add(attachments.getJSONObject(j).optString("url", null));
}
announcements.add(new Announcement(id, title, text, createdBy, createdOn, urls));
} catch (JSONException e) {
log.e(new NetworkError(101205, 403, "Cannot parse announcements!"));
log.e("ID:", i, "JSON:", sites);
e.printStackTrace();
return;
}
}
// Empty announcements *may be* because token is invalid -> check
if (announcements.size() == 0)
mLogin.testLoginToken(token -> callback.onResponse(announcements), errorCallback);
else
callback.onResponse(announcements);
}, error -> errorCallback.onError(new NetworkError(101203, error.networkResponse.statusCode, "Cannot get announcements!")));
}
}

View File

@@ -0,0 +1,90 @@
package de.sebse.fuplanner.services.KVV;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import de.sebse.fuplanner.services.KVV.types.Assignment;
import de.sebse.fuplanner.services.KVV.types.AssignmentList;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkError;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
public class ModulesAssignments extends PartModules<AssignmentList> {
ModulesAssignments(Login login, ModulesList list, Context context) {
super(login, list, context);
}
@Override
protected AssignmentList getPart(Modules.Module module) {
return module.assignments;
}
@Override
protected boolean setPart(Modules.Module module, AssignmentList part) {
boolean changed = module.assignments == null || module.assignments.hashCode() != part.hashCode();
module.assignments = part;
return changed;
}
@Override
protected void upgrade(final String ID, final NetworkCallback<AssignmentList> callback, final NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginToken() == null) {
errorCallback.onError(new NetworkError(101304, 500, "Currently running in offline mode!"));
return;
}
get(String.format("https://kvv.imp.fu-berlin.de/direct/assignment/site/%s.json", ID), mLogin.getLoginToken().getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(101301, 403, "No assignments retrieved!"));
return;
}
AssignmentList assignments = new AssignmentList();
JSONArray sites;
try {
JSONObject json = new JSONObject(body);
sites = json.getJSONArray("assignment_collection");
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101302, 403, "Cannot parse assignments!"));
return;
}
for (int i = 0; i < sites.length(); i++) {
try {
JSONObject site = sites.getJSONObject(i);
String id = site.getString("id");
String title = site.getString("title");
String instructions = site.getString("instructions");
instructions = String.valueOf(fromHtml(instructions));
long dueTime = site.getJSONObject("dueTime").getLong("time");
String gradebookItemName = site.optString("gradebookItemName", null);
String gradeScale = site.getString("gradeScale");
JSONArray attachments = site.getJSONArray("attachments");
ArrayList<String> urls = new ArrayList<>();
for (int j = 0; j < attachments.length(); j++) {
urls.add(attachments.getJSONObject(j).getString("url"));
}
assignments.add(0, new Assignment(id, title, dueTime, gradebookItemName, gradeScale, urls, instructions));
} catch (JSONException e) {
log.e(new NetworkError(101305, 403, "Cannot parse assignments!"));
e.printStackTrace();
log.e("ID:", i, "JSON:", sites);
return;
}
}
// Empty assignments *may be* because token is invalid -> check
if (assignments.size() == 0)
mLogin.testLoginToken(token -> callback.onResponse(assignments), errorCallback);
else
callback.onResponse(assignments);
}, error -> errorCallback.onError(new NetworkError(101303, error.networkResponse.statusCode, "Cannot get assignments!")));
}
}

View File

@@ -0,0 +1,47 @@
package de.sebse.fuplanner.services.KVV;
import android.content.Context;
import android.util.Pair;
import java.util.concurrent.atomic.AtomicReference;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkError;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
final public class ModulesDetails extends Part<Pair<Modules.Module, Boolean>> {
private final PartModules[] parts;
ModulesDetails(Login login, ModulesList list, Context context, PartModules[] parts) {
super(login, list, context);
this.parts = parts;
}
@Override
protected void recv(final Modules.Module module, final NetworkCallback<Pair<Modules.Module, Boolean>> callback, final NetworkErrorCallback errorCallback, final boolean forceRefresh, final int retries) {
final int[] returned = {0};
AtomicReference<NetworkError> lastError = new AtomicReference<>(null);
NetworkCallback<Modules.Module> successCb = success -> {
returned[0] += 1;
callback.onResponse(Pair.create(module, false));
if (returned[0] == parts.length) {
callback.onResponse(Pair.create(module, true));
if (lastError.get() != null)
errorCallback.onError(lastError.get());
}
};
NetworkErrorCallback errorCb = error -> {
lastError.set(error);
returned[0] += 1;
if (returned[0] == parts.length) {
callback.onResponse(Pair.create(module, true));
if (lastError.get() != null)
errorCallback.onError(lastError.get());
}
};
for (PartModules<?> part: parts) {
part.recv(module, successCb, errorCb, forceRefresh, RETRY_COUNT);
}
}
}

View File

@@ -0,0 +1,83 @@
package de.sebse.fuplanner.services.KVV;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import de.sebse.fuplanner.services.KVV.types.Event;
import de.sebse.fuplanner.services.KVV.types.EventList;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkError;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
public class ModulesEvents extends PartModules<EventList> {
ModulesEvents(Login login, ModulesList list, Context context) {
super(login, list, context);
}
@Override
protected EventList getPart(Modules.Module module) {
return module.events;
}
@Override
protected boolean setPart(Modules.Module module, EventList part) {
boolean changed = module.events == null || module.events.hashCode() != part.hashCode();
module.events = part;
return changed;
}
@Override
protected void upgrade(final String ID, final NetworkCallback<EventList> callback, final NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginToken() == null) {
errorCallback.onError(new NetworkError(101404, 500, "Currently running in offline mode!"));
return;
}
get(String.format("https://kvv.imp.fu-berlin.de/direct/calendar/site/%s.json?detailed=true", ID), mLogin.getLoginToken().getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(101401, 403, "No events retrieved!"));
return;
}
EventList events = new EventList();
JSONArray sites;
try {
JSONObject json = new JSONObject(body);
sites = json.getJSONArray("calendar_collection");
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101402, 403, "Cannot parse events!"));
return;
}
for (int i = 0; i < sites.length(); i++) {
try {
JSONObject site = sites.getJSONObject(i);
String id = site.getString("eventId");
String type = site.getString("type");
String title = site.getString("title");
String siteId = site.getString("siteId");
long duration = site.getLong("duration");
long firstTime = site.getJSONObject("firstTime").getLong("time");
String location = site.getString("location");
events.add(new Event(id, type, title, duration, firstTime, siteId, location));
} catch (JSONException e) {
log.e(new NetworkError(101405, 403, "Cannot parse events!"));
e.printStackTrace();
log.e("ID:", i, "JSON:", sites);
return;
}
}
// Empty events *may be* because token is invalid -> check
if (events.size() == 0)
mLogin.testLoginToken(token -> callback.onResponse(events), errorCallback);
else
callback.onResponse(events);
}, error -> errorCallback.onError(new NetworkError(101403, error.networkResponse.statusCode, "Cannot get events!")));
}
}

View File

@@ -0,0 +1,76 @@
package de.sebse.fuplanner.services.KVV;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import de.sebse.fuplanner.services.KVV.types.Grade;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkError;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
public class ModulesGradebook extends PartModules<ArrayList<Grade>> {
ModulesGradebook(Login login, ModulesList list, Context context) {
super(login, list, context);
}
@Override
protected ArrayList<Grade> getPart(Modules.Module module) {
return module.gradebook;
}
@Override
protected boolean setPart(Modules.Module module, ArrayList<Grade> part) {
boolean changed = module.gradebook == null || module.gradebook.hashCode() != part.hashCode();
module.gradebook = part;
return changed;
}
@Override
protected void upgrade(final String ID, final NetworkCallback<ArrayList<Grade>> callback, final NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginToken() == null) {
errorCallback.onError(new NetworkError(101504, 500, "Currently running in offline mode!"));
return;
}
super.get(String.format("https://kvv.imp.fu-berlin.de/direct/gradebook/site/%s.json", ID), mLogin.getLoginToken().getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(101501, 403, "No gradebook retrieved!"));
return;
}
ArrayList<Grade> gradebook = new ArrayList<>();
JSONArray sites;
try {
JSONObject json = new JSONObject(body);
sites = json.getJSONArray("assignments");
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101502, 403, "Cannot parse gradebook!"));
return;
}
for (int i = 0; i < sites.length(); i++) {
try {
JSONObject site = sites.getJSONObject(i);
double grade = site.optDouble("grade", 0);
String itemName = site.optString("itemName", null);
double maxPoints = site.optDouble("points", -1);
gradebook.add(0, new Grade(itemName, grade, maxPoints));
} catch (JSONException e) {
log.e(new NetworkError(101505, 403, "Cannot parse gradebook!"));
log.e("ID:", i, "JSON:", sites);
e.printStackTrace();
return;
}
}
callback.onResponse(gradebook);
}, error -> errorCallback.onError(new NetworkError(101503, error.networkResponse.statusCode, "Cannot get gradebook!")));
}
}

View File

@@ -0,0 +1,201 @@
package de.sebse.fuplanner.services.KVV;
import android.content.Context;
import org.jetbrains.annotations.Nullable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.regex.MatchResult;
import de.sebse.fuplanner.services.KVV.types.Lecturer;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.services.KVV.types.Semester;
import de.sebse.fuplanner.tools.NewAsyncQueue;
import de.sebse.fuplanner.tools.Regex;
import de.sebse.fuplanner.tools.network.HTTPService;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkError;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
import static de.sebse.fuplanner.services.KVV.PartModules.RETRY_COUNT;
public class ModulesList extends HTTPService {
private final Login mLogin;
private final KVVListener mListener;
@Nullable private Modules mModules;
private NewAsyncQueue mQueue = new NewAsyncQueue();
ModulesList(Login login, KVVListener listener, Context context) {
super(context);
this.mLogin = login;
this.mListener = listener;
restore();
}
@Nullable
public String getUsername() {
if (mModules != null) {
return mModules.getUsername();
}
return null;
}
public void find(String moduleID, NetworkCallback<Modules.Module> moduleNetworkCallback, NetworkErrorCallback errorCallback) {
find(moduleID, moduleNetworkCallback, errorCallback, RETRY_COUNT);
}
private void find(String moduleID, NetworkCallback<Modules.Module> moduleNetworkCallback, NetworkErrorCallback errorCallback, int retries) {
if (mModules != null && mLogin.getLoginToken() != null && !mLogin.getLoginToken().isSameUser(mModules.getUsername()))
delete();
if (retries < 0) {
errorCallback.onError(new NetworkError(101107, -1, "Too many retries!"));
return;
}
if (this.mModules != null) {
Modules.Module module = this.mModules.get(moduleID);
if (module != null) {
moduleNetworkCallback.onResponse(module);
return;
}
}
recv(success -> find(moduleID, moduleNetworkCallback, errorCallback, retries - 1), errorCallback, true, RETRY_COUNT);
}
void store() {
if (this.mModules != null) {
try {
this.mModules.save(getContext());
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void restore() {
try {
this.mModules = Modules.load(getContext());
} catch (FileNotFoundException ignored) {
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public void delete() {
if (this.mModules != null) {
this.mModules.delete(getContext());
this.mModules = null;
}
}
public void recv(final NetworkCallback<Modules> callback, final NetworkErrorCallback errorCallback) {
recv(callback, errorCallback, false);
}
public void recv(final NetworkCallback<Modules> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
recv(callback, errorCallback, forceRefresh, RETRY_COUNT);
}
private void recv(final NetworkCallback<Modules> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh, final int retries) {
if (mModules != null && mLogin.getLoginToken() != null && !mLogin.getLoginToken().isSameUser(mModules.getUsername()))
delete();
mQueue.add(() -> {
if (this.mModules != null && !forceRefresh) {
callback.onResponse(this.mModules);
mQueue.next();
return;
}
this.upgrade(success -> {
if (this.mModules == null)
this.mModules = success;
else
this.mModules.updateList(success);
mListener.onModuleListChange();
store();
callback.onResponse(this.mModules);
mQueue.next();
}, error -> {
if (retries > 0 && (error.getHttpStatus() == 401 || error.getHttpStatus() == 403)) {
mLogin.refreshLogin(success -> {
recv(callback, errorCallback, forceRefresh, retries-1);
mQueue.next();
}, error1 -> {
errorCallback.onError(error1);
mQueue.next();
});
return;
}
errorCallback.onError(error);
mQueue.next();
});
});
}
private void upgrade(final NetworkCallback<Modules> callback, final NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginToken() == null) {
errorCallback.onError(new NetworkError(101105, 500, "Currently running in offline mode!"));
return;
}
get("https://kvv.imp.fu-berlin.de/direct/site.json", mLogin.getLoginToken().getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(101101, 403, "No module list retrieved!"));
return;
}
Modules modules = new Modules(mLogin.getLoginToken().getUsername());
JSONArray sites;
try {
JSONObject json = new JSONObject(body);
sites = json.getJSONArray("site_collection");
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101102, 403, "Cannot parse module list!"));
return;
}
for (int i = 0; i < sites.length(); i++) {
try {
JSONObject site = sites.getJSONObject(i);
String semester_string = site.getJSONObject("props").optString("term_eid", null);
Semester semester = new Semester(semester_string);
HashSet<String> lvNumbers = new HashSet<>();
String kvv_lvnumbers = site.getJSONObject("props").optString("kvv_lvnumbers", null);
if (kvv_lvnumbers != null) for (MatchResult matchResult : Regex.allMatches("[0-9]+", kvv_lvnumbers)) {
lvNumbers.add(matchResult.group());
}
String title = site.getString("entityTitle");
LinkedHashSet<Lecturer> lecturers = new LinkedHashSet<>();
String kvv_lecturers = site.getJSONObject("props").optString("kvv_lecturers", null);
if (kvv_lecturers != null) for (String lecturer : kvv_lecturers.split("#")) {
if (lecturer.length() > 2)
lecturers.add(new Lecturer(lecturer));
}
String type = site.getJSONObject("props").optString("kvv_coursetype", null);
String description = site.optString("description", "");
description = String.valueOf(PartModules.fromHtml(description));
String id = site.getString("id");
modules.addModule(semester, lvNumbers, title, lecturers, type, description, id);
} catch (JSONException e) {
log.e(new NetworkError(101103, 403, "Cannot parse module list!"));
log.e("ID:", i, "JSON:", sites);
e.printStackTrace();
} catch (NoSuchFieldException e) {
log.e(new NetworkError(101106, 403, "Cannot parse module list!"));
log.e("ID:", i, "JSON:", sites);
e.printStackTrace();
}
}
// Empty module *may be* because token is invalid -> check
if (modules.size() == 0)
mLogin.testLoginToken(token -> callback.onResponse(modules), errorCallback);
else
callback.onResponse(modules);
}, error -> errorCallback.onError(new NetworkError(101104, error.networkResponse.statusCode, "Cannot get module list!")));
}
}

View File

@@ -0,0 +1,220 @@
package de.sebse.fuplanner.services.KVV;
import android.content.Context;
import android.os.Environment;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.services.KVV.types.Resource;
import de.sebse.fuplanner.tools.Regex;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkError;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
public class ModulesResources extends PartModules<ArrayList<Resource>> {
ModulesResources(Login login, ModulesList list, Context context) {
super(login, list, context);
}
@Override
protected ArrayList<Resource> getPart(Modules.Module module) {
return module.resources;
}
@Override
protected boolean setPart(Modules.Module module, ArrayList<Resource> part) {
boolean changed = module.resources == null || module.resources.hashCode() != part.hashCode();
module.resources = part;
return changed;
}
@Override
protected void upgrade(final String ID, final NetworkCallback<ArrayList<Resource>> callback, final NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginToken() == null) {
errorCallback.onError(new NetworkError(101604, 500, "Currently running in offline mode!"));
return;
}
get(String.format("https://kvv.imp.fu-berlin.de/direct/content/site/%s.json", ID), mLogin.getLoginToken().getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(101601, 403, "No resources retrieved!"));
return;
}
ArrayList<Resource> resources = new ArrayList<>();
JSONArray sites;
try {
JSONObject json = new JSONObject(body);
sites = json.getJSONArray("content_collection");
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101602, 403, "Cannot parse resources!"));
return;
}
for (int i = 0; i < sites.length(); i++) {
try {
JSONObject site = sites.getJSONObject(i);
String author = site.getString("author");
String title = site.getString("title");
long modifiedDate = site.getLong("modifiedDate");
String url = site.getString("url");
boolean visible = site.getBoolean("visible");
String type = site.getString("type");
String container = site.getString("container");
if (type.equals("collection")){
resources.add(new Resource.Folder(author, title, modifiedDate, url, visible, container));
}
else {
resources.add(new Resource.File(author, title, modifiedDate, url, visible, container, type));
}
} catch (JSONException e) {
log.e(new NetworkError(101605, 403, "Cannot parse resources!"));
e.printStackTrace();
log.e("ID:", i, "JSON:", sites);
return;
}
}
ArrayList<Resource> root = new ArrayList<>();
// Generate folder structure
for (Resource res: resources) {
if (!res.getContainer().equals("/content/group/")) {
if (res.getContainer().equals("/content/group/"+ID+"/")){
// if file in root folder
root.add(res);
} else {
// in sub folder
for (Resource res2: resources) {
if (res2.getUrl().endsWith(res.getContainer()) && res2 instanceof Resource.Folder) {
// Append File/Folder to list
((Resource.Folder) res2).add(res);
}
}
}
}
}
// Empty resources *may be* because token is invalid -> check
if (resources.size() == 0)
mLogin.testLoginToken(token -> callback.onResponse(root), errorCallback);
else
callback.onResponse(root);
}, error -> errorCallback.onError(new NetworkError(101603, error.networkResponse.statusCode, "Cannot get resources!")));
}
public void file(final String filename, final String url, final String modulename, final NetworkCallback<String> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
file(filename, url, modulename, callback, errorCallback, forceRefresh, RETRY_COUNT);
}
public void file(final String filename, final String url, final String modulename, final NetworkCallback<String> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh, int retries) {
if (isExternalStorageReadable()){
File f = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS)+"/"+modulename+"/"+filename);
// check if file already downloaded -> do not download again
if (f.exists() && !forceRefresh) {
callback.onResponse(f.getPath());
return;
}
}
fileUpgrade(filename, url , modulename, callback, error -> {
if (retries >= 0 && (error.getHttpStatus() == 401 || error.getHttpStatus() == 403)) {
mLogin.refreshLogin(success -> {
file(filename, url, modulename, callback, errorCallback, forceRefresh, retries-1);
}, errorCallback);
return;
}
errorCallback.onError(error);
});
}
private void fileUpgrade(String filename, String url, String modulename, final NetworkCallback<String> callback, final NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginToken() == null) {
errorCallback.onError(new NetworkError(101604, 500, "Currently running in offline mode!"));
return;
}
get(url, mLogin.getLoginToken().getCookies(), response -> {
if (Regex.has("\\.[Uu][Rr][Ll]$", url)){
// Return redirected URL
String path = response.getHeaders().get("Location");
if (path == null){
path = "";
}
callback.onResponse(path);
} else if (response.getBytes() == null) {
errorCallback.onError(new NetworkError(101705, 403, "Cannot get file!"));
} else if (isExternalStorageWritable()) {
String path = saveFileInDownloads(filename, response.getBytes(), modulename);
callback.onResponse(path);
} else {
errorCallback.onError(new NetworkError(101704, 403, "External storage not writable!"));
}
}, error -> errorCallback.onError(new NetworkError(101702, error.networkResponse.statusCode, "Cannot get file!")));
}
/* Checks if external storage is available for read and write */
private boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
log.w("File system: Writing not possible!");
return false;
}
/* Checks if external storage is available to at least read */
private boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
log.w("File system: Reading not possible!");
return false;
}
private String saveFileInDownloads(String filename, byte[] data, String moduleName) {
// Saves file in folder: DOWNLOADS/moduleName
File folder = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS), moduleName);
if (!folder.mkdir()) {
log.w( "Directory not created");
}
String path = "";
try {
// TODO check if enough storage space is available
FileOutputStream out = new FileOutputStream(folder.getPath()+"/"+filename);
out.write(data);
out.close();
path = folder.getPath()+"/"+filename;
} catch (Exception e) {
log.w("File not saved!");
e.printStackTrace();
}
return path;
}
}

View File

@@ -0,0 +1,34 @@
package de.sebse.fuplanner.services.KVV;
import android.content.Context;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.network.HTTPService;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
public abstract class Part<T> extends HTTPService {
static final int RETRY_COUNT = 1;
protected final Login mLogin;
protected final ModulesList mList;
Part(Login login, ModulesList list, Context context) {
super(context);
this.mLogin = login;
this.mList = list;
}
public void recv(final String moduleID, final NetworkCallback<T> callback, final NetworkErrorCallback errorCallback) {
recv(moduleID, callback, errorCallback, false);
}
public void recv(final String moduleID, final NetworkCallback<T> callback, final NetworkErrorCallback errorCallback, final boolean forceRefresh) {
mList.find(moduleID, success -> recv(success, callback, errorCallback, forceRefresh), errorCallback);
}
public void recv(final Modules.Module module, final NetworkCallback<T> callback, final NetworkErrorCallback errorCallback, final boolean forceRefresh) {
recv(module, callback, errorCallback, forceRefresh, RETRY_COUNT);
}
abstract protected void recv(final Modules.Module module, final NetworkCallback<T> callback, final NetworkErrorCallback errorCallback, final boolean forceRefresh, final int retries);
}

View File

@@ -0,0 +1,65 @@
package de.sebse.fuplanner.services.KVV;
import android.content.Context;
import android.os.Build;
import android.text.Html;
import android.text.Spanned;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.NewAsyncQueue;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
abstract class PartModules<T> extends Part<Modules.Module> {
private NewAsyncQueue mQueue = new NewAsyncQueue();
PartModules(Login login, ModulesList list, Context context) {
super(login, list, context);
}
@Override
protected void recv(final Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback errorCallback, final boolean forceRefresh, final int retries) {
mQueue.add(() -> {
if (getPart(module) != null && !forceRefresh) {
callback.onResponse(module);
mQueue.next();
return;
}
upgrade(module.getID(), success -> {
if (setPart(module, success)) {
this.mList.store();
}
callback.onResponse(module);
mQueue.next();
}, error -> {
if (retries >= 0 && (error.getHttpStatus() == 401 || error.getHttpStatus() == 403)) {
mLogin.refreshLogin(success -> {
recv(module, callback, errorCallback, forceRefresh, retries-1);
mQueue.next();
}, error1 -> {
errorCallback.onError(error1);
mQueue.next();
});
return;
}
errorCallback.onError(error);
mQueue.next();
});
});
}
@SuppressWarnings("deprecation")
static Spanned fromHtml(String html){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
} else {
return Html.fromHtml(html);
}
}
protected abstract T getPart(Modules.Module module);
protected abstract boolean setPart(Modules.Module module, T part);
protected abstract void upgrade(final String ID, final NetworkCallback<T> callback, final NetworkErrorCallback errorCallback);
}

View File

@@ -1,5 +1,7 @@
package de.sebse.fuplanner.services.KVV.types;
import com.google.android.gms.common.internal.Objects;
import java.io.Serializable;
import java.util.ArrayList;
@@ -25,7 +27,7 @@ public class Announcement implements Serializable {
return urls;
}
public String getId() {
private String getId() {
return id;
}
@@ -54,4 +56,9 @@ public class Announcement implements Serializable {
"\nCreated on: "+getCreatedOn()+
"\nURLs: "+getUrls().toString();
}
@Override
public int hashCode() {
return Objects.hashCode(getId(), getBody(), getCreatedBy(), getCreatedOn(), getTitle(), getUrls());
}
}

View File

@@ -1,5 +1,7 @@
package de.sebse.fuplanner.services.KVV.types;
import com.google.android.gms.common.internal.Objects;
import java.io.Serializable;
import java.util.ArrayList;
@@ -7,8 +9,6 @@ public class Assignment implements Serializable {
private final String id;
private final String title;
private final long dueTime;
private final String gradebookItemName;
private final String gradeScale;
private final ArrayList<String> urls;
private final String instructions;
@@ -16,14 +16,11 @@ public class Assignment implements Serializable {
this.id = id;
this.title = title;
this.dueTime = dueTime;
this.gradebookItemName = gradebookItemName;
this.gradeScale = gradeScale;
this.urls = urls;
//this.grade = grade;
this.instructions = instructions;
}
public String getId() {
private String getId() {
return id;
}
@@ -52,6 +49,11 @@ public class Assignment implements Serializable {
return "ID: "+getId()+
"\nTitle: "+getTitle()+
"\nDue date: "+getDueDate()+
"\nInstuctions: "+getInstructions().substring(0, Math.min(getInstructions().length(), 100));
"\nInstructions: "+getInstructions().substring(0, Math.min(getInstructions().length(), 100));
}
@Override
public int hashCode() {
return Objects.hashCode(getId(), getDueDate(), getInstructions(), getTitle(), getUrls());
}
}

View File

@@ -1,5 +1,7 @@
package de.sebse.fuplanner.services.KVV.types;
import com.google.android.gms.common.internal.Objects;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
@@ -111,4 +113,9 @@ public class Event implements Serializable {
"\nStart Date: "+getStartDate()+
"\nEnd date: "+getEndDate();
}
@Override
public int hashCode() {
return Objects.hashCode(getId(), getType(), getStartDate(), getEndDate(), getTitle(), getLocation());
}
}

View File

@@ -8,4 +8,9 @@ public class EventList extends DateSortedList<Event> {
public long getDateByItem(Event item) {
return item.getEndDate();
}
@Override
public boolean reversed() {
return false;
}
}

View File

@@ -1,13 +1,15 @@
package de.sebse.fuplanner.services.KVV.types;
import com.google.android.gms.common.internal.Objects;
import java.io.Serializable;
public class Gradebook implements Serializable {
public class Grade implements Serializable {
private final String itemName;
private final double grade;
private final double maxPoints;
public Gradebook(String itemName, double points, double maxPoints) {
public Grade(String itemName, double points, double maxPoints) {
this.itemName = itemName;
this.grade = points;
this.maxPoints = maxPoints;
@@ -31,4 +33,9 @@ public class Gradebook implements Serializable {
"\nPoints: "+ getPoints()+
"\nMax points: "+getMaxPoints();
}
@Override
public int hashCode() {
return Objects.hashCode(getItemName(), getPoints(), getMaxPoints());
}
}

View File

@@ -0,0 +1,128 @@
package de.sebse.fuplanner.services.KVV.types;
import android.annotation.SuppressLint;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import androidx.annotation.NonNull;
import de.sebse.fuplanner.tools.logging.Logger;
public class GroupedEvents {
private ArrayList<Group> arrayList = new ArrayList<>();
private Logger log = new Logger(this);
public void add(Event event) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(event.getStartDate());
long dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
for (Group group : arrayList) {
if (group.add(event)) {
return;
}
}
arrayList.add(new Group(event));
}
public List<Group> getGroups() {
return Collections.unmodifiableList(arrayList);
}
public class Group {
private long firstDate;
private long lastDate;
private ArrayList<Long> skippedDates;
private int dayOfWeek;
private long startTime;
private long duration;
private Group(Event event) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(event.getStartDate());
dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
startTime = calendar.get(Calendar.HOUR_OF_DAY)*3600000+calendar.get(Calendar.MINUTE)*60000+calendar.get(Calendar.SECOND)*1000+calendar.get(Calendar.MILLISECOND);
duration = event.getEndDate()-event.getStartDate();
skippedDates = new ArrayList<>();
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
firstDate = calendar.getTimeInMillis();
lastDate = firstDate;
}
private boolean add(Event event) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(event.getStartDate());
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
int startTime = calendar.get(Calendar.HOUR_OF_DAY)*3600000+calendar.get(Calendar.MINUTE)*60000+calendar.get(Calendar.SECOND)*1000+calendar.get(Calendar.MILLISECOND);
long length = event.getEndDate()-event.getStartDate();
if (this.dayOfWeek != dayOfWeek || this.startTime != startTime || this.duration != length)
return false;
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
long date = calendar.getTimeInMillis();
if (date < firstDate) {
firstDate = addDays(firstDate, -7);
while (firstDate > date) {
skippedDates.add(firstDate);
firstDate = addDays(firstDate, -7);
}
} else if (date > lastDate) {
lastDate = addDays(lastDate, 7);
while (lastDate < date) {
skippedDates.add(lastDate);
lastDate = addDays(lastDate, 7);
}
} else {
skippedDates.remove(date);
}
return true;
}
private long addDays(long timeInMillis, int days) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timeInMillis);
calendar.add(Calendar.DAY_OF_YEAR, days);
return calendar.getTimeInMillis();
}
public long getFirstDate() {
return firstDate;
}
public long getLastDate() {
return lastDate;
}
public List<Long> getSkippedDates() {
return Collections.unmodifiableList(skippedDates);
}
public int getDayOfWeek() {
return dayOfWeek;
}
public long getStartTime() {
return startTime;
}
public long getDuration() {
return duration;
}
@SuppressLint("DefaultLocale")
@NonNull
@Override
public String toString() {
return String.format("%d - %d - %d - %s", dayOfWeek, startTime, duration, skippedDates);
}
}
}

View File

@@ -5,23 +5,25 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Lecturer implements Serializable {
private final String firstname;
private final String firstName;
private final String surname;
private final String mail;
private final boolean isResponsible;
public Lecturer(String parsableString) throws NoSuchFieldException {
Pattern pattern = Pattern.compile("([^|]*)\\|([^|]*)\\|([^|]*)\\|\\|", Pattern.DOTALL);
Pattern pattern = Pattern.compile("([^|]*)\\|([^|]*)\\|([^|]*)\\|\\|([^|]*)", Pattern.DOTALL);
Matcher matcher = pattern.matcher(parsableString);
if (!matcher.find()) {
throw new NoSuchFieldException();
}
this.firstname = matcher.group(1);
this.firstName = matcher.group(1);
this.surname = matcher.group(2);
this.mail = matcher.group(3);
this.isResponsible = matcher.group(4).equals("true");
}
public String getFirstname() {
return firstname;
public String getFirstName() {
return firstName;
}
public String getSurname() {
@@ -32,9 +34,21 @@ public class Lecturer implements Serializable {
return mail;
}
public boolean isResponsible() {
return isResponsible;
}
public String getName() {
return getFirstName() + " " + getSurname();
}
public String getNameShort() {
return getFirstName().substring(0, 1) + ". " + getSurname();
}
@Override
public String toString() {
return "First name: "+getFirstname()+
return "First name: "+ getFirstName()+
"\nSurname: "+getSurname()+
"\nMail: "+getMail();
}

View File

@@ -1,9 +1,9 @@
package de.sebse.fuplanner.services.KVV.types;
import android.content.Context;
import android.support.annotation.Nullable;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
@@ -11,21 +11,21 @@ import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashMap;
import androidx.annotation.Nullable;
/**
* Created by sebastian on 29.10.17.
*/
public class LoginToken implements Serializable {
private static final long EASY_LOGIN_TIME_MILLIS = 1000 * 60 * 60 * 300;
private static final String FILE_NAME = "LoginTokenSaving";
private final String username;
private final String shibsessionKey;
private final String shibsessionName;
private final String JSESSIONID;
private String fullname;
private String email;
private long saveDate = 0;
@Nullable private String fullName;
@Nullable private String email;
public LoginToken(String username, String shibsessionKey, String shibsessionName, String JSESSIONID) {
this.username = username;
@@ -36,17 +36,23 @@ public class LoginToken implements Serializable {
@Nullable
public static LoginToken load(Context context) throws IOException, ClassNotFoundException {
FileInputStream fis = context.openFileInput(FILE_NAME);
FileInputStream fis;
try {
fis = context.openFileInput(FILE_NAME);
} catch (FileNotFoundException e) {
return null;
}
ObjectInputStream is = new ObjectInputStream(fis);
LoginToken loginToken = (LoginToken) is.readObject();
Object readObject = is.readObject();
if (!(readObject instanceof LoginToken))
return null;
LoginToken loginToken = (LoginToken) readObject;
is.close();
fis.close();
loginToken = (loginToken.saveDate > (System.currentTimeMillis() - EASY_LOGIN_TIME_MILLIS)) ? loginToken : null;
return loginToken;
}
public void save(Context context) throws IOException {
saveDate = System.currentTimeMillis();
FileOutputStream fos = context.openFileOutput(FILE_NAME, Context.MODE_PRIVATE);
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(this);
@@ -58,8 +64,8 @@ public class LoginToken implements Serializable {
context.deleteFile(FILE_NAME);
}
public void setAdditionals(String fullname, String email) {
this.fullname = fullname;
public void setAdditionals(String fullName, String email) {
this.fullName = fullName;
this.email = email;
}
@@ -79,8 +85,8 @@ public class LoginToken implements Serializable {
return JSESSIONID;
}
public String getFullname() {
return fullname;
public String getFullName() {
return fullName;
}
public String getEmail() {
@@ -95,8 +101,8 @@ public class LoginToken implements Serializable {
return cookies;
}
public boolean isSameUser(LoginToken token) {
return token != null && this.getUsername().equals(token.getUsername());
public boolean isSameUser(String username) {
return this.getUsername().equals(username);
}
@Override

View File

@@ -1,8 +1,8 @@
package de.sebse.fuplanner.services.KVV.types;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -13,6 +13,10 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Created by sebastian on 29.10.17.
@@ -20,20 +24,21 @@ import java.util.Iterator;
public class Modules implements Iterable<Modules.Module>, Serializable {
private SortedListModule list;
private final LoginToken token;
private final String mUsername;
//private transient Logger log = new Logger(this);
private static final String FILE_NAME = "ModuleListSaving";
public Modules(LoginToken loginToken) {
this.token = loginToken;
public Modules(String username) {
this.mUsername = username;
this.list = new SortedListModule();
}
public void addModule(String semester, HashSet<String> lvNumber, String title, HashSet<Lecturer> lecturer, String type, String description, String ID) {
public void addModule(@Nullable Semester semester, HashSet<String> lvNumber, String title, LinkedHashSet<Lecturer> lecturer, String type, String description, String ID) {
Module m = new Module(semester, lvNumber, title, lecturer, type, description, ID);
this.list.add(m);
}
@NonNull
@Override
public String toString() {
return this.list.toString();
@@ -64,7 +69,10 @@ public class Modules implements Iterable<Modules.Module>, Serializable {
public static Modules load(Context context) throws IOException, ClassNotFoundException {
FileInputStream fis = context.openFileInput(FILE_NAME);
ObjectInputStream is = new ObjectInputStream(fis);
Modules modules = (Modules) is.readObject();
Object readObject = is.readObject();
if (!(readObject instanceof Modules))
return null;
Modules modules = (Modules) readObject;
is.close();
fis.close();
return modules;
@@ -82,8 +90,8 @@ public class Modules implements Iterable<Modules.Module>, Serializable {
context.deleteFile(FILE_NAME);
}
public LoginToken getToken() {
return token;
public String getUsername() {
return mUsername;
}
public void updateList(Modules modules) {
@@ -102,29 +110,25 @@ public class Modules implements Iterable<Modules.Module>, Serializable {
}
public class Module implements Serializable {
public final String semester;
public final HashSet<String> lvNumber;
public final String title;
public final HashSet<Lecturer> lecturer;
public final String type;
public final String description;
private final String ID;
@Nullable public final Semester semester;
@NotNull final HashSet<String> lvNumber;
@NotNull public final String title;
@NotNull
public final ArrayList<Lecturer> lecturer;
@Nullable public final String type;
@Nullable public final String description;
@NotNull private final String ID;
@Nullable public ArrayList<Announcement> announcements;
@Nullable public AssignmentList assignments;
@Nullable public EventList events;
@Nullable public ArrayList<Gradebook> gradebook;
@Nullable public ArrayList<Grade> gradebook;
@Nullable public ArrayList<Resource> resources;
/*private Module() {
this(null, null, null, null, null);
throw new AssertionError("Do not use this constructor!");
}*/
public float getGradebookPercent(){
float maxPoint = 0;
float userPoint = 0;
if (gradebook != null) {
for (Gradebook g : gradebook){
for (Grade g : gradebook){
maxPoint += g.getMaxPoints();
userPoint += g.getPoints();
}
@@ -134,24 +138,25 @@ public class Modules implements Iterable<Modules.Module>, Serializable {
return userPoint/maxPoint;
}
private Module(String semester, HashSet<String> lvNumber, String title, HashSet<Lecturer> lecturer, String type, String description, String ID) {
semester = semester.replace("SS", "S");
semester = semester.replaceAll("[0-9]{2}([0-9]{2})", "$1");
private Module(@Nullable Semester semester, @NotNull HashSet<String> lvNumber, @NotNull String title, @NotNull LinkedHashSet<Lecturer> lecturer, @Nullable String type, @Nullable String description, @NotNull String ID) {
title = title.replaceAll("(.*?) (S[0-9]{2}|W[0-9/]{5})", "$1");
this.semester = semester;
this.lvNumber = lvNumber;
this.title = title;
this.lecturer = lecturer;
this.lecturer = new ArrayList<>(lecturer);
this.type = type;
this.description = description;
this.ID = ID;
}
@NonNull
public String getID() {
return ID;
}
@NonNull
@Override
public String toString() {
return "Semester: "+semester+

View File

@@ -1,22 +1,33 @@
package de.sebse.fuplanner.services.KVV.types;
import com.google.android.gms.common.internal.Objects;
import java.io.Serializable;
import java.util.ArrayList;
import androidx.annotation.LayoutRes;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.tools.ui.treeview.LayoutItemType;
import de.sebse.fuplanner.tools.ui.treeview.TreeNode;
public abstract class Resource implements Serializable {
public class Resource implements Serializable {
private final String author;
private final long modifiedDate;
private final String title;
private final String url;
final String author;
final long modifiedDate;
final String title;
final String url;
private final boolean visible;
private final String container;
public Resource(String author, String title, long modifiedDate, String url) {
Resource(String author, String title, long modifiedDate, String url, boolean visible, String container) {
this.author = author;
this.title = title;
this.modifiedDate = modifiedDate;
this.url = url;
this.visible = visible;
this.container = container;
}
public String getAuthor() {
@@ -35,16 +46,96 @@ public class Resource implements Serializable {
return url;
}
@Override
public String toString() {
return "Resource{" +
"author='" + author + '\'' +
", modifiedDate=" + modifiedDate +
", title='" + title + '\'' +
", url='" + url + '\'' +
'}';
public String getContainer() {
return container;
}
public boolean isVisible() {
return visible;
}
public abstract TreeNode getTreeNode();
@Override
public int hashCode() {
return Objects.hashCode(getAuthor(), getContainer(), getModifiedDate(), getTitle(), getUrl());
}
public static class File extends Resource implements LayoutItemType {
private final String type;
public File(String author, String title, long modifiedDate, String url, boolean visible, String container, String type) {
super(author, title, modifiedDate, url, visible, container);
this.type = type;
}
@Override
public String toString() {
return "Resource{" +
"author='" + author + '\'' +
", modifiedDate=" + modifiedDate +
", title='" + title + '\'' +
", url='" + url + '\'' +
", type='" + type + '\'' +
'}';
}
@Override
public TreeNode getTreeNode() {
return new TreeNode<>(this);
}
@Override
public @LayoutRes int getLayoutId() {
return R.layout.item_file;
}
}
public static class Folder extends Resource implements LayoutItemType {
private final ArrayList<Resource> children;
public Folder(String author, String title, long modifiedDate, String url, boolean visible, String container) {
super(author, title, modifiedDate, url, visible, container);
children = new ArrayList<>();
}
public void add(Resource res){
children.add(res);
}
public Resource get(int id){
return children.get(id);
}
public int size(){
return children.size();
}
@Override
public String toString() {
return "Resource{" +
"author='" + author + '\'' +
", modifiedDate=" + modifiedDate +
", title='" + title + '\'' +
", url='" + url + '\'' +
", children='" + children + '\'' +
'}';
}
@Override
public TreeNode getTreeNode() {
TreeNode dir = new TreeNode<>(this);
for (Resource res: children) {
dir.addChild(res.getTreeNode());
}
return dir;
}
@Override
public @LayoutRes int getLayoutId() {
return R.layout.item_dir;
}
}
}

View File

@@ -0,0 +1,55 @@
package de.sebse.fuplanner.services.KVV.types;
import java.io.Serializable;
import androidx.annotation.Nullable;
import de.sebse.fuplanner.tools.Regex;
public class Semester implements Serializable {
public static final int SEM_WS = 1;
public static final int SEM_SS = 2;
private int type;
private int year;
public Semester(int type, int year) {
this.type = type;
this.year = year;
}
public Semester(String semester_string) throws NoSuchFieldException {
/*Semester sem = null;
if (semester != null) {
sem = new Semester(semester)
semester = semester.replace("SS", "S");
semester = semester.replaceAll("[0-9]{2}([0-9]{2})", "$1");
}*/
/*String s1type = Regex.regex("^(S|WS) ", a);
int s1year = Integer.parseInt(Regex.regex("^(S|WS) ([0-9]{2})", a, 2));
String s2type = Regex.regex("^(S|WS) ", b);
int s2year = Integer.parseInt(Regex.regex("^(S|WS) ([0-9]{2})", b, 2));*/
String type = Regex.regex("^(SS|WS) ", semester_string);
String year = Regex.regex("^(SS|WS) ([0-9]{2})", semester_string, 2);
this.type = type.equals("SS") ? SEM_SS : SEM_WS;
this.year = Integer.parseInt(year);
}
public int getType() {
return type;
}
public int getYear() {
return year;
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj instanceof Semester) {
Semester other = (Semester) obj;
return other.type == type && other.year == year;
}
return false;
}
}

View File

@@ -1,9 +1,9 @@
package de.sebse.fuplanner.services.KVV.types;
import de.sebse.fuplanner.tools.Regex;
import androidx.annotation.Nullable;
import de.sebse.fuplanner.tools.SortedList;
public class SortedListModule extends SortedList<Modules.Module, String, String> {
public class SortedListModule extends SortedList<Modules.Module, String, Semester> {
private static final int LARGER = 1;
private static final int EQUAL = 0;
private static final int SMALLER = -1;
@@ -27,7 +27,7 @@ public class SortedListModule extends SortedList<Modules.Module, String, String>
super.add(e);
}
public String getLatestSemester() {
public Semester getLatestSemester() {
if (size() > 0)
//noinspection ConstantConditions
return this.get(0).semester;
@@ -35,7 +35,7 @@ public class SortedListModule extends SortedList<Modules.Module, String, String>
return null;
}
private static int compareSemester(String a, String b) throws NoSuchFieldException {
private static int compareSemester(@Nullable Semester a, @Nullable Semester b) throws NoSuchFieldException {
if (a == null && b == null)
return EQUAL;
if (a == null)
@@ -43,17 +43,13 @@ public class SortedListModule extends SortedList<Modules.Module, String, String>
if (b == null)
return LARGER;
String s1type = Regex.regex("(S|WS)", a);
int s1year = Integer.parseInt(Regex.regex("(S|WS) ([0-9]{2})", a, 2));
String s2type = Regex.regex("(S|WS)", b);
int s2year = Integer.parseInt(Regex.regex("(S|WS) ([0-9]{2})", b, 2));
if (s1year == s2year) {
if (s1type.equals(s2type))
if (a.getYear() == b.getYear()) {
if (a.getType() == b.getType())
return EQUAL;
return s1type.equals("SS") ? SMALLER : LARGER;
return a.getType() == Semester.SEM_SS ? SMALLER : LARGER;
}
return s1year < s2year ? SMALLER : LARGER;
return a.getYear() < b.getYear() ? SMALLER : LARGER;
}
@Override
@@ -62,7 +58,7 @@ public class SortedListModule extends SortedList<Modules.Module, String, String>
}
@Override
public boolean hasFilter(Modules.Module o1, String filter) {
return o1.semester.equals(filter);
public boolean hasFilter(Modules.Module o1, Semester filter) {
return o1.semester != null && o1.semester.equals(filter);
}
}

View File

@@ -0,0 +1,264 @@
package de.sebse.fuplanner.services.KVV.ui;
import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Environment;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.FileProvider;
import de.sebse.fuplanner.MainActivity;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.types.Resource;
import de.sebse.fuplanner.tools.Regex;
import de.sebse.fuplanner.tools.RequestPermissionsResultListener;
import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.logging.Logger;
import static androidx.core.content.ContextCompat.checkSelfPermission;
public class Download {
private final ContextInterface contextInterface;
private final ActivityInterface activityInterface;
private RequestedDownload requestedDownload;
private Logger log = new Logger(this);
public Download(ContextInterface contextInterface, ActivityInterface activityInterface) {
this.contextInterface = contextInterface;
this.activityInterface = activityInterface;
}
public void openDownloadDialog(String title, String url, String folderName) {
openDownloadDialog(new Resource.File("", title, 0, url, true, "", ""), folderName);
}
public void openDownloadDialog(Resource.File file, String folderName) {
Context context = contextInterface.get();
if (context == null)
return;
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context);
File f = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS)+"/"+folderName+"/"+file.getTitle());
Resources resources = context.getResources();
String message = "";
if (file.getAuthor() != null && !file.getAuthor().isEmpty())
message += resources.getString(R.string.creator_name, file.getAuthor());
if (file.getModifiedDate() != 0) {
if (!message.isEmpty())
message += "\n";
message += resources.getString(R.string.last_modified_on, UtilsDate.getModifiedDateTime(context, file.getModifiedDate()));
}
alertDialogBuilder
.setTitle(file.getTitle())
.setMessage(message)
.setCancelable(true)
.setNeutralButton(R.string.close, (dialog, id) -> dialog.cancel());
// if already downloaded, show open button
if (f.exists()) {
alertDialogBuilder
.setPositiveButton(R.string.download_again, (dialog, id) -> download(file, folderName, true))
.setNegativeButton(R.string.openFile, (dialog, id) -> download(file, folderName, false));
} else {
alertDialogBuilder
.setPositiveButton(R.string.download, (dialog, id) -> download(file, folderName, true));
}
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}
private void download(Resource.File file, String folderName, boolean downloadNew){
MainActivity activity = activityInterface.get();
if (activity == null) {
showDownloadError();
return;
}
if (checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
// Access granted
downloadOrOpen(file, folderName, downloadNew);
} else {
this.requestedDownload = new RequestedDownload(file, folderName, downloadNew);
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
1);
}
}
private void downloadOrOpen(Resource.File file, String folderName, boolean downloadNew) {
if (!isExternalStorageWritable()) {
return;
}
MainActivity activity = activityInterface.get();
if (activity == null) {
showDownloadError();
return;
}
activity.getKVV().modules().resources().file(file.getTitle(), file.getUrl(), folderName, success -> {
Context context = contextInterface.get();
if (success.equals("")) {
showDownloadError();
} else {
if (Regex.has("^http", success)){
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(success));
context.startActivity(intent);
}
else {
fileOpen(new File(success));
}
}
}, log::e, downloadNew);
}
private void showDownloadError() {
Context context = contextInterface.get();
if (context == null)
return;
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context);
alertDialogBuilder
.setTitle(R.string.ErrorFileDownload)
.setMessage(
R.string.ErrorFileDownloadText
)
.setCancelable(true)
.setNeutralButton(R.string.close, (dialog, id) -> dialog.cancel());
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}
public RequestPermissionsResultListener getRequestPermissionsResultListener() {
return (requestCode, permissions, grantResults) -> {
if (requestedDownload == null) {
log.d("No request");
return;
}
if (activityInterface.get() == null) {
showDownloadError();
return;
}
ArrayList<Integer> intList = new ArrayList<>();
for (int i : grantResults)
{
intList.add(i);
}
log.d(requestCode, Arrays.asList(permissions), intList);
int pos = Arrays.asList(permissions).indexOf("android.permission.WRITE_EXTERNAL_STORAGE");
if (pos != -1) {
if (grantResults[pos] != -1) {
downloadOrOpen(requestedDownload.file, requestedDownload.folderName, requestedDownload.downloadNew);
} else {
log.d(requestedDownload, pos, grantResults[pos]);
showDownloadError();
}
requestedDownload = null;
}
};
}
private class RequestedDownload {
Resource.File file;
String folderName;
boolean downloadNew;
RequestedDownload(Resource.File file, String folderName, boolean downloadNew) {
this.file = file;
this.folderName = folderName;
this.downloadNew = downloadNew;
}
}
/* Checks if external storage is available for read and write */
private boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
log.e("File system: Writing not possible");
return false;
}
private void fileOpen(File url){
Uri uri = FileProvider.getUriForFile(contextInterface.get(), contextInterface.get().getApplicationContext().getPackageName() + ".my.provider", url);
Intent intent;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
intent = new Intent(Intent.ACTION_VIEW);
} else {
intent = new Intent();
}
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Check what kind of file you are trying to open, by comparing the url with extensions.
// When the if condition is matched, plugin sets the correct intent (mime) type,
// so Android knew what application to use to open the file
if (url.toString().contains(".doc") || url.toString().contains(".docx")) {
// Word document
intent.setDataAndType(uri, "application/msword");
} else if(url.toString().contains(".pdf")) {
// PDF file
intent.setDataAndType(uri, "application/pdf");
} else if(url.toString().contains(".ppt") || url.toString().contains(".pptx")) {
// Powerpoint file
intent.setDataAndType(uri, "application/vnd.ms-powerpoint");
} else if(url.toString().contains(".xls") || url.toString().contains(".xlsx")) {
// Excel file
intent.setDataAndType(uri, "application/vnd.ms-excel");
} else if(url.toString().contains(".zip") || url.toString().contains(".rar")) {
// ZIP file
intent.setDataAndType(uri, "application/zip");
} else if(url.toString().contains(".rtf")) {
// RTF file
intent.setDataAndType(uri, "application/rtf");
} else if(url.toString().contains(".wav") || url.toString().contains(".mp3")) {
// WAV audio file
intent.setDataAndType(uri, "audio/x-wav");
} else if(url.toString().contains(".gif")) {
// GIF file
intent.setDataAndType(uri, "image/gif");
} else if(url.toString().contains(".jpg") || url.toString().contains(".jpeg") || url.toString().contains(".png")) {
// JPG file
intent.setDataAndType(uri, "image/jpeg");
} else if(url.toString().contains(".txt")) {
// Text file
intent.setDataAndType(uri, "text/plain");
} else if(url.toString().contains(".3gp") || url.toString().contains(".mpg") || url.toString().contains(".mpeg") || url.toString().contains(".mpe") || url.toString().contains(".mp4") || url.toString().contains(".avi")) {
// Video files
intent.setDataAndType(uri, "video/*");
} else {
//if you want you can also define the intent type for any other file
//additionally use else clause below, to manage other unknown extensions
//in this case, Android will show all applications installed on the device
//so you can choose which application to use
intent.setDataAndType(uri, "*/*");
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
contextInterface.get().startActivity(intent);
}
@FunctionalInterface
public interface ContextInterface {
@Nullable Context get();
}
@FunctionalInterface
public interface ActivityInterface {
@Nullable MainActivity get();
}
public interface OnDownloadRequestInterface {
void request(String title, String url);
}
}

View File

@@ -8,7 +8,7 @@ import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
public class AsyncQueue {
private final HashMap<String, LinkedList<AsyncQueueCallback>> mQueues = new HashMap<>();
private final HashMap<String, Boolean> mRunnings = new HashMap<>();
private final HashMap<String, Boolean> mIsRunning = new HashMap<>();
public void add(String hash, AsyncQueueCallback callback) {
if (isRunning(hash))
@@ -46,11 +46,11 @@ public class AsyncQueue {
}
private boolean isRunning(String hash) {
return mRunnings.containsKey(hash) && mRunnings.get(hash);
return mIsRunning.containsKey(hash) && mIsRunning.get(hash);
}
private void setRunning(String hash, boolean value) {
mRunnings.put(hash, value);
mIsRunning.put(hash, value);
}
private LinkedList<AsyncQueueCallback> getQueue(String hash) {

View File

@@ -7,7 +7,7 @@ public class ColorRGB implements Color {
private final int mGreen;
private final int mBlue;
public ColorRGB(int red, int green, int blue) {
private ColorRGB(int red, int green, int blue) {
this.mRed = red;
this.mGreen = green;
this.mBlue = blue;

View File

@@ -1,7 +1,7 @@
package de.sebse.fuplanner.tools;
import android.graphics.Paint;
import android.support.annotation.ColorInt;
import androidx.annotation.ColorInt;
public class ColorRes implements Color {
@ColorInt private final int mResId;

View File

@@ -11,21 +11,20 @@ public abstract class DateSortedList<T> extends ArrayList<T> {
public T getPast(int index) {
if (split < 0)
sort();
if (index >= split)
if (index >= this.sizePast())
throw new ArrayIndexOutOfBoundsException(String.format("Index %d out of bounds! Only %d past events found!", index, split));
if (reversed())
index = sizePast() - index - 1;
index += sizeUpcoming();
return this.get(index);
}
public T getUpcoming(int index) {
if (split < 0)
sort();
index += split;
if (index >= this.size())
if (index >= this.sizeUpcoming())
throw new ArrayIndexOutOfBoundsException(String.format("Index %d out of bounds! Only %d upcoming events found!", index-split, this.size()-split));
if (reversed())
index = sizeUpcoming() - index - 1;
if (!reversed())
index += this.sizePast();
return this.get(index);
}
@@ -34,7 +33,7 @@ public abstract class DateSortedList<T> extends ArrayList<T> {
if (split < 0)
sort();
if (reversed())
index = size() - index - 1;
index = size() - 1 - index;
return super.get(index);
}

View File

@@ -0,0 +1,26 @@
package de.sebse.fuplanner.tools;
import java.util.HashMap;
public class EventListener<T> {
private HashMap<String, EventFunction<T>> list = new HashMap<>();
public void add(String id,EventFunction<T> listener) {
list.put(id, listener);
}
public void remove(String id) {
list.remove(id);
}
public void emit(T value) {
for (EventFunction<T> listener : list.values()) {
listener.apply(value);
}
}
@FunctionalInterface
public interface EventFunction<T> {
void apply(T value);
}
}

View File

@@ -1,23 +1,30 @@
package de.sebse.fuplanner.tools;
import android.support.annotation.StringRes;
import androidx.annotation.StringRes;
import de.sebse.fuplanner.services.Canteen.CanteenBrowser;
import de.sebse.fuplanner.services.GoogleAuth.GoogleAuth;
import de.sebse.fuplanner.services.KVV.KVV;
public interface MainAcitivityListener {
public interface MainActivityListener {
void onTitleTextChange(String newTitle);
void onTitleTextChange(@StringRes int titleId);
void showToast(String message);
void showToast(@StringRes int msgStringRes);
KVV getKVV();
GoogleAuth getGoogleAuth();
void loginTokenInvalid(boolean doPrecheck);
void refreshFailed(boolean isFailed);
CanteenBrowser getCanteenBrowser();
@Deprecated
void onRefreshCompleted(boolean isFailed);
void addRequestPermissionsResultListener(RequestPermissionsResultListener listener, String id);
void removeRequestPermissionsResultListener(String id);
}

View File

@@ -0,0 +1,58 @@
package de.sebse.fuplanner.tools;
import java.util.LinkedList;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
public class NewAsyncQueue {
private final LinkedList<AsyncQueueCallback> mQueue = new LinkedList<>();
private boolean mIsRunning = false;
public void add(AsyncQueueCallback callback) {
if (isRunning())
getQueue().addLast(callback);
else {
setRunning(true);
callback.run();
}
}
public void next() {
AsyncQueueCallback callback = getQueue().pollFirst();
if (callback == null)
setRunning(false);
else
callback.run();
}
public interface AsyncQueueCallback {
void run();
}
public NetworkErrorCallback check(NetworkErrorCallback value) {
return error -> {
value.onError(error);
next();
};
}
public <T> NetworkCallback<T> check(NetworkCallback<T> value) {
return success -> {
value.onResponse(success);
next();
};
}
private boolean isRunning() {
return mIsRunning;
}
private void setRunning(boolean value) {
mIsRunning = value;
}
private LinkedList<AsyncQueueCallback> getQueue() {
return mQueue;
}
}

View File

@@ -1,8 +1,8 @@
package de.sebse.fuplanner.tools;
import android.content.Context;
import android.support.annotation.ArrayRes;
import android.support.v7.preference.PreferenceManager;
import androidx.annotation.ArrayRes;
import androidx.preference.PreferenceManager;
public class Preferences {
public static String getString(Context context, @ArrayRes int key) {

View File

@@ -9,6 +9,14 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Regex {
public static boolean has(@Language("Regexp") String regex, String match) {
try {
regex(regex, match, 0);
return true;
} catch (NoSuchFieldException e) {
return false;
}
}
public static String regex(@Language("Regexp") String regex, String match) throws NoSuchFieldException {
return regex(regex, match, 1);
}

View File

@@ -0,0 +1,5 @@
package de.sebse.fuplanner.tools;
public interface RequestPermissionsResultListener {
void callback(int requestCode, String[] permissions, int[] grantResults);
}

View File

@@ -1,7 +1,7 @@
package de.sebse.fuplanner.tools;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.Serializable;
import java.util.ArrayList;
@@ -25,6 +25,7 @@ public abstract class SortedList<T, I, F> implements Iterable<T>, Serializable {
protected abstract boolean hasIdentifier(T o1, I id);
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
protected abstract boolean hasFilter(T o1, F filter);
@Nullable

View File

@@ -0,0 +1,14 @@
package de.sebse.fuplanner.tools;
public class Triplet<T, U, V> {
public final T first;
public final U second;
public final V third;
public Triplet(T first, U second, V third) {
this.first = first;
this.second = second;
this.third = third;
}
}

View File

@@ -4,16 +4,14 @@ import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.annotation.Nullable;
import androidx.annotation.Nullable;
import android.text.format.DateFormat;
import com.google.android.gms.common.logging.Logger;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class DateUtils {
public class UtilsDate {
@Deprecated
public static String getModifiedDateTime(long modified) {
return getModifiedDateTime(null, modified);
@@ -48,6 +46,8 @@ public class DateUtils {
if (context != null && DateFormat.is24HourFormat(context))
skeleton = skeleton.replaceAll("h", "H");
if (context != null && !DateFormat.is24HourFormat(context))
skeleton = skeleton.replaceAll("H", "h");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
dateFormat = new SimpleDateFormat(getDateFormat(locale, skeleton));
} else {

View File

@@ -0,0 +1,17 @@
package de.sebse.fuplanner.tools;
import android.content.Context;
import android.content.res.Resources;
import android.util.DisplayMetrics;
public class UtilsUi {
public static float convertPixelsToDp(float px, Context context) {
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
return px / ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT);
}
public static float convertDpToPixels(Context context, float dp) {
return dp * context.getResources().getDisplayMetrics().density;
}
}

View File

@@ -10,10 +10,14 @@ public class Logger {
}
public Logger(Object object) {
this.tag = getClassName(object);
}
private static String getClassName(Object object) {
if (object instanceof String)
this.tag = (String) object;
return (String) object;
else
this.tag = object.getClass().getSimpleName();
return object.getClass().getSimpleName();
}
public void d(Object... msg) {

View File

@@ -1,24 +1,18 @@
package de.sebse.fuplanner.tools.network;
import com.android.volley.AuthFailureError;
import com.android.volley.Header;
import com.android.volley.Request;
import com.android.volley.toolbox.HttpResponse;
import com.android.volley.toolbox.HurlStack;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.StatusLine;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine;
import java.io.DataOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -28,12 +22,15 @@ import java.util.regex.Pattern;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
import androidx.annotation.VisibleForTesting;
/**
* Created by sebastian on 29.10.17.
*/
public class BetterHurlStack extends HurlStack {
private static final String HEADER_CONTENT_TYPE = "Content-Type";
private static final int HTTP_CONTINUE = 100;
private final UrlRewriter mUrlRewriter;
private final SSLSocketFactory mSslSocketFactory;
@@ -50,14 +47,14 @@ public class BetterHurlStack extends HurlStack {
/**
* @param urlRewriter Rewriter to use for request URLs
*/
BetterHurlStack(boolean followRedirects, UrlRewriter urlRewriter) {
private BetterHurlStack(boolean followRedirects, UrlRewriter urlRewriter) {
this(followRedirects, urlRewriter, null);
}
/**
* @param urlRewriter Rewriter to use for request URLs
* @param sslSocketFactory SSL factory to use for HTTPS connections
*/
BetterHurlStack(boolean followRedirects, UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) {
private BetterHurlStack(boolean followRedirects, UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) {
super(urlRewriter, sslSocketFactory);
mUrlRewriter = urlRewriter;
mSslSocketFactory = sslSocketFactory;
@@ -76,12 +73,12 @@ public class BetterHurlStack extends HurlStack {
}
@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
String url = request.getUrl();
HashMap<String, String> map = new HashMap<>();
map.putAll(request.getHeaders());
map.putAll(additionalHeaders);
map.putAll(request.getHeaders());
if (mUrlRewriter != null) {
String rewritten = mUrlRewriter.rewriteUrl(url);
if (rewritten == null) {
@@ -91,67 +88,85 @@ public class BetterHurlStack extends HurlStack {
}
URL parsedUrl = new URL(url);
HttpURLConnection connection = openConnection(parsedUrl, request);
for (String headerName : map.keySet()) {
connection.addRequestProperty(headerName, map.get(headerName));
boolean keepConnectionOpen = false;
try {
for (String headerName : map.keySet()) {
connection.setRequestProperty(headerName, map.get(headerName));
}
setConnectionParametersForRequest(connection, request);
// Initialize HttpResponse with data from the HttpURLConnection.
int responseCode = connection.getResponseCode();
if (responseCode == -1) {
// -1 is returned by getResponseCode() if the response code could not be retrieved.
// Signal to the caller that something was wrong with the connection.
throw new IOException("Could not retrieve response code from HttpUrlConnection.");
}
if (!hasResponseBody(request.getMethod(), responseCode)) {
return new HttpResponse(responseCode, convertHeaders(connection.getHeaderFields()));
}
// Need to keep the connection open until the stream is consumed by the caller. Wrap the
// stream such that close() will disconnect the connection.
keepConnectionOpen = true;
return new HttpResponse(
responseCode,
convertHeaders(connection.getHeaderFields()),
connection.getContentLength(),
new UrlConnectionInputStream(connection));
} finally {
if (!keepConnectionOpen) {
connection.disconnect();
}
}
setConnectionParametersForRequest(connection, request);
// Initialize HttpResponse with data from the HttpURLConnection.
ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
int responseCode = connection.getResponseCode();
if (responseCode == -1) {
// -1 is returned by getResponseCode() if the response code could not be retrieved.
// Signal to the caller that something was wrong with the connection.
throw new IOException("Could not retrieve response code from HttpUrlConnection.");
}
StatusLine responseStatus = new BasicStatusLine(protocolVersion,
connection.getResponseCode(), connection.getResponseMessage());
BasicHttpResponse response = new BasicHttpResponse(responseStatus);
response.setEntity(entityFromConnection(connection));
for (Map.Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
if (header.getKey() != null) {
Header h;
if (header.getKey().equals("Set-Cookie")) {
}
@VisibleForTesting
private static List<Header> convertHeaders(Map<String, List<String>> responseHeaders) {
List<Header> headerList = new ArrayList<>(responseHeaders.size());
for (Map.Entry<String, List<String>> entry : responseHeaders.entrySet()) {
// HttpUrlConnection includes the status line as a header with a null key; omit it here
// since it's not really a header and the rest of Volley assumes non-null keys.
if (entry.getKey() != null) {
if (entry.getKey().equals("Set-Cookie")) {
Pattern pattern = Pattern.compile("^([^=]+=[^;]+;)");
StringBuilder cookieValue = new StringBuilder();
for (String value: header.getValue()) {
for (String value : entry.getValue()) {
Matcher matcher = pattern.matcher(value);
if (matcher.find()) {
cookieValue.append(matcher.group(1));
}
}
h = new BasicHeader(header.getKey(), cookieValue.toString());
headerList.add(new Header(entry.getKey(), cookieValue.toString()));
} else {
h = new BasicHeader(header.getKey(), header.getValue().get(0));
headerList.add(new Header(entry.getKey(), entry.getValue().get(0)));
}
response.addHeader(h);
}
}
return response;
return headerList;
}
/**
* Initializes an {@link HttpEntity} from the given {@link HttpURLConnection}.
* @param connection A http connection
* @return an HttpEntity populated with data from <code>connection</code>.
* Checks if a response message contains a body.
*
* @see <a href="https://tools.ietf.org/html/rfc7230#section-3.3">RFC 7230 section 3.3</a>
* @param requestMethod request method
* @param responseCode response status code
* @return whether the response has a body
*/
private static HttpEntity entityFromConnection(HttpURLConnection connection) {
BasicHttpEntity entity = new BasicHttpEntity();
InputStream inputStream;
try {
inputStream = connection.getInputStream();
} catch (IOException ioe) {
inputStream = connection.getErrorStream();
}
entity.setContent(inputStream);
entity.setContentLength(connection.getContentLength());
entity.setContentEncoding(connection.getContentEncoding());
entity.setContentType(connection.getContentType());
return entity;
private static boolean hasResponseBody(int requestMethod, int responseCode) {
return requestMethod != Request.Method.HEAD
&& !(HTTP_CONTINUE <= responseCode && responseCode < HttpURLConnection.HTTP_OK)
&& responseCode != HttpURLConnection.HTTP_NO_CONTENT
&& responseCode != HttpURLConnection.HTTP_NOT_MODIFIED;
}
// NOTE: Any request headers added here (via setRequestProperty or addRequestProperty) should be
// checked against the existing properties in the connection and not overridden if already set.
@SuppressWarnings("deprecation")
/* package */ static void setConnectionParametersForRequest(HttpURLConnection connection,
Request<?> request) throws IOException, AuthFailureError {
/* package */ private static void setConnectionParametersForRequest(
HttpURLConnection connection, Request<?> request) throws IOException, AuthFailureError {
switch (request.getMethod()) {
case Request.Method.DEPRECATED_GET_OR_POST:
// This is the deprecated way that needs to be handled for backwards compatibility.
@@ -159,16 +174,8 @@ public class BetterHurlStack extends HurlStack {
// GET. Otherwise, it is assumed that the request is a POST.
byte[] postBody = request.getPostBody();
if (postBody != null) {
// Prepare output. There is no need to set Content-Length explicitly,
// since this is handled by HttpURLConnection using the size of the prepared
// output stream.
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.addRequestProperty(HEADER_CONTENT_TYPE,
request.getPostBodyContentType());
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.write(postBody);
out.close();
addBody(connection, request, postBody);
}
break;
case Request.Method.GET:
@@ -209,14 +216,26 @@ public class BetterHurlStack extends HurlStack {
throws IOException, AuthFailureError {
byte[] body = request.getBody();
if (body != null) {
connection.setDoOutput(true);
connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType());
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.write(body);
out.close();
addBody(connection, request, body);
}
}
private static void addBody(HttpURLConnection connection, Request<?> request, byte[] body)
throws IOException {
// Prepare output. There is no need to set Content-Length explicitly,
// since this is handled by HttpURLConnection using the size of the prepared
// output stream.
connection.setDoOutput(true);
// Set the content-type unless it was already set (by Request#getHeaders).
if (!connection.getRequestProperties().containsKey(HEADER_CONTENT_TYPE)) {
connection.setRequestProperty(
HEADER_CONTENT_TYPE, request.getBodyContentType());
}
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.write(body);
out.close();
}
private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
HttpURLConnection connection = createConnection(url);
int timeoutMs = request.getTimeoutMs();
@@ -230,4 +249,40 @@ public class BetterHurlStack extends HurlStack {
}
return connection;
}
/**
* Wrapper for a {@link HttpURLConnection}'s InputStream which disconnects the connection on
* stream close.
*/
static class UrlConnectionInputStream extends FilterInputStream {
private final HttpURLConnection mConnection;
UrlConnectionInputStream(HttpURLConnection connection) {
super(inputStreamFromConnection(connection));
mConnection = connection;
}
@Override
public void close() throws IOException {
super.close();
mConnection.disconnect();
}
}
/**
* Initializes an {@link InputStream} from the given {@link HttpURLConnection}.
*
* @param connection A http url connection
* @return an HttpEntity populated with data from <code>connection</code>.
*/
private static InputStream inputStreamFromConnection(HttpURLConnection connection) {
InputStream inputStream;
try {
inputStream = connection.getInputStream();
} catch (IOException ioe) {
inputStream = connection.getErrorStream();
}
return inputStream;
}
}

View File

@@ -1,7 +1,6 @@
package de.sebse.fuplanner.tools.network;
import android.content.Context;
import android.support.annotation.Nullable;
import com.android.volley.AuthFailureError;
import com.android.volley.NetworkResponse;
@@ -17,6 +16,8 @@ import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import androidx.annotation.Nullable;
import de.sebse.fuplanner.tools.EventListener;
import de.sebse.fuplanner.tools.logging.Logger;
/**
@@ -25,14 +26,30 @@ import de.sebse.fuplanner.tools.logging.Logger;
public class HTTPService {
private final RequestQueue requestQueue;
private final Context mContext;
protected Logger log = new Logger(this);
private EventListener<VolleyError> errorResponseListener = new EventListener<>();
private EventListener<Result> successResponseListener = new EventListener<>();
protected HTTPService(Context context) {
this(context, false);
this.mContext = context;
requestQueue = Volley.newRequestQueue(context, new BetterHurlStack(false));
}
protected HTTPService(Context context, boolean followRedirects) {
requestQueue = Volley.newRequestQueue(context, new BetterHurlStack(followRedirects));
public void addErrorListener(String id, EventListener.EventFunction<VolleyError> listener) {
errorResponseListener.add(id, listener);
}
public void removeErrorListener(String id) {
errorResponseListener.remove(id);
}
public void addSuccessListener(String id, EventListener.EventFunction<Result> listener) {
successResponseListener.add(id, listener);
}
public void removeSuccessListener(String id) {
successResponseListener.remove(id);
}
protected void get(String url, @Nullable final HashMap<String, String> cookies, Response.Listener<Result> response, Response.ErrorListener error) {
@@ -40,24 +57,35 @@ public class HTTPService {
@Override
public void deliverError(VolleyError error) {
if (error == null) {
super.deliverError(new VolleyError(new NetworkResponse(500, null, null, true, 0)));
deliver(new VolleyError(new NetworkResponse(500, null, true, 0, null)));
} else if (error.networkResponse == null) {
int statusCode;
if (error instanceof TimeoutError)
statusCode = 408;
else
statusCode = 500;
super.deliverError(new VolleyError(new NetworkResponse(statusCode, null, null, true, error.getNetworkTimeMs())));
deliver(new VolleyError(new NetworkResponse(statusCode, null, true, error.getNetworkTimeMs(), null)));
} else {
final int status = error.networkResponse.statusCode;
if (status == 302) {
super.deliverResponse(new Result(null, error.networkResponse.headers));
deliverResponse(new Result(null, error.networkResponse.headers));
} else {
super.deliverError(error);
deliver(error);
}
}
}
@Override
protected void deliverResponse(Result response) {
successResponseListener.emit(response);
super.deliverResponse(response);
}
private void deliver(VolleyError error) {
errorResponseListener.emit(error);
super.deliverError(error);
}
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> params = super.getHeaders();
if (cookies != null) {
@@ -96,7 +124,6 @@ public class HTTPService {
sb.append('&');
}
try {
//Log.e("Superissimo", e.getKey()+"|||"+e.getValue());
sb.append(URLEncoder.encode(e.getKey(), "UTF-8")).append('=').append(URLEncoder.encode(e.getValue(), "UTF-8"));
} catch (UnsupportedEncodingException ignored) {
}
@@ -112,24 +139,35 @@ public class HTTPService {
@Override
public void deliverError(VolleyError error) {
if (error == null) {
super.deliverError(new VolleyError(new NetworkResponse(500, null, null, true, 0)));
deliver(new VolleyError(new NetworkResponse(500, null, true, 0, null)));
} else if (error.networkResponse == null) {
int statusCode;
if (error instanceof TimeoutError)
statusCode = 408;
else
statusCode = 500;
super.deliverError(new VolleyError(new NetworkResponse(statusCode, null, null, true, error.getNetworkTimeMs())));
deliver(new VolleyError(new NetworkResponse(statusCode, null, true, error.getNetworkTimeMs(), null)));
} else {
final int status = error.networkResponse.statusCode;
if (status == 302) {
super.deliverResponse(new Result(null, error.networkResponse.headers));
deliverResponse(new Result(null, error.networkResponse.headers));
} else {
super.deliverError(error);
deliver(error);
}
}
}
private void deliver(VolleyError error) {
errorResponseListener.emit(error);
super.deliverError(error);
}
@Override
protected void deliverResponse(Result response) {
successResponseListener.emit(response);
super.deliverResponse(response);
}
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> params = super.getHeaders();
if (cookies != null) {
@@ -149,4 +187,8 @@ public class HTTPService {
};
requestQueue.add(request);
}
protected Context getContext() {
return mContext;
}
}

View File

@@ -6,8 +6,6 @@ import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.toolbox.HttpHeaderParser;
import java.io.UnsupportedEncodingException;
/**
* Created by sebastian on 24.10.17.
*/
@@ -28,13 +26,7 @@ class HttpRequest extends Request<Result> {
@Override
protected Response<Result> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}
Result result = new Result(parsed, response.headers);
Result result = new Result(response.data, response.headers);
return Response.success(result, HttpHeaderParser.parseCacheHeaders(response));
}

View File

@@ -1,6 +1,6 @@
package de.sebse.fuplanner.tools.network;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
public interface NetworkCallback<T> {
void onResponse(@NonNull T success);

View File

@@ -23,7 +23,7 @@ public class NetworkError {
return httpStatus;
}
public String getMessage() {
private String getMessage() {
return message;
}

View File

@@ -1,21 +1,38 @@
package de.sebse.fuplanner.tools.network;
import com.android.volley.toolbox.HttpHeaderParser;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import androidx.annotation.Nullable;
/**
* Created by sebastian on 24.10.17.
*/
public class Result {
private final String parsed;
@Nullable private final byte[] body;
private final Map<String, String> headers;
public Result(String parsed, Map<String, String> headers) {
this.parsed = parsed;
Result(@Nullable byte[] body, Map<String, String> headers) {
this.body = body;
this.headers = headers;
}
@Nullable
public String getParsed() {
return parsed;
if (this.body == null)
return null;
try {
return new String(this.body, HttpHeaderParser.parseCharset(headers));
} catch (UnsupportedEncodingException e) {
return new String(this.body);
}
}
@Nullable
public byte[] getBytes() {
return body;
}
public Map<String, String> getHeaders() {

View File

@@ -0,0 +1,32 @@
package de.sebse.fuplanner.tools.ui;
import android.view.View;
import android.widget.TextView;
import com.cunoraz.tagview.TagView;
import de.sebse.fuplanner.R;
public class AnnouncementViewHolder extends ExpandableCardViewHolder {
public final TextView mTitle;
public final TextView mSubTitle;
public final TextView mNotes;
public final TagView mTagGroup;
public AnnouncementViewHolder(View view) {
super(view);
View outerView = getOuterView();
View innerView = getInnerView();
mTitle = outerView.findViewById(R.id.title);
mSubTitle = outerView.findViewById(R.id.sub_title);
mNotes = innerView.findViewById(R.id.notes);
mTagGroup = innerView.findViewById(R.id.tag_group);
}
@Override
public String toString() {
return super.toString() + " '" + mTitle.getText() + "' '" + mSubTitle.getText() + "'";
}
}

View File

@@ -1,8 +1,9 @@
package de.sebse.fuplanner.tools.ui;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
public class CustomViewHolder extends RecyclerView.ViewHolder {
public final View mView;

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