278 Commits

Author SHA1 Message Date
Caesar2011
6325f312ea unfinished code 2019-01-07 13:25:24 +01:00
Caesar2011
e0ed23dec5 Still unfinished JSON converter 2019-01-04 15:02:07 +01:00
Caesar2011
bb85911324 Unfinished, unusable code 2019-01-04 00:06:11 +01:00
Sebastian Seedorf
1ed6522ac4 Listener optimization 2019-01-03 12:55:23 +01:00
Sebastian Seedorf
f646dd485d Removed unused imports 2019-01-03 12:44:34 +01:00
Sebastian Seedorf
73f9485394 Removed unused Offline Mode 2019-01-03 11:59:24 +01:00
Sebastian Seedorf
22f7cf3e96 renamed packages 2019-01-03 10:01:47 +01:00
Sebastian Seedorf
4db38ba4fb GoogleAuth removed 2019-01-03 09:55:05 +01:00
Caesar2011
6306cc9c77 Implemented Sync Adapter 2019-01-03 01:02:11 +01:00
Caesar2011
8160ef12b1 Login Bugs removed 2018-12-19 15:46:12 +01:00
Caesar2011
4d22613672 Login Improved; Still Bug when re-login 2018-12-19 01:08:39 +01:00
Caesar2011
fd18e4b61a Login Finished 2018-12-18 23:54:21 +01:00
Caesar2011
cde5fc3582 Login Testing 2018-12-17 16:14:50 +01:00
Caesar2011
c807dda73c Activity Active Bugfix 2018-12-06 13:32:39 +01:00
Joshua
797e848900 Login Authenticator Added 2018-12-03 18:32:06 +01:00
Caesar2011
2dbfd0141a Minor Time and Save Bugfix 2018-11-27 18:11:42 +01:00
Caesar2011
9b39563474 More time to leave 2018-11-27 16:32:34 +01:00
Caesar2011
8791d65d96 Changed Canteen Listener 2018-11-21 22:56:19 +01:00
Caesar2011
6bd99c59a8 Version 16 2018-11-21 17:54:46 +01:00
Caesar2011
6b117997c3 Resized Resource strings 2018-11-21 17:54:20 +01:00
Caesar2011
4c012ed2ba Fixed NullPointerException 2018-11-21 17:53:49 +01:00
Caesar2011
5209491287 Version 15 2018-11-21 13:41:53 +01:00
Caesar2011
4812336cc7 Schedule Left-Right-Bugfix 2018-11-21 13:32:11 +01:00
Caesar2011
4fe6dee4c8 Version 14 2018-11-21 13:14:42 +01:00
Caesar2011
2aad0f19a3 Updated News 2018-11-21 12:26:19 +01:00
Caesar2011
a79f59c66c Improved Schedule Double Tap Listener 2018-11-18 21:51:20 +01:00
Caesar2011
2d160d3b38 Design Update 3 2018-11-18 20:49:28 +01:00
Caesar2011
6acea97458 Merge remote-tracking branch 'origin/master' 2018-11-18 20:34:58 +01:00
Caesar2011
c6f032f92e Design Update 2018-11-18 20:34:35 +01:00
Caesar2011
544162bb47 Design Update 2018-11-18 20:30:23 +01:00
Caesar2011
d5e08ac3c3 Version 13 2018-11-18 19:30:43 +01:00
Caesar2011
671c9bd86a News Show Notification on Sidebar 2018-11-18 19:23:22 +01:00
Caesar2011
35141bc1ec Back Button Implemented! Finally! Finally! Finally! 2018-11-18 16:49:02 +01:00
Caesar2011
d62256f1db Implemented Shortcuts 2018-11-18 14:55:03 +01:00
Caesar2011
0adeca7367 Better NavigationDrawer Item Display 2018-11-18 14:08:06 +01:00
Caesar2011
0b32f8e9f8 News Translatable 2018-11-18 13:56:21 +01:00
Caesar2011
4d9b2200d3 News Data 2018-11-18 12:31:12 +01:00
Caesar2011
50d07193a0 Implemented News with Demo Entries 2018-11-18 11:12:13 +01:00
Caesar2011
8e3360549f DateSortedList Sorting bug fix 2018-11-17 11:52:08 +01:00
Caesar2011
7679135af1 Added News Fragment 2018-11-17 00:40:16 +01:00
Caesar2011
6523d82d6b Code cleanup 2018-11-17 00:08:08 +01:00
Caesar2011
7a7e56bdf5 Resource list icon color adjusted 2018-11-16 23:24:08 +01:00
Caesar2011
14b38f22a3 Fixed Re-login Bug 2018-11-16 23:23:16 +01:00
Caesar2011
1d90b3e9da Merge remote-tracking branch 'origin/master' 2018-11-16 22:13:34 +01:00
Caesar2011
b6deff7b99 Version 12 2018-11-16 22:13:15 +01:00
Caesar2011
dd7e9a910c Removed Debug Resource 2018-11-16 21:51:15 +01:00
Caesar2011
3aa07e0a6d Removed Slashes 2018-11-15 18:36:56 +01:00
Caesar2011
9a1892db0a Update All Parts in Module; Removed Logs 2018-11-15 18:31:37 +01:00
Caesar2011
5ba6c899be Event List Design updated 2018-11-15 18:06:56 +01:00
Caesar2011
547a62d07b Version 11 2018-11-14 01:46:20 +01:00
Caesar2011
d9e2911554 Caption Corrected 2018-11-14 01:46:07 +01:00
Caesar2011
2e96d205d6 Merge branch 'newkvv'
# Conflicts:
#	app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailAnnounceFragment.java
#	app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailAssignmentFragment.java
#	app/src/main/java/de/sebse/fuplanner/services/KVV/KVVModuleList.java
2018-11-14 01:27:57 +01:00
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
17c093c040 Version 10 - Fixed Download 2018-11-08 21:59:23 +01:00
Caesar2011
a1ce5c644a Download not working 2018-11-08 21:52:47 +01:00
Caesar2011
2d1d8e1e8b Version 10 2018-11-08 20:59:35 +01:00
Caesar2011
518ae756b1 Re-added new download opener 2018-11-08 20:39:07 +01:00
Joshua
959733a3c9 bug fix beim laden der Module 2018-11-08 18:31:15 +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
237 changed files with 8229 additions and 3329 deletions

5
.gitignore vendored
View File

@@ -1,9 +1,10 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
/.idea/*
/.idea - PC/*
.DS_Store
/build
/captures
.externalNativeBuild
app/release/*

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 16
versionName "1.3.5"
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

@@ -2,25 +2,86 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.sebse.fuplanner">
<uses-permission
android:name="android.permission.AUTHENTICATE_ACCOUNTS"
android:maxSdkVersion="22" />
<uses-permission
android:name="android.permission.GET_ACCOUNTS"
android:maxSdkVersion="22" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.MANAGE_ACCOUNTS"
android:maxSdkVersion="22" />
<uses-permission
android:name="android.permission.USE_CREDENTIALS"
android:maxSdkVersion="22" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- To auto-complete the email text field in the login form with the user's emails -->
<uses-permission android:name="android.permission.READ_PROFILE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<application
android:allowBackup="true"
android:fullBackupContent="@xml/backup_descriptor"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/FUTheme"
android:fullBackupContent="@xml/backup_descriptor">
android:theme="@style/FUTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.MAIN" />
<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>
<provider
android:authorities="de.sebse.fuplanner.contentprovider.kvv.modules"
android:name="de.sebse.fuplanner.services.kvv.sync.KVVContentProvider"
android:exported="false"
android:syncable="true">
</provider>
<activity
android:name=".services.fulogin.FUAuthenticatorActivity"
android:label="@string/title_activity_fuauthenticator" />
<service android:name=".services.fulogin.FUAuthenticatorService">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
<service
android:name=".services.kvv.sync.KVVSyncService"
android:label="@string/kvv_sync"
android:exported="false">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter_kvv" />
</service>
</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

@@ -1,46 +1,62 @@
package de.sebse.fuplanner;
import android.accounts.AccountManager;
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 org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
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;
import de.sebse.fuplanner.fragments.NewsFragment;
import de.sebse.fuplanner.fragments.PrefsFragment;
import de.sebse.fuplanner.fragments.ScheduleFragment;
import de.sebse.fuplanner.fragments.StartupFragment;
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.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.services.canteen.CanteenBrowser;
import de.sebse.fuplanner.services.canteen.types.Canteen;
import de.sebse.fuplanner.services.canteen.types.CanteenListener;
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.services.news.NewsManager;
import de.sebse.fuplanner.services.fulogin.AccountGeneral;
import de.sebse.fuplanner.tools.CustomAccountManager;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.NewAsyncQueue;
import de.sebse.fuplanner.tools.Preferences;
import de.sebse.fuplanner.tools.Regex;
import de.sebse.fuplanner.tools.RequestPermissionsResultListener;
import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.types.News;
public class MainActivity extends AppCompatActivity
implements MainAcitivityListener,
implements MainActivityListener, KVVListener, CanteenListener,
NavigationView.OnNavigationItemSelectedListener,
LoginFragment.OnLoginFragmentInteractionListener,
ModulesFragment.OnModulesFragmentInteractionListener,
CanteensFragment.OnCanteensFragmentInteractionListener {
@@ -48,34 +64,42 @@ public class MainActivity extends AppCompatActivity
private static final int FRAGMENT_STARTUP = 0;
private static final int FRAGMENT_MODULES = 1;
private static final int FRAGMENT_MODULES_DETAILS = 2;
private static final int FRAGMENT_LOGIN = 3;
private static final int FRAGMENT_SCHEDULE = 4;
private static final int FRAGMENT_CANTEENS = 5;
private static final int FRAGMENT_CANTEENS_DETAILS = 6;
private static final int FRAGMENT_PREFERENCES = 7;
private static final int FRAGMENT_NEWS = 8;
private static final String ARG_FRAGMENT_PAGE = "fragment_page";
private static final String ARG_FRAGMENT_STATUS = "fragment_status";
private static final int DOUBLE_CLICK_TO_EXIT_MILLIS = 2000;
private FragmentManager mFragmentManager;
private GoogleAuth mGoogleAuth;
private KVV mKVV;
private NewsManager mNewsManager;
private CanteenBrowser mCanteenBrowser;
private final Logger log = new Logger(this);
private NavigationView mNavigationView;
private int fragmentPage = FRAGMENT_NONE;
private String fragmentData = "";
private CanteenBrowser mCanteenBrowser;
private boolean mOfflineMode = false;
private int mFragmentPage = FRAGMENT_NONE;
@NotNull
private String mFragmentData = "";
private final HashMap<String, RequestPermissionsResultListener> permissionListeners = new HashMap<>();
private final NewAsyncQueue mQueue = new NewAsyncQueue();
private long mDoubleBackToExitPressedOnce = 0;
private CustomAccountManager mAccountManager;
private boolean isPaused = false;
private boolean isLoggedInBeforePause = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int newFragmentPage = FRAGMENT_NONE;
String newFragmentData = "";
mAccountManager = new CustomAccountManager(AccountManager.get(this), () -> MainActivity.this);
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,41 +116,91 @@ 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();
//if (mAccountManager.getAccountsByType(AccountGeneral.ACCOUNT_TYPE).length == 0) {
if (!mAccountManager.hasAccounts(AccountGeneral.ACCOUNT_TYPE)) {
desiredPage = getDefaultFragmentAfterLogout();
desiredData = "";
mAccountManager.getTokenByType(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_KVV, null);
updateNavigation();
changeFragment(desiredPage, desiredData);
} else {
if (loginToken != null)
toLoginState(loginToken, getDefaultFragmentAfterLogin(), "");
else
checkAndDoLogin();
updateNavigation();
changeFragment(FRAGMENT_STARTUP);
int targetPage = desiredPage;
String targetData = desiredData;
getKVV().account().restoreOnlineLogin(isRestored -> {
updateNavigation();
if (isRestored)
changeFragment(targetPage, targetData);
else
changeFragment(getDefaultFragmentAfterLogout());
});
}
}
/*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);*/
@Override
protected void onPause() {
super.onPause();
isPaused = true;
isLoggedInBeforePause = getKVV().account().isLoggedIn();
}
@Override
protected void onResume() {
super.onResume();
if (isPaused) {
getKVV().account().restoreOnlineLogin(isRestored -> {
updateNavigation();
if (isRestored && !isLoggedInBeforePause)
changeFragment(getDefaultFragmentAfterLogin());
else if (!isRestored && isLoggedInBeforePause) {
getKVV().account().logout(false);
changeFragment(getDefaultFragmentAfterLogout());
}
});
}
isPaused = false;
}
@Override
public void onBackPressed() {
if (mDoubleBackToExitPressedOnce + DOUBLE_CLICK_TO_EXIT_MILLIS > System.currentTimeMillis()) {
super.onBackPressed();
return;
}
DrawerLayout drawer = findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
if (mFragmentPage == FRAGMENT_MODULES_DETAILS) {
ModDetailFragment fragment = (ModDetailFragment) mFragmentManager.findFragmentByTag(String.valueOf(FRAGMENT_MODULES_DETAILS));
if (fragment != null && fragment.isVisible() && Regex.has("\\.[1-9][0-9]*", fragment.getData())) {
fragment.gotoFragmentPart(0, -1);
} else {
changeFragment(FRAGMENT_MODULES);
}
} else if (mFragmentPage == FRAGMENT_CANTEENS_DETAILS) {
DaySwitcherFragment fragment = (DaySwitcherFragment) mFragmentManager.findFragmentByTag(String.valueOf(FRAGMENT_CANTEENS_DETAILS));
if (fragment != null && fragment.isVisible() && Regex.has("\\.[1-9][0-9]*", fragment.getData())) {
fragment.gotoFragmentPart(0);
} else {
changeFragment(FRAGMENT_CANTEENS);
}
} else if (getKVV().account().isLoggedIn() && mFragmentPage != getDefaultFragmentAfterLogin()) {
changeFragment(getDefaultFragmentAfterLogin());
} else {
mDoubleBackToExitPressedOnce = System.currentTimeMillis();
showToast(R.string.back_to_exit);
//getTokenForAccountCreateIfNeeded(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_KVV);
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
if (fragmentPage == FRAGMENT_SCHEDULE) {
if (mFragmentPage == FRAGMENT_SCHEDULE) {
getMenuInflater().inflate(R.menu.options_schedule, menu);
return true;
}
@@ -162,7 +236,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,9 +243,12 @@ 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_news:
changeFragment(FRAGMENT_NEWS);
break;
case R.id.nav_settings:
changeFragment(FRAGMENT_PREFERENCES);
break;
@@ -184,16 +260,11 @@ public class MainActivity extends AppCompatActivity
startActivity(sendIntent);
break;
case R.id.nav_logout:
this.getKVV().logout();
this.getGoogleAuth().getLoginState(credentials -> {
if (credentials != null) {
this.getGoogleAuth().deleteLoginState(credentials.getUsername(), credentials.getPassword());
}
this.toLogoutState();
});
getKVV().account().logout(true);
getKVV().modules().list().delete();
break;
}
}
DrawerLayout drawer = findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
@@ -206,33 +277,43 @@ public class MainActivity extends AppCompatActivity
return true;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
this.getGoogleAuth().onActivityResult(requestCode, resultCode, data);
}
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt(ARG_FRAGMENT_PAGE, fragmentPage);
savedInstanceState.putString(ARG_FRAGMENT_STATUS, fragmentData);
if (mFragmentPage != FRAGMENT_STARTUP && mFragmentPage != FRAGMENT_NONE) {
Fragment fragment = mFragmentManager.findFragmentByTag(String.valueOf(mFragmentPage));
savedInstanceState.putInt(ARG_FRAGMENT_PAGE, mFragmentPage);
if (fragment instanceof ModDetailFragment) {
savedInstanceState.putString(ARG_FRAGMENT_STATUS, ((ModDetailFragment) fragment).getData());
} else if (fragment instanceof DaySwitcherFragment) {
savedInstanceState.putString(ARG_FRAGMENT_STATUS, ((DaySwitcherFragment) fragment).getData());
} else {
savedInstanceState.putString(ARG_FRAGMENT_STATUS, mFragmentData);
}
}
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);
}
}
/* --------------------------------------------*/
/* --------------------------------------------*/
/* --------------------------------------------*/
public GoogleAuth getGoogleAuth() {
if (this.mGoogleAuth == null) {
this.mGoogleAuth = new GoogleAuth(this);
public NewsManager getNewsManager() {
if (this.mNewsManager == null) {
this.mNewsManager = new NewsManager(this);
}
return this.mGoogleAuth;
return this.mNewsManager;
}
public KVV getKVV() {
if (this.mKVV == null) {
this.mKVV = new KVV(this);
this.mKVV = new KVV(this, this);
}
return this.mKVV;
}
@@ -248,41 +329,25 @@ public class MainActivity extends AppCompatActivity
return FRAGMENT_MODULES;
}
private int getDefaultFragmentAfterLogout() {
return FRAGMENT_CANTEENS;
}
private void toLogoutState() {
changeFragment(FRAGMENT_LOGIN);
setOfflineBanner(true);
setRefreshFailedBanner(false);
updateNavigation();
changeFragment(getDefaultFragmentAfterLogout());
mAccountManager.getTokenByType(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_KVV, null);
}
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) {
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,26 +355,38 @@ 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_LOGIN:
fragment = LoginFragment.newInstance();
case FRAGMENT_MODULES:
fragment = ModulesFragment.newInstance();
break;
/*case FRAGMENT_LOGIN:
fragment = LoginFragment.newInstance();
break;*/
case FRAGMENT_SCHEDULE:
fragment = ScheduleFragment.newInstance();
break;
case FRAGMENT_CANTEENS_DETAILS:
fragment = DaySwitcherFragment.newInstance(newData);
break;
case FRAGMENT_CANTEENS:
fragment = CanteensFragment.newInstance();
break;
case FRAGMENT_CANTEENS_DETAILS:
fragment = DaySwitcherFragment.newInstance(Integer.parseInt(newData));
case FRAGMENT_NEWS:
Preferences.setLong(this, R.string.pref_last_visited_news, System.currentTimeMillis());
updateNavigation();
fragment = NewsFragment.newInstance();
break;
case FRAGMENT_PREFERENCES:
fragment = PrefsFragment.newInstance();
@@ -324,39 +401,103 @@ 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.mFragmentPage = newFragment;
this.mFragmentData = newData;
invalidateOptionsMenu();
}
private void setRefreshFailedBanner(boolean refreshFailed) {
View viewNoConnection = findViewById(R.id.no_connection_msg);
if (refreshFailed)
viewNoConnection.setVisibility(View.VISIBLE);
else
viewNoConnection.setVisibility(View.GONE);
}
private void setNavigationSelection() {
MenuItem item = null;
switch (mFragmentPage) {
case FRAGMENT_MODULES_DETAILS:
getKVV().modules().list().find(mFragmentData, success -> {
int size = mNavigationView.getMenu().size();
//noinspection ConstantConditions
String title = success == null ? null : success.title;
for (int k = 0; k < size; k++) {
MenuItem menuItem = mNavigationView.getMenu().getItem(k);
if (menuItem.getTitle().equals(title)) {
menuItem.setChecked(true);
break;
}
}
}, log::e);
return;
case FRAGMENT_MODULES:
item = mNavigationView.getMenu().findItem(R.id.nav_modules);
break;
case FRAGMENT_SCHEDULE:
item = mNavigationView.getMenu().findItem(R.id.nav_schedule);
break;
case FRAGMENT_CANTEENS_DETAILS:
getCanteenBrowser().getCanteens(success -> {
int size = mNavigationView.getMenu().size();
Canteen canteen = success.getCanteen(Integer.parseInt(mFragmentData));
//noinspection ConstantConditions
String title = canteen == null ? null : canteen.getName();
for (int k = 0; k < size; k++) {
MenuItem menuItem = mNavigationView.getMenu().getItem(k);
if (menuItem.getTitle().equals(title)) {
menuItem.setChecked(true);
break;
}
}
}, log::e);
return;
case FRAGMENT_CANTEENS:
item = mNavigationView.getMenu().findItem(R.id.nav_canteens);
break;
case FRAGMENT_NEWS:
item = mNavigationView.getMenu().findItem(R.id.nav_news);
break;
case FRAGMENT_PREFERENCES:
item = mNavigationView.getMenu().findItem(R.id.nav_settings);
break;
default: // FRAGMENT_STARTUP / FRAGMENT_LOGIN
break;
}
if (item != null)
item.setChecked(true);
}
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);
mAccountManager.getTokenByType(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_KVV, null);
});
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 ? 3 : 2;
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 +508,12 @@ public class MainActivity extends AppCompatActivity
});
i++;
}
}, log::e);
if (++count[0] == MAX_COUNT) done.run();
}, error -> {
if (++count[0] == MAX_COUNT) done.run();
log.e(error);
});
}
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 +524,46 @@ public class MainActivity extends AppCompatActivity
});
i++;
}
}, log::e);
if (++count[0] == MAX_COUNT) done.run();
}, error -> {
if (++count[0] == MAX_COUNT) done.run();
log.e(error);
});
getNewsManager().recv(success -> {
long lastVisited = Preferences.getLong(this, R.string.pref_last_visited_news);
int i = 0;
for (News news: success) {
if (news.getDate() > lastVisited) i++;
}
if (i > 0) {
MenuItem menuItem = mNavigationView.getMenu().findItem(R.id.nav_news);
menuItem.setIcon(R.drawable.ic_sms_failed);
View view = View.inflate(this, R.layout.action_icon_number, null);
TextView v = view.findViewById(R.id.number);
v.setText(String.format(Locale.getDefault(), "%d", i));
menuItem.setActionView(view);
}
if (++count[0] == MAX_COUNT) done.run();
}, error -> {
if (++count[0] == MAX_COUNT) done.run();
log.e(error);
});
}
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 +572,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);
setNavigationSelection();
}
@Override
public void onCanteensFragmentInteraction(final int itemID) {
changeFragment(FRAGMENT_CANTEENS_DETAILS, String.valueOf(itemID));
setNavigationSelection();
}
@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 onCanteenRefreshCompleted(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 onLogin(LoginToken token) {
toLoginState(token.getFullName(), token.getEmail(), getDefaultFragmentAfterLogin());
}
@Override
public void onLogout() {
toLogoutState();
}
@Override
public void onModuleListChange() {
updateNavigation();
}
@Override
public void onKVVNetworkResponse(NetworkResponse error) {
setRefreshFailedBanner(error != null);
}
@Override
public CustomAccountManager getAccountManager() {
return mAccountManager;
}
}

View File

@@ -1,20 +1,19 @@
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.services.canteen.types.Canteen;
import de.sebse.fuplanner.services.canteen.types.Canteens;
import de.sebse.fuplanner.tools.ui.ItemViewHolder;
/**
* {@link RecyclerView.Adapter} that can display a {@link Modules.Module} and makes a call to the
* {@link RecyclerView.Adapter} that can display a {@link Canteen} and makes a call to the
* specified {@link OnCanteensFragmentInteractionListener}.
*/
class CanteensAdapter extends RecyclerView.Adapter<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.services.canteen.CanteenBrowser;
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
@@ -47,7 +48,7 @@ public class CanteensFragment extends Fragment {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_modules_list, container, false);
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);
@@ -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

@@ -1,148 +0,0 @@
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 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.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);
public LoginFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @return A new instance of fragment LoginFragment.
*/
public static LoginFragment newInstance() {
LoginFragment fragment = new LoginFragment();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
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();
}
View btn_login = v.findViewById(R.id.btn_login);
btn_login.setOnClickListener(view -> {
final ProgressDialog progressDialog = new ProgressDialog(LoginFragment.this.getContext(),
R.style.FUTheme_Dialog);
progressDialog.setIndeterminate(true);
progressDialog.setMessage("Authenticating...");
progressDialog.show();
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();
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);
});
}
}
});
return v;
}
@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");
}
@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);
}
}

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.Modules;
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
@@ -47,7 +47,7 @@ public class ModulesFragment extends Fragment {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_modules_list, container, false);
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);
@@ -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

@@ -0,0 +1,61 @@
package de.sebse.fuplanner.fragments;
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.ModulesFragment.OnModulesFragmentInteractionListener;
import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.types.News;
import de.sebse.fuplanner.tools.types.NewsList;
import de.sebse.fuplanner.tools.ui.NewsViewHolder;
/**
* {@link RecyclerView.Adapter} that can display a {@link Modules.Module} and makes a call to the
* specified {@link OnModulesFragmentInteractionListener}.
*/
class NewsAdapter extends RecyclerView.Adapter<NewsViewHolder> {
private NewsList mValues;
NewsAdapter() {
mValues = null;
}
public void setNews(NewsList news) {
mValues = news;
this.notifyDataSetChanged();
}
@NonNull
@Override
public NewsViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_news_item, parent, false);
return new NewsViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull NewsViewHolder holder, int position) {
News news = mValues.getPast(position);
holder.mHeader.setText(news.getTitle());
switch (news.getCategory()) {
case News.CATEGORY_UPDATE:
holder.mSubLeft.setText(R.string.update_news);
case News.CATEGORY_TRICKS:
holder.mSubLeft.setText(R.string.tricks);
}
holder.mSubRight.setText(UtilsDate.getModifiedDate(news.getDate()));
holder.mText.setText(news.getText());
}
@Override
public int getItemCount() {
return mValues != null ? mValues.sizePast() : 0;
}
}

View File

@@ -0,0 +1,97 @@
package de.sebse.fuplanner.fragments;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.types.NewsList;
/**
* A simple {@link Fragment} subclass.
* Use the {@link NewsFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class NewsFragment extends Fragment {
private Logger log = new Logger(this);
private MainActivityListener mListener;
private NewsAdapter mAdapter;
private NewsList dates = new NewsList();
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
// TODO: Rename and change types of parameters
public NewsFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @return A new instance of fragment NewsFragment.
*/
// TODO: Rename and change types and number of parameters
public static NewsFragment newInstance() {
NewsFragment fragment = new NewsFragment();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof MainActivityListener) {
mListener = (MainActivityListener) context;
mListener.onTitleTextChange(R.string.news);
}
else
throw new RuntimeException(context.toString() + " must implement MainActivityListener");
refresh(false);
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_recycler_norefresh, container, false);
// Set the adapter
Context context = view.getContext();
RecyclerView recyclerView = view.findViewById(R.id.list);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
mAdapter = new NewsAdapter();
recyclerView.setAdapter(mAdapter);
refresh(false);
return view;
}
private void refresh(boolean forceRefresh) {
if (mListener == null)
return;
mListener.getNewsManager().recv(success -> {
if (mAdapter != null)
mAdapter.setNews(success);
}, log::e, forceRefresh);
}
}

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.services.kvv.types.Event;
import de.sebse.fuplanner.services.kvv.types.Modules;
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,18 @@ 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;
public class ScheduleFragment extends Fragment implements
MonthLoader.MonthChangeListener,
WeekView.ScrollListener,
WeekView.DoubleTapLeftRightListener,
WeekView.EventClickListener {
private MainActivityListener mListener;
private WeekView mWeekView;
private final Logger log = new Logger(this);
private Modules mModules = null;
@@ -59,24 +62,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
@@ -86,7 +87,7 @@ public class ScheduleFragment extends Fragment implements MonthLoader.MonthChang
mWeekView = v.findViewById(R.id.weekView);
mWeekView.setMonthChangeListener(this);
mWeekView.setScrollListener(this);
mWeekView.setDoubleTapListener(this);
mWeekView.setDoubleTapLeftRightListener(this);
mWeekView.setOnEventClickListener(this);
invalidate(false);
return v;
@@ -99,8 +100,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 +145,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())));
}
@@ -153,25 +154,23 @@ public class ScheduleFragment extends Fragment implements MonthLoader.MonthChang
}
@Override
public void onDoubleTapListener(Calendar time) {
public void onDoubleTapLeftRightListener(boolean isRight) {
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
if (c.getTimeInMillis() > time.getTimeInMillis()){//nach links blättern
firstVisibleDay.add(Calendar.DATE, -1);
while (firstVisibleDay.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
firstVisibleDay.add(Calendar.DATE, -1);
}
}
else{
// skip to next week
if (isRight){
firstVisibleDay.add(Calendar.DATE, 1);
while (firstVisibleDay.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
firstVisibleDay.add(Calendar.DATE, 1);
}
}
else {
firstVisibleDay.add(Calendar.DATE, -1);
while (firstVisibleDay.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
firstVisibleDay.add(Calendar.DATE, -1);
}
}
mWeekView.goToDate(firstVisibleDay);
onFirstVisibleDayChanged(firstVisibleDay, mWeekView.getLastVisibleDay());
}
@Override
@@ -180,24 +179,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.services.canteen.types.Canteen;
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.services.canteen.CanteenBrowser;
import de.sebse.fuplanner.services.canteen.types.Canteen;
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.
@@ -31,11 +31,12 @@ public class DaySwitcherFragment extends Fragment implements DaySwitcherListener
// Parameters
private int mCanteenId;
private String mPageRestoreRequest = null;
private MainAcitivityListener mListener;
private MainActivityListener mListener;
private final Logger log = new Logger(this);
private ViewPager mViewPager;
private DaySwitcherAdapter adapterViewPager;
private ViewPager mViewPager;
public DaySwitcherFragment() {
// Required empty public constructor
@@ -48,10 +49,14 @@ public class DaySwitcherFragment extends Fragment implements DaySwitcherListener
* @param canteenId Canteen id in canteens list.
* @return A new instance of fragment DaySwitcherFragment.
*/
public static Fragment newInstance(int canteenId) {
public static Fragment newInstance(String canteenId) {
DaySwitcherFragment fragment = new DaySwitcherFragment();
Bundle args = new Bundle();
args.putInt(ARG_POSITION, canteenId);
if (!canteenId.contains("."))
args.putString(ARG_POSITION, canteenId+".0");
else
args.putString(ARG_POSITION, canteenId);
fragment.setArguments(args);
return fragment;
}
@@ -60,7 +65,15 @@ public class DaySwitcherFragment extends Fragment implements DaySwitcherListener
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mCanteenId = getArguments().getInt(ARG_POSITION);
String canteenId = getArguments().getString(ARG_POSITION);
if (!canteenId.contains(".")) {
mCanteenId = Integer.parseInt(canteenId);
mPageRestoreRequest = null;
} else {
String[] split = canteenId.split("\\.", 2);
mCanteenId = Integer.parseInt(split[0]);
mPageRestoreRequest = split[1];
}
}
if (mListener != null) {
mListener.onTitleTextChange(R.string.canteens);
@@ -81,8 +94,8 @@ public class DaySwitcherFragment extends Fragment implements DaySwitcherListener
mViewPager = v.findViewById(R.id.vpPager);
adapterViewPager = new DaySwitcherAdapter(getChildFragmentManager());
mViewPager.setAdapter(adapterViewPager);
refresh(false);
applyPageRequest();
refresh();
return v;
}
@@ -90,8 +103,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 +117,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,9 +126,12 @@ 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);
applyPageRequest();
browser.getCanteen(canteen, success -> {
adapterViewPager.setModule();
applyPageRequest();
if (callback != null)
callback.onResponse(success);
}, error -> {
@@ -136,4 +152,20 @@ public class DaySwitcherFragment extends Fragment implements DaySwitcherListener
refresh(true, callback, errorCallback);
}
public void gotoFragmentPart(int part) {
mViewPager.setCurrentItem(part, true);
}
public String getData() {
return mCanteenId+"."+mViewPager.getCurrentItem();
}
private void applyPageRequest() {
if (mPageRestoreRequest != null) {
int request = Integer.parseInt(mPageRestoreRequest);
mViewPager.setCurrentItem(request);
if (request == mViewPager.getCurrentItem())
mPageRestoreRequest = null;
}
}
}

View File

@@ -1,6 +1,6 @@
package de.sebse.fuplanner.fragments.canteen;
import de.sebse.fuplanner.services.Canteen.types.Canteen;
import de.sebse.fuplanner.services.canteen.types.Canteen;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;

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.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 final String[] CATEGORY_KEYS = new String[]{"Essen", "Aktionen", "Beilagen", "Desserts", "Salate", "Suppen", "Vorspeisen"};
@StringRes
private final 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 final int CATEGORY_OTHER = R.string.others;
private final 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,19 +3,19 @@ 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;
import de.sebse.fuplanner.services.Canteen.types.Canteen;
import de.sebse.fuplanner.services.Canteen.types.Day;
import de.sebse.fuplanner.services.canteen.CanteenBrowser;
import de.sebse.fuplanner.services.canteen.types.Canteen;
import de.sebse.fuplanner.services.canteen.types.Day;
import de.sebse.fuplanner.tools.logging.Logger;
/**
@@ -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.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.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.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 final 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) {
@@ -109,7 +40,90 @@ class ModDetailAnnounceAdapter extends BaseExpandableListAdapter {
this.setModule();
}
public void setModule() {
private 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.types.Modules;
import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.services.kvv.ui.Download;
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,51 @@ 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 -> {
if (mListener == null)
return;
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);
}, 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 += "/Announcement";
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;
}
}
private 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.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.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.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 final 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) {
@@ -112,7 +40,90 @@ class ModDetailAssignmentAdapter extends BaseExpandableListAdapter {
this.setModule();
}
public void setModule() {
private 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.types.Modules;
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 -> {
if (mListener == null)
return;
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);
}, 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(), "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;
}
}
private Download getDownload() {
if (download == null)
download = new Download(this::getContext, () -> (MainActivity) getActivity());
return download;
}
}

View File

@@ -1,31 +1,47 @@
package de.sebse.fuplanner.fragments.moddetails;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.content.Context;
import android.content.res.Resources;
import android.util.Pair;
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.Modules;
import de.sebse.fuplanner.tools.DateUtils;
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.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.StringViewHolder;
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 static final int TYPE_HEADER = 3;
private Modules.Module mValue;
private final ArrayList<Pair<Integer, Integer>> mPositionalData;
private final ArrayList<Pair<Integer, Object>> mPositionalData;
private final Logger log = new Logger(this);
ModDetailEventAdapter() {
mValue = null;
@@ -38,94 +54,158 @@ class ModDetailEventAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
this.setModule();
}
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));
private void setModule() {
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 Pair<>(TYPE_HEADER, value.getKey()));
boolean showAsGrouped = true;
for (GroupedEvents.Group group : value.getValue().getGroups()) {
if (group.getSkippedDates().size() > 2) {
showAsGrouped = false;
break;
}
}
if (showAsGrouped) {
for (GroupedEvents.Group group : value.getValue().getGroups()) {
mPositionalData.add(new Pair<>(TYPE_GROUPED, group));
}
} else {
for (Event event : value.getValue()) {
mPositionalData.add(new Pair<>(TYPE_UNGROUPED, event));
}
}
}
}
for (Map.Entry<String, EventList> value: listsUngrouped.entrySet()) {
if (value.getValue().size() > 0) {
mPositionalData.add(new Pair<>(TYPE_HEADER, value.getKey()));
for (Event event : value.getValue()) {
mPositionalData.add(new Pair<>(TYPE_UNGROUPED, event));
}
}
}
if (mPositionalData.size() == 0)
mPositionalData.add(new Pair<>(TYPE_NONE, 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:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_caption, parent, false);
return new HeaderViewHolder(view);
case TYPE_ITEM:
case TYPE_GROUPED:
case TYPE_UNGROUPED:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_items, parent, false);
return new ItemViewHolder(view);
case TYPE_HEADER:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_caption, parent, false);
return new StringViewHolder(view);
default:
//noinspection ConstantConditions
return null;
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_no_items, parent, false);
return new CustomViewHolder(view);
}
}
@Override
public int getItemViewType(int position) {
// Note that unlike in ListView adapters, types don't have to be contiguous
if (position < mPositionalData.size())
return mPositionalData.get(position).first;
else return -1;
return mPositionalData.get(position).first;
}
@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);
Pair<Integer, Object> data = mPositionalData.get(position);
Resources resources = holder.mView.getResources();
Context context = holder.mView.getContext();
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;
StringViewHolder s = (StringViewHolder) holder;
String title = "";
if (VALUE_LECTURE.equals(data.second)) {
title = resources.getString(R.string.lecture);
} else if (VALUE_TUTORIAL.equals(data.second)) {
title = resources.getString(R.string.tutorial);
} else if (VALUE_EXAM.equals(data.second)) {
title = resources.getString(R.string.exam);
} else if (VALUE_DEADLINE.equals(data.second)) {
title = resources.getString(R.string.deadline);
} else if (VALUE_OTHER.equals(data.second)) {
title = resources.getString(R.string.others);
}
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;
s.mString.setText(title);
return;
case TYPE_GROUPED:
ItemViewHolder ig = (ItemViewHolder) holder;
GroupedEvents.Group group = ((GroupedEvents.Group) data.second);
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));
}
}
//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());
ig.mTitle.setText(context.getString(R.string.event_scale, weekday, startTime, endTime));
ig.mSubLeft.setText(context.getString(R.string.date_scale, start, end));
ig.mSubRight.setText(excepts != null ? context.getString(R.string.except_list, excepts.toString()) : "");
return;
case TYPE_UNGROUPED:
ItemViewHolder iu = (ItemViewHolder) holder;
Event event = (Event) data.second;
String date;
if (UtilsDate.dateEquals(event.getStartDate(), System.currentTimeMillis()))
start = UtilsDate.getModifiedTime(context, 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(context, event.getStartDate());
if (UtilsDate.dateEquals(event.getStartDate(), event.getEndDate()))
end = UtilsDate.getModifiedTime(context, 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;
end = UtilsDate.getModifiedDateTime(context, event.getEndDate());
date = context.getString(R.string.date_scale, start, end);
iu.mTitle.setText(event.getTitle());
iu.mSubLeft.setText(date);
iu.mSubRight.setText("");
}
}
@@ -134,38 +214,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

@@ -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,32 @@ 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 -> {
if (mListener == null)
return;
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);
}, 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.services.kvv.types.Modules;
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.Modules;
import de.sebse.fuplanner.tools.ui.CustomViewHolder;
import de.sebse.fuplanner.services.kvv.types.Grade;
import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.tools.ui.StringViewHolder;
class ModDetailGradebookAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_TOTAL = 0;
@@ -36,20 +36,16 @@ class ModDetailGradebookAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
this.setModule();
}
public void setModule() {
private void setModule() {
mPositionalData.clear();
mPositionalData.add(new Pair<>(TYPE_TOTAL, SECTION_GRADE));
addPositionalListData(getGradesCount(), SECTION_GRADE);
for (int i = 0; i < getGradesCount(); i++) {
mPositionalData.add(new Pair<>(TYPE_GRADE, SECTION_GRADE +1024*i));
}
this.notifyDataSetChanged();
}
private void addPositionalListData(int count, int category) {
for (int i = 0; i < count; i++) {
mPositionalData.add(new Pair<>(TYPE_GRADE, category+1024*i));
}
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -58,11 +54,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 +81,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,33 +114,20 @@ 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);
mGradeMax = view.findViewById(R.id.grade_max);
}
@NonNull
@Override
public String toString() {
return super.toString() + " '" + mTitle.getText() + " '" + mGrade.getText() + " '" + mGradeMax.getText() + "'";

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,32 @@ 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 -> {
if (mListener == null)
return;
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);
}, 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,32 @@
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.Modules;
import de.sebse.fuplanner.tools.DateUtils;
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.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.ShortcutViewHolder;
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 +35,8 @@ 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;
private static final int TYPE_SHORTCUTS = 5;
@Nullable private final ModDetailListener mListener;
@@ -48,10 +55,15 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
this.setModule();
}
public void setModule() {
private void setModule() {
mPositionalData.clear();
mPositionalData.add(new Pair<>(TYPE_HEADER, ModulePart.DESCRIPTION));
mPositionalData.add(new Pair<>(TYPE_DESCRIPTION, null));
mPositionalData.add(new Pair<>(TYPE_SHORTCUTS, 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 +89,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 +102,14 @@ 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);
case TYPE_SHORTCUTS:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_moddetails_shortcuts, parent, false);
return new ShortcutViewHolder(view);
default:
//noinspection ConstantConditions
return null;
@@ -111,19 +131,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 +164,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 +177,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 +188,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 +205,36 @@ 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(emailIntent);
});
break;
}
break;
case TYPE_SHORTCUTS:
ShortcutViewHolder s = (ShortcutViewHolder) holder;
s.mLeft.setOnClickListener(view -> {
if (mListener != null) mListener.gotoFragmentPart(ModulePart.RESOURCES, -1);
});
s.mRight.setOnClickListener(view -> {
if (mListener != null) mListener.gotoFragmentPart(ModulePart.GRADEBOOK, -1);
});
break;
case TYPE_SHOW_MORE:
CustomViewHolder c = (CustomViewHolder) holder;
c.mView.setOnClickListener(view -> {
@@ -221,19 +274,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 +284,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();
}
private 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,164 @@
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().details().recv(mItemPos, pair -> {
adapter.setModule(pair.first);
if (pair.second)
swipeLayout.setRefreshing(false);
}, error -> {
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
}
private 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

@@ -1,9 +0,0 @@
package de.sebse.fuplanner.services.GoogleAuth;
/**
* Created by sebastian on 07.11.17.
*/
public interface ConnectedListener {
void connected();
}

View File

@@ -1,23 +0,0 @@
package de.sebse.fuplanner.services.GoogleAuth;
/**
* Created by Sebastian on 06.11.2017.
*/
public class Credentials {
private final String username;
private final String password;
public Credentials(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}

View File

@@ -1,9 +0,0 @@
package de.sebse.fuplanner.services.GoogleAuth;
/**
* Created by Sebastian on 06.11.2017.
*/
public interface CredentialsListener {
void onCredentials(Credentials credentials);
}

View File

@@ -1,221 +0,0 @@
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;
import com.google.android.gms.auth.api.credentials.Credential;
import com.google.android.gms.auth.api.credentials.CredentialRequest;
import com.google.android.gms.auth.api.credentials.CredentialsClient;
import com.google.android.gms.auth.api.credentials.CredentialsOptions;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
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 static android.app.Activity.RESULT_OK;
/**
* Created by Sebastian on 06.11.2017.
*/
public class GoogleAuth {
// https://developers.google.com/identity/smartlock-passwords/android/retrieve-credentials
private static final String TAG = "GoogleAuth";
private final FragmentActivity activity;
private CredentialsClient mCredentialsClient;
private boolean mIsResolving;
@Nullable
private CredentialsListener mCredentialsListener;
private boolean isConnected = false;
public GoogleAuth(FragmentActivity activity) {
this.activity = activity;
}
private void connect() {
if (this.isUnavailable()) {
Log.d(TAG, "STATUS: Google auth not available!");
return;
}
this.mCredentialsClient = getClient();
this.isConnected = true;
}
public void getLoginState(final CredentialsListener credentialsListener) {
if (this.isUnavailable()) {
Log.d(TAG, "STATUS: Google auth not available!");
credentialsListener.onCredentials(null);
return;
}
if (!this.isConnected)
connect();
CredentialRequest request = new CredentialRequest.Builder()
.setPasswordLoginSupported(true)
.build();
mCredentialsClient.request(request).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
// Successfully read the credential without any user interaction, this
// means there was only a single credential and the user has auto
// sign-in enabled.
Credential credential = task.getResult().getCredential();
credentialsListener.onCredentials(new Credentials(credential.getId(), credential.getPassword()));
return;
}
Exception e = task.getException();
if (e instanceof ResolvableApiException) {
// This is most likely the case where the user has multiple saved
// credentials and needs to pick one. This requires showing UI to
// resolve the read request.
GoogleAuth.this.mCredentialsListener = credentialsListener;
ResolvableApiException rae = (ResolvableApiException) e;
resolveResult(rae, RequestCode.RC_READ);
return;
}
if (e instanceof ApiException) {
ApiException ae = (ApiException) e;
if (ae.getStatusCode() == CommonStatusCodes.SIGN_IN_REQUIRED) {
// This means only a hint is available, but we are handling that
// elsewhere so no need to act here.
} else {
credentialsListener.onCredentials(null);
Log.w(TAG, "Unexpected status code: " + ae.getStatusCode());
}
}
});
}
public void setLoginState(String username, String password) {
if (this.isUnavailable()) {
Log.d(TAG, "STATUS: Google auth not available!");
Toast.makeText(activity, "Google auth not available!", Toast.LENGTH_SHORT).show();
return;
}
if (!this.isConnected)
connect();
Credential credential = new Credential.Builder(username)
.setPassword(password)
.build();
mCredentialsClient.save(credential).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
showToast("Credential saved.");
return;
}
Exception e = task.getException();
if (e instanceof ResolvableApiException) {
// The first time a credential is saved, the user is shown UI
// to confirm the action. This requires resolution.
ResolvableApiException rae = (ResolvableApiException) e;
resolveResult(rae, RequestCode.RC_SAVE);
} else {
// Save failure cannot be resolved.
Log.w(TAG, "Save failed.", e);
showToast("Credential Save Failed");
}
});
}
public void deleteLoginState(String username, String password) {
if (this.isUnavailable()) {
Log.d(TAG, "STATUS: Google auth not available!");
return;
}
if (!this.isConnected)
connect();
Credential credential = new Credential.Builder(username)
.setPassword(password)
.build();
mCredentialsClient.delete(credential).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
// Credential was deleted successfully
}
});
}
private boolean isUnavailable() {
return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this.activity) != ConnectionResult.SUCCESS;
}
private CredentialsClient getClient() {
CredentialsOptions options = new CredentialsOptions.Builder()
.forceEnableSaveDialog()
.build();
return com.google.android.gms.auth.api.credentials.Credentials.getClient(this.activity, options);
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult:" + requestCode + ":" + resultCode + ":" + data);
switch (requestCode) {
case RequestCode.RC_HINT:
// Drop into handling for RC_READ
case RequestCode.RC_READ:
if (resultCode == RESULT_OK) {
boolean isHint = (requestCode == RequestCode.RC_HINT);
Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
if (mCredentialsListener != null)
this.mCredentialsListener.onCredentials(new Credentials(credential.getId(), credential.getPassword()));
else
Log.d(TAG, "No Credentials Listener");
} else {
if (mCredentialsListener != null)
this.mCredentialsListener.onCredentials(null);
else
Log.d(TAG, "No Credentials Listener");
Log.e(TAG, "Credential Read: NOT OK");
showToast("Credential Read Failed");
}
mIsResolving = false;
break;
case RequestCode.RC_SAVE:
if (resultCode == RESULT_OK) {
Log.d(TAG, "Credential Save: OK");
showToast("Credential Save Success");
} else {
Log.e(TAG, "Credential Save: NOT OK");
showToast("Credential Save Failed");
}
mIsResolving = false;
break;
}
}
private void resolveResult(ResolvableApiException rae, int requestCode) {
// We don't want to fire multiple resolutions at once since that can result
// in stacked dialogs after rotation or another similar event.
if (mIsResolving) {
Log.w(TAG, "resolveResult: already resolving.");
return;
}
Log.d(TAG, "Resolving: " + rae);
try {
rae.startResolutionForResult(this.activity, requestCode);
mIsResolving = true;
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "STATUS: Failed to send resolution.", e);
}
}
/** Display a short Toast message **/
private void showToast(String msg) {
Toast.makeText(this.activity, msg, Toast.LENGTH_SHORT).show();
}
}

View File

@@ -1,11 +0,0 @@
package de.sebse.fuplanner.services.GoogleAuth;
/**
* Created by sebastian on 07.11.17.
*/
class RequestCode {
public static final int RC_SAVE = 1;
public static final int RC_HINT = 2;
public static final int RC_READ = 3;
}

View File

@@ -1,195 +0,0 @@
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 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;
public KVV(Context context) {
mListener = (MainAcitivityListener) context;
this.context = context;
this.updatingList = new ArrayList<>();
}
public LoginToken easyLogin() {
KVVLogin login = new KVVLogin(this.context);
lastToken = login.easyLogin();
this.endUpdate();
return lastToken;
}
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);
});
}
public void logout() {
KVVModuleList modules = (KVVModuleList) addons.get("modules");
if (modules != null) {
modules.deleteModulesOffline(this.context);
}
invalidate();
}
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);
}
}
}

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

@@ -1,57 +0,0 @@
package de.sebse.fuplanner.services.KVV.types;
import java.io.Serializable;
import java.util.ArrayList;
public class Announcement implements Serializable {
private final String id;
private final String title;
private final String body;
private final String createdBy;
private final long createdOn;
private final ArrayList<String> urls;
public Announcement(String id, String title, String body, String createdBy, long createdOn, ArrayList<String> urls) {
this.id = id;
this.title = title;
this.body = body;
this.createdBy = createdBy;
this.createdOn = createdOn;
this.urls = urls;
}
public ArrayList<String> getUrls() {
return urls;
}
public String getId() {
return id;
}
public String getTitle() {
return title;
}
public String getBody() {
return body;
}
public String getCreatedBy() {
return createdBy;
}
public long getCreatedOn() {
return createdOn;
}
@Override
public String toString() {
return "ID: "+getId()+
"\nTitle: "+getTitle()+
"\nBody length: "+getBody().length()+
"\nCreated by: "+getCreatedBy()+
"\nCreated on: "+getCreatedOn()+
"\nURLs: "+getUrls().toString();
}
}

View File

@@ -1,34 +0,0 @@
package de.sebse.fuplanner.services.KVV.types;
import java.io.Serializable;
public class Gradebook implements Serializable {
private final String itemName;
private final double grade;
private final double maxPoints;
public Gradebook(String itemName, double points, double maxPoints) {
this.itemName = itemName;
this.grade = points;
this.maxPoints = maxPoints;
}
public double getMaxPoints() {
return maxPoints;
}
public double getPoints() {
return grade;
}
public String getItemName() {
return itemName;
}
@Override
public String toString() {
return "Name: "+getItemName()+
"\nPoints: "+ getPoints()+
"\nMax points: "+getMaxPoints();
}
}

View File

@@ -1,41 +0,0 @@
package de.sebse.fuplanner.services.KVV.types;
import java.io.Serializable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Lecturer implements Serializable {
private final String firstname;
private final String surname;
private final String mail;
public Lecturer(String parsableString) throws NoSuchFieldException {
Pattern pattern = Pattern.compile("([^|]*)\\|([^|]*)\\|([^|]*)\\|\\|", Pattern.DOTALL);
Matcher matcher = pattern.matcher(parsableString);
if (!matcher.find()) {
throw new NoSuchFieldException();
}
this.firstname = matcher.group(1);
this.surname = matcher.group(2);
this.mail = matcher.group(3);
}
public String getFirstname() {
return firstname;
}
public String getSurname() {
return surname;
}
public String getMail() {
return mail;
}
@Override
public String toString() {
return "First name: "+getFirstname()+
"\nSurname: "+getSurname()+
"\nMail: "+getMail();
}
}

View File

@@ -1,111 +0,0 @@
package de.sebse.fuplanner.services.KVV.types;
import android.content.Context;
import android.support.annotation.Nullable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashMap;
/**
* 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;
public LoginToken(String username, String shibsessionKey, String shibsessionName, String JSESSIONID) {
this.username = username;
this.shibsessionKey = shibsessionKey;
this.shibsessionName = shibsessionName;
this.JSESSIONID = JSESSIONID;
}
@Nullable
public static LoginToken load(Context context) throws IOException, ClassNotFoundException {
FileInputStream fis = context.openFileInput(FILE_NAME);
ObjectInputStream is = new ObjectInputStream(fis);
LoginToken loginToken = (LoginToken) is.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);
os.close();
fos.close();
}
public void delete(Context context) {
context.deleteFile(FILE_NAME);
}
public void setAdditionals(String fullname, String email) {
this.fullname = fullname;
this.email = email;
}
public String getUsername() {
return username;
}
private String getShibsessionKey() {
return shibsessionKey;
}
private String getShibsessionName() {
return shibsessionName;
}
private String getJSESSIONID() {
return JSESSIONID;
}
public String getFullname() {
return fullname;
}
public String getEmail() {
return email;
}
public HashMap<String, String> getCookies() {
HashMap<String, String> cookies = new HashMap<>();
cookies.put("JSESSIONID", getJSESSIONID());
cookies.put(getShibsessionKey(), getShibsessionName());
cookies.put("pasystem_timezone_ok", "true");
return cookies;
}
public boolean isSameUser(LoginToken token) {
return token != null && this.getUsername().equals(token.getUsername());
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
HashMap<String, String> cookies = this.getCookies();
for (String header: cookies.keySet()) {
result.append(header).append("=").append(cookies.get(header)).append(";");
}
return result.substring(0, result.length()-1);
}
}

View File

@@ -1,165 +0,0 @@
package de.sebse.fuplanner.services.KVV.types;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
/**
* Created by sebastian on 29.10.17.
*/
public class Modules implements Iterable<Modules.Module>, Serializable {
private SortedListModule list;
private final LoginToken token;
//private transient Logger log = new Logger(this);
private static final String FILE_NAME = "ModuleListSaving";
public Modules(LoginToken loginToken) {
this.token = loginToken;
this.list = new SortedListModule();
}
public void addModule(String semester, HashSet<String> lvNumber, String title, HashSet<Lecturer> lecturer, String type, String description, String ID) {
Module m = new Module(semester, lvNumber, title, lecturer, type, description, ID);
this.list.add(m);
}
@Override
public String toString() {
return this.list.toString();
}
@NonNull
@Override
public Iterator<Module> iterator() {
return this.list.iterator();
}
public Iterator<Module> latestSemesterIterator() {
return this.list.filteredIterator(this.list.getLatestSemester());
}
public int size() {
return this.list.size();
}
public Module get(String id) {
return this.list.getById(id);
}
public Module getByIndex(int index) {
return this.list.get(index);
}
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();
is.close();
fis.close();
return modules;
}
public void save(Context context) throws IOException {
FileOutputStream fos = context.openFileOutput(FILE_NAME, Context.MODE_PRIVATE);
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(this);
os.close();
fos.close();
}
public void delete(Context context) {
context.deleteFile(FILE_NAME);
}
public LoginToken getToken() {
return token;
}
public void updateList(Modules modules) {
SortedListModule old = this.list;
this.list = modules.list;
for (Module oldModule : old) {
Module newModule = this.list.getById(oldModule.getID());
if (newModule != null) {
newModule.announcements = oldModule.announcements;
newModule.assignments = oldModule.assignments;
newModule.events = oldModule.events;
newModule.gradebook = oldModule.gradebook;
newModule.resources = oldModule.resources;
}
}
}
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 ArrayList<Announcement> announcements;
@Nullable public AssignmentList assignments;
@Nullable public EventList events;
@Nullable public ArrayList<Gradebook> 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){
maxPoint += g.getMaxPoints();
userPoint += g.getPoints();
}
}
if (maxPoint == 0)
return 0;
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");
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.type = type;
this.description = description;
this.ID = ID;
}
public String getID() {
return ID;
}
@Override
public String toString() {
return "Semester: "+semester+
"\nlvNumber: "+lvNumber.toString()+
"\ntitle: "+title+
"\nlecturer: "+lecturer.toString()+
"\ntype: "+type+
"\nID: "+ID;
}
}
}

View File

@@ -1,50 +0,0 @@
package de.sebse.fuplanner.services.KVV.types;
import java.io.Serializable;
public class Resource implements Serializable {
private final String author;
private final long modifiedDate;
private final String title;
private final String url;
public Resource(String author, String title, long modifiedDate, String url) {
this.author = author;
this.title = title;
this.modifiedDate = modifiedDate;
this.url = url;
}
public String getAuthor() {
return author;
}
public long getModifiedDate() {
return modifiedDate;
}
public String getTitle() {
return title;
}
public String getUrl() {
return url;
}
@Override
public String toString() {
return "Resource{" +
"author='" + author + '\'' +
", modifiedDate=" + modifiedDate +
", title='" + title + '\'' +
", url='" + url + '\'' +
'}';
}
}

View File

@@ -1,4 +1,4 @@
package de.sebse.fuplanner.services.Canteen;
package de.sebse.fuplanner.services.canteen;
import android.content.Context;
@@ -8,12 +8,11 @@ import org.json.JSONObject;
import java.io.IOException;
import de.sebse.fuplanner.MainActivity;
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.services.canteen.types.Canteen;
import de.sebse.fuplanner.services.canteen.types.CanteenListener;
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.network.HTTPService;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkError;
@@ -23,15 +22,15 @@ public class CanteenBrowser extends HTTPService {
private Canteens canteens;
private final AsyncQueue queue = new AsyncQueue();
private final Context context;
private MainAcitivityListener mListener;
private CanteenListener mListener;
public CanteenBrowser(Context context) {
super(context);
this.context = context;
if (context instanceof MainAcitivityListener)
mListener = (MainActivity) context;
if (context instanceof CanteenListener)
mListener = (CanteenListener) context;
else
throw new RuntimeException(context.toString() + "must implement MainActivityListener");
throw new RuntimeException(context.toString() + " must implement CanteenListener");
try {
this.canteens = Canteens.load(context);
} catch (IOException e) {
@@ -65,7 +64,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 +191,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()];
@@ -224,17 +223,14 @@ public class CanteenBrowser extends HTTPService {
private<T> NetworkCallback<T> saveOnCallback(NetworkCallback<T> callback, boolean forceRefresh){
return (success -> {
if (forceRefresh)
mListener.refreshFailed(false);
mListener.onCanteenRefreshCompleted(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.onCanteenRefreshCompleted(true);
errorCallback.onError(error);
});
}

View File

@@ -1,6 +1,6 @@
package de.sebse.fuplanner.services.Canteen.types;
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();
}
@@ -110,6 +121,7 @@ public class Canteen implements Serializable, Iterable<Day> {
return lng;
}
@NonNull
@Override
public String toString() {
return id+": "+name+"\n"+list.toString()+"\n";

View File

@@ -0,0 +1,5 @@
package de.sebse.fuplanner.services.canteen.types;
public interface CanteenListener {
void onCanteenRefreshCompleted(boolean b);
}

View File

@@ -1,7 +1,7 @@
package de.sebse.fuplanner.services.Canteen.types;
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!");
}
};
}
@@ -94,6 +94,7 @@ public class Canteens implements Serializable, Iterable<Canteen> {
context.deleteFile(FILE_NAME);
}
@NonNull
@Override
public String toString() {
return this.list.toString();

View File

@@ -1,6 +1,6 @@
package de.sebse.fuplanner.services.Canteen.types;
package de.sebse.fuplanner.services.canteen.types;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import java.io.Serializable;
import java.util.Calendar;
@@ -58,6 +58,7 @@ public class Day implements Serializable, Iterable<Meal> {
return canteenId;
}
@NonNull
@Override
public String toString() {
return Canteen.calendarToKey(getCalendar())+"\n"+this.list+"\n";

View File

@@ -1,19 +1,21 @@
package de.sebse.fuplanner.services.Canteen.types;
package de.sebse.fuplanner.services.canteen.types;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import androidx.annotation.NonNull;
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 +44,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;
@@ -112,6 +114,7 @@ public class Meal implements Serializable {
return certificates;
}
@NonNull
@Override
public String toString() {
return name + " (" + category + ")";

View File

@@ -1,4 +1,4 @@
package de.sebse.fuplanner.services.Canteen.types;
package de.sebse.fuplanner.services.canteen.types;
import de.sebse.fuplanner.tools.SortedList;

View File

@@ -1,4 +1,4 @@
package de.sebse.fuplanner.services.Canteen.types;
package de.sebse.fuplanner.services.canteen.types;
import java.util.Calendar;

View File

@@ -1,4 +1,4 @@
package de.sebse.fuplanner.services.Canteen.types;
package de.sebse.fuplanner.services.canteen.types;
import de.sebse.fuplanner.tools.SortedList;

View File

@@ -0,0 +1,7 @@
package de.sebse.fuplanner.services.fulogin;
public class AccountGeneral {
public static final String ACCOUNT_TYPE = "de.sebse.fuplanner.fuauth";
public static final String AUTHTOKEN_TYPE_KVV = "KVV";
public static final String AUTHTOKEN_TYPE_BLACKBOARD = "Blackboard";
}

View File

@@ -0,0 +1,104 @@
package de.sebse.fuplanner.services.fulogin;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.accounts.NetworkErrorException;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import java.util.concurrent.ExecutionException;
public class FUAuthenticator extends AbstractAccountAuthenticator {
private final Context mContext;
public FUAuthenticator(Context context) {
super(context);
this.mContext = context;
}
@Override
public Bundle editProperties(AccountAuthenticatorResponse accountAuthenticatorResponse, String s) {
return null;
}
@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
final Intent intent = new Intent(mContext, FUAuthenticatorActivity.class);
intent.putExtra(FUAuthenticatorActivity.ARG_ACCOUNT_TYPE, accountType);
intent.putExtra(FUAuthenticatorActivity.ARG_AUTH_TYPE, authTokenType);
intent.putExtra(FUAuthenticatorActivity.ARG_IS_ADDING_NEW_ACCOUNT, true);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, Bundle bundle) throws NetworkErrorException {
return null;
}
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
// Extract the username and password from the Account Manager, and ask
// the server for an appropriate AuthToken.
final AccountManager am = AccountManager.get(mContext);
String authToken = am.peekAuthToken(account, authTokenType);
// Lets give another try to authenticate the user
if (TextUtils.isEmpty(authToken)) {
final String password = am.getPassword(account);
if (password != null) {
try {
authToken = new UserLoginTask(account.name, password, authTokenType, mContext).execute((Void) null).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
// If we get an authToken - we return it
if (!TextUtils.isEmpty(authToken)) {
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
return result;
}
// If we get here, then we couldn't access the user's password - so we
// need to re-prompt them for their credentials. We do that by creating
// an intent to display our AuthenticatorActivity.
final Intent intent = new Intent(mContext, FUAuthenticatorActivity.class);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
intent.putExtra(FUAuthenticatorActivity.ARG_ACCOUNT_TYPE, account.type);
intent.putExtra(FUAuthenticatorActivity.ARG_AUTH_TYPE, authTokenType);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
@Override
public String getAuthTokenLabel(String s) {
return null;
}
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String s, Bundle bundle) throws NetworkErrorException {
return null;
}
@Override
public Bundle hasFeatures(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String[] strings) throws NetworkErrorException {
return null;
}
}

View File

@@ -0,0 +1,281 @@
package de.sebse.fuplanner.services.fulogin;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorActivity;
import android.accounts.AccountManager;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Context;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import androidx.annotation.NonNull;
import de.sebse.fuplanner.R;
import static de.sebse.fuplanner.services.fulogin.UserLoginTask.PARAM_USER_PASS;
/**
* A login screen that offers login via email/password.
*/
public class FUAuthenticatorActivity extends AccountAuthenticatorActivity implements LoaderCallbacks<Cursor> {
/**
* Id to identity READ_CONTACTS permission request.
*/
private static final int REQUEST_READ_CONTACTS = 0;
/**
* Keep track of the login task to ensure we can cancel it if requested.
*/
UserLoginTask mAuthTask = null;
// UI references.
private EditText mEmailView;
EditText mPasswordView;
private View mProgressView;
private View mLoginFormView;
public static final String ARG_ACCOUNT_TYPE = "ARG_ACCOUNT_TYPE";
public static final String ARG_AUTH_TYPE = "ARG_AUTH_TYPE";
public static final String ARG_IS_ADDING_NEW_ACCOUNT = "ARG_IS_ADDING_NEW_ACCOUNT";
private String mAccountType;
private String mAuthTokenType;
private boolean mIsAddingNewAccount;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAccountType = getIntent().getStringExtra(ARG_ACCOUNT_TYPE);
mAuthTokenType = getIntent().getStringExtra(ARG_AUTH_TYPE);
mIsAddingNewAccount = getIntent().getBooleanExtra(ARG_IS_ADDING_NEW_ACCOUNT, false);
setContentView(R.layout.activity_fu_authenticator);
// Set up the login form.
mEmailView = findViewById(R.id.input_username);
populateAutoComplete();
mPasswordView = findViewById(R.id.input_password);
mPasswordView.setOnEditorActionListener((textView, id, keyEvent) -> {
if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) {
attemptLogin();
return true;
}
return false;
});
Button mEmailSignInButton = findViewById(R.id.btn_login);
mEmailSignInButton.setOnClickListener(view -> attemptLogin());
mLoginFormView = findViewById(R.id.login_form);
mProgressView = findViewById(R.id.login_progress);
}
private void populateAutoComplete() {
if (!mayRequestContacts()) {
return;
}
getLoaderManager().initLoader(0, null, this);
}
private boolean mayRequestContacts() {
/*if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
return true;
}
if (shouldShowRequestPermissionRationale(READ_CONTACTS)) {
Snackbar.make(mEmailView, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE)
.setAction(android.R.string.ok, new View.OnClickListener() {
@Override
@TargetApi(Build.VERSION_CODES.M)
public void onClick(View v) {
requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
}
});
} else {
requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
}*/
return false;
}
/**
* Callback received when a permissions request has been completed.
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode == REQUEST_READ_CONTACTS) {
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
populateAutoComplete();
}
}
}
/**
* Attempts to sign in or register the account specified by the login form.
* If there are form errors (invalid email, missing fields, etc.), the
* errors are presented and no actual login attempt is made.
*/
private void attemptLogin() {
InputMethodManager inputManager = (InputMethodManager) this.getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputManager != null && getCurrentFocus() != null)
inputManager.hideSoftInputFromWindow(this.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
if (mAuthTask != null) {
return;
}
// Reset errors.
mEmailView.setError(null);
mPasswordView.setError(null);
// Store values at the time of the login attempt.
String email = mEmailView.getText().toString();
String password = mPasswordView.getText().toString();
boolean cancel = false;
View focusView = null;
// Check for a valid password, if the user entered one.
if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
mPasswordView.setError(getString(R.string.error_invalid_password));
focusView = mPasswordView;
cancel = true;
}
// Check for a valid email address.
if (TextUtils.isEmpty(email)) {
mEmailView.setError(getString(R.string.error_field_required));
focusView = mEmailView;
cancel = true;
}
if (cancel) {
// There was an error; don't attempt login and focus the first
// form field with an error.
focusView.requestFocus();
} else {
// Show a progress spinner, and kick off a background task to
// perform the user login attempt.
showProgress(true);
mAuthTask = new UserLoginTask(email, password, mAuthTokenType, this);
mAuthTask.execute((Void) null);
}
}
private boolean isPasswordValid(String password) {
//TODO: Replace this with your own logic
return password.length() > 4;
}
/**
* Shows the progress UI and hides the login form.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
void showProgress(final boolean show) {
// On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
// for very easy animations. If available, use these APIs to fade-in
// the progress spinner.
int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
mLoginFormView.animate().setDuration(shortAnimTime).alpha(
show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
});
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mProgressView.animate().setDuration(shortAnimTime).alpha(
show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
}
});
}
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
return new CursorLoader(this,
// Retrieve data rows for the device user's 'profile' contact.
Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,
// Select only email addresses.
ContactsContract.Contacts.Data.MIMETYPE +
" = ?", new String[]{ContactsContract.CommonDataKinds.Email
.CONTENT_ITEM_TYPE},
// Show primary email addresses first. Note that there won't be
// a primary email address if the user hasn't specified one.
ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
}
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
}
private interface ProfileQuery {
String[] PROJECTION = {
ContactsContract.CommonDataKinds.Email.ADDRESS,
ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
};
int ADDRESS = 0;
int IS_PRIMARY = 1;
}
void finishLogin(Intent intent) {
String accountName = intent.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
String accountPassword = intent.getStringExtra(PARAM_USER_PASS);
final Account account = new Account(accountName, intent.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE));
final AccountManager mAccountManager = AccountManager.get(this);
if (getIntent().getBooleanExtra(ARG_IS_ADDING_NEW_ACCOUNT, false)) {
String authtoken = intent.getStringExtra(AccountManager.KEY_AUTHTOKEN);
String authtokenType = mAuthTokenType;
// Creating the account on the device and setting the auth token we got
// (Not setting the auth token will cause another call to the server to authenticate the user)
mAccountManager.addAccountExplicitly(account, accountPassword, null);
mAccountManager.setAuthToken(account, authtokenType, authtoken);
} else {
mAccountManager.setPassword(account, accountPassword);
}
setAccountAuthenticatorResult(intent.getExtras());
setResult(RESULT_OK, intent);
finish();
}
}

View File

@@ -0,0 +1,13 @@
package de.sebse.fuplanner.services.fulogin;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class FUAuthenticatorService extends Service {
@Override
public IBinder onBind(Intent intent) {
FUAuthenticator authenticator = new FUAuthenticator(this);
return authenticator.getIBinder();
}
}

View File

@@ -0,0 +1,124 @@
package de.sebse.fuplanner.services.fulogin;
import android.accounts.AccountManager;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Build;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import androidx.annotation.Nullable;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.kvv.types.LoginToken;
import de.sebse.fuplanner.services.kvv.sync.Login;
import de.sebse.fuplanner.tools.logging.Logger;
/**
* Represents an asynchronous login/registration task used to authenticate
* the user.
*/
public class UserLoginTask extends AsyncTask<Void, Void, String> {
/**
* A dummy authentication store containing known user names and passwords.
* TODO: remove after connecting to a real authentication system.
*/
/*private static final String[] DUMMY_CREDENTIALS = new String[]{
"foo@example.com:hello", "bar@example.com:world"
};*/
static final String PARAM_USER_PASS = "PARAM_USER_PASS";
private final String mEmail;
private final String mPassword;
private final Login mVolleyLogin;
private String mTokenType;
private Logger log = new Logger(this);
@SuppressLint("StaticFieldLeak")
@Nullable
private FUAuthenticatorActivity mActivity;
UserLoginTask(String email, String password, String tokenType, @NotNull Context context) {
mEmail = email;
mPassword = password;
mTokenType = tokenType;
mVolleyLogin = new Login(context);
if (context instanceof FUAuthenticatorActivity)
mActivity = (FUAuthenticatorActivity) context;
}
@Override
protected String doInBackground(Void... params) {
// TODO: attempt authentication against a network service.
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<LoginToken> login = new AtomicReference<>();
mVolleyLogin.doLogin(mEmail, mPassword, success -> {
mVolleyLogin.testLoginToken(success, success1 -> {
login.set(success);
latch.countDown();
}, error -> latch.countDown());
}, error -> latch.countDown());
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.d(login.get());
if (login.get() == null) {
return null;
} else {
return login.get().toJsonString();
}
/*for (String credential : DUMMY_CREDENTIALS) {
String[] pieces = credential.split(":");
if (pieces[0].equals(mEmail)) {
// Account exists, return true if the password matches.
return pieces[1].equals(mPassword) ? "auth token here" : null;
}
}
// TODO: register the new account here.
return null;*/
}
@Override
protected void onPostExecute(final String success) {
if (mActivity == null || mActivity.isFinishing())
return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && mActivity.isDestroyed())
return;
mActivity.mAuthTask = null;
mActivity.showProgress(false);
if (success != null) {
final Intent res = new Intent();
res.putExtra(AccountManager.KEY_ACCOUNT_NAME, mEmail);
res.putExtra(AccountManager.KEY_ACCOUNT_TYPE, AccountGeneral.ACCOUNT_TYPE);
res.putExtra(AccountManager.KEY_AUTHTOKEN, success);
res.putExtra(PARAM_USER_PASS, mPassword);
mActivity.finishLogin(res);
} else {
mActivity.mPasswordView.setError(mActivity.getString(R.string.error_incorrect_password));
mActivity.mPasswordView.requestFocus();
}
}
@Override
protected void onCancelled() {
if (mActivity == null || mActivity.isFinishing())
return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && mActivity.isDestroyed())
return;
mActivity.mAuthTask = null;
mActivity.showProgress(false);
}
}

View File

@@ -0,0 +1,47 @@
package de.sebse.fuplanner.services.kvv;
import android.content.Context;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
public class KVV {
private final HashMap<String, Object> addons = new HashMap<>();
private final KVVListener mListener;
private final Context mContext;
public KVV(KVVListener listener, Context context) {
this.mListener = listener;
this.mContext = context;
}
@NotNull
public Login account() {
return (Login) addAndGet("account", () -> new Login(mListener, mContext));
}
@NotNull
public Modules modules() {
return (Modules) addAndGet("module", () -> new Modules(account(), mListener, mContext));
}
@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);
}
return o;
}
private interface ModuleCreatorInterface {
@NotNull Object create();
}
}

View File

@@ -0,0 +1,18 @@
package de.sebse.fuplanner.services.kvv;
import com.android.volley.NetworkResponse;
import de.sebse.fuplanner.services.kvv.types.LoginToken;
import de.sebse.fuplanner.tools.CustomAccountManager;
public interface KVVListener {
default void onLogin(LoginToken token) {}
default void onLogout() {}
default void onModuleListChange() {}
default void onKVVNetworkResponse(NetworkResponse error) {}
CustomAccountManager getAccountManager();
}

View File

@@ -0,0 +1,128 @@
package de.sebse.fuplanner.services.kvv;
import android.content.Context;
import org.jetbrains.annotations.NotNull;
import androidx.annotation.Nullable;
import de.sebse.fuplanner.services.kvv.types.LoginToken;
import de.sebse.fuplanner.services.fulogin.AccountGeneral;
import de.sebse.fuplanner.tools.CustomAccountManager;
import de.sebse.fuplanner.tools.NetworkCallbackCollector;
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;
public class Login extends HTTPService {
private final KVVListener mListener;
@Nullable private LoginToken mToken;
private boolean mLoginPending = false;
private final NetworkCallbackCollector<LoginToken> mRefreshCallbacks = new NetworkCallbackCollector<>();
Login(KVVListener listener, Context context) {
super(context);
this.mListener = listener;
}
public void restoreOnlineLogin(BooleanInterface callback) {
if (mLoginPending) {
callback.run(false);
return;
}
mLoginPending = true;
LoginToken.load(mListener.getAccountManager(), token -> {
boolean result = setToken(token);
mLoginPending = false;
log.d("loginToken", token != null ? token.toString() : null);
callback.run(result);
});
}
public void isOfflineStoredAvailable(BooleanInterface callback) {
LoginToken.load(mListener.getAccountManager(), token -> {
callback.run(token != null);
});
}
public boolean logout(boolean delete) {
if (mLoginPending)
return false;
if (mToken == null)
return true;
if (delete)
mToken.delete(mListener.getAccountManager());
mToken = null;
return handleCallbacks();
}
public boolean isLoginPending() {
return mLoginPending;
}
public boolean isLoggedIn() {
return mToken != null;
}
public boolean isInOnlineMode() {
return isLoggedIn();
}
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) {
new de.sebse.fuplanner.services.kvv.sync.Login(getContext()).testLoginToken(token, callback, errorCallback);
}
@Nullable public LoginToken getLoginToken() {
return mToken;
}
void refreshLogin(NetworkCallback<LoginToken> success, NetworkErrorCallback error) {
boolean isFirst = mRefreshCallbacks.isEmpty();
mRefreshCallbacks.add(success, error);
if (!isFirst)
return;
CustomAccountManager manager = mListener.getAccountManager();
manager.doInvalidateToken(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_KVV, ignored -> {
restoreOnlineLogin(isRestored -> {
if (isRestored)
testLoginToken(mRefreshCallbacks::responseResponse, mRefreshCallbacks::responseError);
else {
logout(true);
mRefreshCallbacks.responseError(new NetworkError(100180, 403, "Re-login failed!"));
}
});
});
}
private boolean handleCallbacks() {
if (mToken != null) {
mListener.onLogin(mToken);
return true;
} else {
mListener.onLogout();
return false;
}
}
private boolean setToken(@Nullable LoginToken token) {
if (token == null)
return false;
boolean isOnlyRefresh = mToken != null;
mToken = token;
return isOnlyRefresh || handleCallbacks();
}
public interface BooleanInterface {
void run(boolean bool);
}
}

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 final 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,204 @@
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 final 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().isOtherUser(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().isOtherUser(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 if (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) {
log.d(mLogin.isInOnlineMode(), mLogin.getLoginToken());
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);
if (semester_string == null)
continue;
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,222 @@
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.UtilsDate;
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");"20181018155405439"
long modifiedDate = UtilsDate.stringToMillis(site.getString("modifiedDate"), "yyyyMMddHHmmssSSS");
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);
}
private 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;
final Login mLogin;
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 final 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

@@ -0,0 +1,67 @@
package de.sebse.fuplanner.services.kvv.sync;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class KVVContentProvider extends ContentProvider {
private static final String PROVIDER_NAME = "de.sebse.fuplanner.contentprovider.kvv.modules";
private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/modules");
private static final int MODULE = 1;
private static final int MODULE_ID = 2;
private static final UriMatcher uriMatcher = getUriMatcher();
private static UriMatcher getUriMatcher() {
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, "modules", MODULE);
uriMatcher.addURI(PROVIDER_NAME, "modules/#", MODULE_ID);
return uriMatcher;
}
@Override
public boolean onCreate() {
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
switch (uriMatcher.match(uri)) {
case MODULE:
return "vnd.android.cursor.dir/vnd.com."+PROVIDER_NAME;
case MODULE_ID:
return "vnd.android.cursor.item/vnd.com."+PROVIDER_NAME;
}
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
return 0;
}
}

View File

@@ -0,0 +1,87 @@
package de.sebse.fuplanner.services.kvv.sync;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;
import com.android.volley.NetworkResponse;
import java.util.Iterator;
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.CustomAccountManager;
import de.sebse.fuplanner.tools.logging.Logger;
public class KVVSyncAdapter extends AbstractThreadedSyncAdapter {
private KVV mKVV;
private Logger log = new Logger(this);
/**
* Set up the sync adapter
*/
public KVVSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
init(context);
}
/**
* Set up the sync adapter. This form of the
* constructor maintains compatibility with Android 3.0
* and later platform versions
*/
public KVVSyncAdapter(
Context context,
boolean autoInitialize,
boolean allowParallelSyncs) {
super(context, autoInitialize, allowParallelSyncs);
init(context);
}
private void init(Context context) {
mKVV = new KVV(new KVVListener() {
CustomAccountManager accountManager = null;
@Override
public CustomAccountManager getAccountManager() {
if (accountManager == null)
accountManager = new CustomAccountManager(AccountManager.get(context), () -> null);
return accountManager;
}
}, context);
mKVV.account().restoreOnlineLogin(bool -> {});
}
/*
* Specify the code you want to run in the sync adapter. The entire
* sync adapter runs in a background thread, so you don't have to set
* up your own background processing.
*/
@Override
public void onPerformSync(
Account account,
Bundle extras,
String authority,
ContentProviderClient provider,
SyncResult syncResult) {
log.d("start syncing");
/*
* Put the data transfer code here.
*/
mKVV.modules().list().recv(success -> {
Iterator<Modules.Module> iterator = success.latestSemesterIterator();
while (iterator.hasNext()) {
Modules.Module module = iterator.next();
log.d("sync module", module.title);
mKVV.modules().details().recv(module, success1 -> {
if (success1.second)
log.d("Sync Successful for Module '"+module.title+"'!");
}, log::e, true);
}
}, log::e, true);
}
}

View File

@@ -0,0 +1,24 @@
package de.sebse.fuplanner.services.kvv.sync;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class KVVSyncService extends Service {
private static final Object sSyncAdapterLock = new Object();
private static KVVSyncAdapter sSyncAdapter = null;
@Override
public void onCreate() {
synchronized (sSyncAdapterLock) {
if (sSyncAdapter == null)
sSyncAdapter = new KVVSyncAdapter(getApplicationContext(), true);
}
}
@Override
public IBinder onBind(Intent intent) {
return sSyncAdapter.getSyncAdapterBinder();
}
}

View File

@@ -1,72 +1,44 @@
package de.sebse.fuplanner.services.KVV;
package de.sebse.fuplanner.services.kvv.sync;
import android.content.Context;
import org.jetbrains.annotations.NotNull;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.sebse.fuplanner.services.KVV.types.LoginToken;
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.
*/
class KVVLogin extends HTTPService {
private final Context mContext;
private LoginToken loginToken;
KVVLogin(Context context) {
super(context, false);
this.mContext = context;
try {
this.loginToken = LoginToken.load(context);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
public class Login extends HTTPService {
public Login(Context context) {
super(context);
}
public LoginToken easyLogin() {
return this.loginToken;
}
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 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;
}
} else {
doLogin(username, password, token -> {
this.loginToken = token;
testLogin(this.loginToken, success -> callback.onResponse(this.loginToken), errorCallback);
}, errorCallback);
}
}
public void deleteOffline() {
if (this.loginToken != null)
this.loginToken.delete(mContext);
}
public void saveOffline() throws IOException {
if (this.loginToken != null)
this.loginToken.save(mContext);
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!")));
}
@@ -74,7 +46,16 @@ class KVVLogin extends HTTPService {
private void doLogin(String username, String password, NetworkCallback<LoginToken> callback, NetworkErrorCallback error) {
public void doLogin(String username, String password, NetworkCallback<LoginToken> callback, NetworkErrorCallback error) {
startKVVSession(success -> {
String kvvJSESSIONID = success.get("JSESSIONID");
getSAMLRequest(kvvJSESSIONID, success1 -> startIdentSession(success1.get("Location"), success11 -> {
@@ -92,21 +73,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 +199,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 +216,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 +304,3 @@ class KVVLogin extends HTTPService {
return result;
}
}

View File

@@ -0,0 +1,95 @@
package de.sebse.fuplanner.services.kvv.types;
import com.google.android.gms.common.internal.Objects;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashSet;
import androidx.annotation.NonNull;
import de.sebse.fuplanner.tools.UtilsJson;
public class Announcement implements UtilsJson.JsonConvertible {
private final String id;
private final String title;
private final String body;
private final String createdBy;
private final long createdOn;
private final ArrayList<String> urls;
public Announcement(String id, String title, String body, String createdBy, long createdOn, ArrayList<String> urls) {
this.id = id;
this.title = title;
this.body = body;
this.createdBy = createdBy;
this.createdOn = createdOn;
this.urls = urls;
}
public Announcement(JSONObject json) throws JSONException {
this.id = json.getString("id");
this.title = json.getString("title");
this.body = json.getString("body");
this.createdBy = json.getString("createdBy");
this.createdOn = json.getLong("createdOn");
ArrayList<String> urls = new ArrayList<>();
for (String url: UtilsJson.jsonArrayToIterableString(json.getJSONArray("urls"))) {
urls.add(url);
}
this.urls = urls;
}
public ArrayList<String> getUrls() {
return urls;
}
private String getId() {
return id;
}
public String getTitle() {
return title;
}
public String getBody() {
return body;
}
public String getCreatedBy() {
return createdBy;
}
public long getCreatedOn() {
return createdOn;
}
@NonNull
@Override
public String toString() {
return "ID: "+getId()+
"\nTitle: "+getTitle()+
"\nBody length: "+getBody().length()+
"\nCreated by: "+getCreatedBy()+
"\nCreated on: "+getCreatedOn()+
"\nURLs: "+getUrls().toString();
}
@Override
public int hashCode() {
return Objects.hashCode(getId(), getBody(), getCreatedBy(), getCreatedOn(), getTitle(), getUrls());
}
@Override
public JSONObject toJSONObject() throws JSONException {
JSONObject json = new JSONObject();
json.put("id", id);
json.put("title", title);
json.put("body", body);
json.put("createdBy", createdBy);
json.put("createdOn", createdOn);
json.put("urls", UtilsJson.collectionToJson(urls));
return json;
}
}

View File

@@ -1,14 +1,19 @@
package de.sebse.fuplanner.services.KVV.types;
package de.sebse.fuplanner.services.kvv.types;
import com.google.android.gms.common.internal.Objects;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.Serializable;
import java.util.ArrayList;
public class Assignment implements Serializable {
import androidx.annotation.NonNull;
import de.sebse.fuplanner.tools.UtilsJson;
public class Assignment implements UtilsJson.JsonConvertible {
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 +21,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;
}
@@ -47,11 +49,28 @@ public class Assignment implements Serializable {
return instructions;
}
@NonNull
@Override
public String toString() {
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());
}
@Override
public JSONObject toJSONObject() throws JSONException {
JSONObject json = new JSONObject();
json.put("id", id);
json.put("title", title);
json.put("dueTime", dueTime);
json.put("createdBy", UtilsJson.collectionToJson(urls));
json.put("instructions", instructions);
return json;
}
}

View File

@@ -1,4 +1,4 @@
package de.sebse.fuplanner.services.KVV.types;
package de.sebse.fuplanner.services.kvv.types;
import de.sebse.fuplanner.tools.DateSortedList;

View File

@@ -1,13 +1,19 @@
package de.sebse.fuplanner.services.KVV.types;
package de.sebse.fuplanner.services.kvv.types;
import com.google.android.gms.common.internal.Objects;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import androidx.annotation.NonNull;
import de.sebse.fuplanner.tools.ColorRGB;
import de.sebse.fuplanner.tools.UtilsJson;
public class Event implements Serializable {
public class Event implements UtilsJson.JsonConvertible {
private final String siteId;
private final String id;
private final String type;
@@ -104,6 +110,7 @@ public class Event implements Serializable {
}
}
@NonNull
@Override
public String toString() {
return "ID: "+getId()+
@@ -111,4 +118,22 @@ public class Event implements Serializable {
"\nStart Date: "+getStartDate()+
"\nEnd date: "+getEndDate();
}
@Override
public int hashCode() {
return Objects.hashCode(getId(), getType(), getStartDate(), getEndDate(), getTitle(), getLocation());
}
@Override
public JSONObject toJSONObject() throws JSONException {
JSONObject json = new JSONObject();
json.put("siteId", siteId);
json.put("id", id);
json.put("type", type);
json.put("title", title);
json.put("duration", duration);
json.put("firstTime", firstTime);
json.put("location", location);
return json;
}
}

View File

@@ -1,4 +1,4 @@
package de.sebse.fuplanner.services.KVV.types;
package de.sebse.fuplanner.services.kvv.types;
import de.sebse.fuplanner.tools.DateSortedList;
@@ -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

@@ -0,0 +1,55 @@
package de.sebse.fuplanner.services.kvv.types;
import com.google.android.gms.common.internal.Objects;
import org.json.JSONException;
import org.json.JSONObject;
import androidx.annotation.NonNull;
import de.sebse.fuplanner.tools.UtilsJson;
public class Grade implements UtilsJson.JsonConvertible {
private final String itemName;
private final double grade;
private final double maxPoints;
public Grade(String itemName, double points, double maxPoints) {
this.itemName = itemName;
this.grade = points;
this.maxPoints = maxPoints;
}
public double getMaxPoints() {
return maxPoints;
}
public double getPoints() {
return grade;
}
public String getItemName() {
return itemName;
}
@NonNull
@Override
public String toString() {
return "Name: "+getItemName()+
"\nPoints: "+ getPoints()+
"\nMax points: "+getMaxPoints();
}
@Override
public int hashCode() {
return Objects.hashCode(getItemName(), getPoints(), getMaxPoints());
}
@Override
public JSONObject toJSONObject() throws JSONException {
JSONObject json = new JSONObject();
json.put("itemName", itemName);
json.put("grade", grade);
json.put("maxPoints", maxPoints);
return json;
}
}

View File

@@ -0,0 +1,136 @@
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;
public class GroupedEvents extends EventList {
private final ArrayList<Group> arrayList = new ArrayList<>();
private int skippedDayCount = 0;
public boolean add(Event event) {
boolean result = super.add(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 result;
}
}
arrayList.add(new Group(event));
return result;
}
public List<Group> getGroups() {
return Collections.unmodifiableList(arrayList);
}
public int getSkippedDayCount() {
return skippedDayCount;
}
public class Group {
private long firstDate;
private long lastDate;
private final ArrayList<Long> skippedDates;
private final int dayOfWeek;
private final long startTime;
private final 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);
skippedDayCount += 1;
firstDate = addDays(firstDate, -7);
}
} else if (date > lastDate) {
lastDate = addDays(lastDate, 7);
while (lastDate < date) {
skippedDates.add(lastDate);
skippedDayCount += 1;
lastDate = addDays(lastDate, 7);
}
} else {
skippedDates.remove(date);
skippedDayCount -= 1;
}
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

@@ -0,0 +1,85 @@
package de.sebse.fuplanner.services.kvv.types;
import com.google.android.gms.common.internal.Objects;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import androidx.annotation.NonNull;
import de.sebse.fuplanner.tools.UtilsJson;
public class Lecturer implements UtilsJson.JsonConvertible {
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);
Matcher matcher = pattern.matcher(parsableString);
if (!matcher.find()) {
throw new NoSuchFieldException();
}
this.firstName = matcher.group(1);
this.surname = matcher.group(2);
this.mail = matcher.group(3);
this.isResponsible = matcher.group(4).equals("true");
}
public Lecturer(JSONObject json) throws JSONException {
this.firstName = json.getString("firstName");
this.surname = json.getString("surname");
this.mail = json.getString("mail");
this.isResponsible = json.getBoolean("isResponsible");
}
public String getFirstName() {
return firstName;
}
public String getSurname() {
return surname;
}
public String getMail() {
return mail;
}
public boolean isResponsible() {
return isResponsible;
}
public String getName() {
return getFirstName() + " " + getSurname();
}
public String getNameShort() {
return getFirstName().substring(0, 1) + ". " + getSurname();
}
@NonNull
@Override
public String toString() {
return "First name: "+ getFirstName()+
"\nSurname: "+getSurname()+
"\nMail: "+getMail();
}
@Override
public int hashCode() {
return Objects.hashCode(getFirstName(), getSurname(), getMail(), isResponsible());
}
@Override
public JSONObject toJSONObject() throws JSONException {
JSONObject json = new JSONObject();
json.put("firstName", firstName);
json.put("surname", surname);
json.put("mail", mail);
json.put("isResponsible", isResponsible);
return json;
}
}

View File

@@ -0,0 +1,148 @@
package de.sebse.fuplanner.services.kvv.types;
import android.content.Context;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.sebse.fuplanner.services.fulogin.AccountGeneral;
import de.sebse.fuplanner.tools.CustomAccountManager;
import de.sebse.fuplanner.tools.logging.Logger;
/**
* Created by sebastian on 29.10.17.
*/
public class LoginToken {
private final String username;
private final String shibsessionKey;
private final String shibsessionName;
private final String JSESSIONID;
@Nullable private String fullName;
@Nullable private String email;
public LoginToken(String username, String shibsessionKey, String shibsessionName, String JSESSIONID) {
this.username = username;
this.shibsessionKey = shibsessionKey;
this.shibsessionName = shibsessionName;
this.JSESSIONID = JSESSIONID;
}
@Nullable
public static void load(CustomAccountManager manager, LoginTokenInterface callback) {
if (!manager.hasAccounts(AccountGeneral.ACCOUNT_TYPE)) {
callback.run(null);
return;
}
manager.getTokenByType(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_KVV, tokenString -> {
if (tokenString == null) {
callback.run(null);
return;
}
callback.run(LoginToken.fromJsonString(tokenString));
});
}
public void delete(CustomAccountManager manager) {
manager.deleteAccount(AccountGeneral.ACCOUNT_TYPE);
}
public void setAdditionals(String fullName, String email) {
this.fullName = fullName;
this.email = email;
}
public String getUsername() {
return username;
}
private String getShibsessionKey() {
return shibsessionKey;
}
private String getShibsessionName() {
return shibsessionName;
}
private String getJSESSIONID() {
return JSESSIONID;
}
@Nullable
public String getFullName() {
return fullName;
}
@Nullable
public String getEmail() {
return email;
}
public HashMap<String, String> getCookies() {
HashMap<String, String> cookies = new HashMap<>();
cookies.put("JSESSIONID", getJSESSIONID());
cookies.put(getShibsessionKey(), getShibsessionName());
cookies.put("pasystem_timezone_ok", "true");
return cookies;
}
public boolean isOtherUser(String username) {
return !this.getUsername().equals(username);
}
@NonNull
@Override
public String toString() {
StringBuilder result = new StringBuilder();
HashMap<String, String> cookies = this.getCookies();
for (String header: cookies.keySet()) {
result.append(header).append("=").append(cookies.get(header)).append(";");
}
return result.substring(0, result.length()-1);
}
public String toJsonString() {
JSONObject json = new JSONObject();
try {
json.put("username", username);
json.put("shibsessionKey", shibsessionKey);
json.put("shibsessionName", shibsessionName);
json.put("JSESSIONID", JSESSIONID);
json.put("fullName", fullName);
json.put("email", email);
} catch (JSONException e) {
return null;
}
return json.toString();
}
public static LoginToken fromJsonString(String tokenString) {
try {
JSONObject json = new JSONObject(tokenString);
LoginToken token = new LoginToken(
json.getString("username"),
json.getString("shibsessionName"),
json.getString("shibsessionName"),
json.getString("JSESSIONID"));
if (!json.isNull("fullName"))
token.setAdditionals(
json.getString("fullName"),
json.getString("email")
);
return token;
} catch (JSONException e) {
e.printStackTrace();
return null;
}
}
public interface LoginTokenInterface {
void run(LoginToken token);
}
}

View File

@@ -0,0 +1,317 @@
package de.sebse.fuplanner.services.kvv.types;
import android.content.Context;
import com.google.android.gms.common.internal.Objects;
import org.jetbrains.annotations.NotNull;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.sebse.fuplanner.tools.UtilsJson;
import static de.sebse.fuplanner.tools.UtilsJson.collectionToJson;
import static de.sebse.fuplanner.tools.UtilsJson.jsonArrayToIterableObject;
/**
* Created by sebastian on 29.10.17.
*/
public class Modules implements Iterable<Modules.Module> {
private SortedListModule list;
private final String mUsername;
private long mLoadTime;
//private transient Logger log = new Logger(this);
private static final String FILE_NAME = "ModuleListSaving";
private static final String FILE_NAME_TIMESTAMP = "ModuleListSavingTimestamp";
public Modules(String username) {
this.mUsername = username;
this.list = new SortedListModule();
}
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();
}
@NonNull
@Override
public Iterator<Module> iterator() {
return this.list.iterator();
}
public Iterator<Module> latestSemesterIterator() {
return this.list.filteredIterator(this.list.getLatestSemester());
}
public int size() {
return this.list.size();
}
public Module get(String id) {
return this.list.getById(id);
}
public Module getByIndex(int index) {
return this.list.get(index);
}
public static Modules load(Context context) throws IOException {
String ret = "";
InputStream inputStream = context.openFileInput(FILE_NAME);
if (inputStream != null) {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String receiveString;
StringBuilder stringBuilder = new StringBuilder();
while ((receiveString = bufferedReader.readLine()) != null) {
stringBuilder.append(receiveString);
}
inputStream.close();
ret = stringBuilder.toString();
}
try {
JSONObject json = new JSONObject(ret);
Modules mod = new Modules(json.getString("username"));
JSONArray arr = json.getJSONArray("modules");
for (JSONObject jsonObject: jsonArrayToIterableObject(arr)) {
mod.list.add(new Module(jsonObject));
}
FileInputStream ofi = context.openFileInput(FILE_NAME_TIMESTAMP);
ObjectInputStream inputStream1 = new ObjectInputStream(ofi);
mod.mLoadTime = inputStream1.readLong();
return mod;
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
public void save(Context context) throws IOException {
JSONObject json = new JSONObject();
try {
json.put("username", mUsername);
json.put("modules", UtilsJson.collectionToJson(list));
OutputStreamWriter osw = new OutputStreamWriter(context.openFileOutput(FILE_NAME, Context.MODE_PRIVATE));
osw.write(json.toString());
osw.close();
} catch (JSONException e) {
e.printStackTrace();
}
mLoadTime = System.currentTimeMillis();
FileOutputStream ofo = context.openFileOutput(FILE_NAME_TIMESTAMP, Context.MODE_PRIVATE);
ObjectOutputStream outputStream = new ObjectOutputStream(ofo);
outputStream.writeLong(mLoadTime);
}
public void delete(Context context) {
context.deleteFile(FILE_NAME);
}
public String getUsername() {
return mUsername;
}
public boolean updateList(Modules modules) {
SortedListModule old = this.list;
this.list = modules.list;
boolean isChanged = false;
for (Module oldModule : old) {
Module newModule = this.list.getById(oldModule.getID());
if (newModule != null) {
if (!isChanged && !hashEquals(oldModule, newModule))
isChanged = true;
newModule.announcements = oldModule.announcements;
newModule.assignments = oldModule.assignments;
newModule.events = oldModule.events;
newModule.gradebook = oldModule.gradebook;
newModule.resources = oldModule.resources;
} else {
isChanged = true;
}
}
return isChanged;
}
private boolean hashEquals(Module o1, Module o2) {
if (o1 == null && o2 == null)
return true;
else if (o1 == null || o2 == null)
return false;
else
return o1.hashCode() == o2.hashCode();
}
public static class Module implements UtilsJson.JsonConvertible {
@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<Grade> gradebook;
@Nullable public ArrayList<Resource> resources;
@Override
public int hashCode() {
return Objects.hashCode(semester, lvNumber, title, lecturer, type, description, ID);
}
@Override
public JSONObject toJSONObject() throws JSONException {
JSONObject json = new JSONObject();
json.put("semester", semester != null ? semester.toJSONObject() : null);
json.put("lvNumber", collectionToJson(lvNumber));
json.put("title", title);
json.put("lecturer", collectionToJson(lecturer));
json.put("type", type);
json.put("description", description);
json.put("ID", ID);
json.put("announcements", collectionToJson(announcements));
json.put("assignments", collectionToJson(assignments));
json.put("events", collectionToJson(events));
json.put("gradebook", collectionToJson(gradebook));
json.put("resources", collectionToJson(resources));
return json;
}
public float getGradebookPercent(){
float maxPoint = 0;
float userPoint = 0;
if (gradebook != null) {
for (Grade g : gradebook){
maxPoint += g.getMaxPoints();
userPoint += g.getPoints();
}
}
if (maxPoint == 0)
return 0;
return userPoint/maxPoint;
}
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 = new ArrayList<>(lecturer);
this.type = type;
this.description = description;
this.ID = ID;
}
private Module(@NotNull JSONObject json) throws JSONException {
HashSet<String> lv = new HashSet<>();
for (String number: UtilsJson.jsonArrayToIterableString(json.getJSONArray("lvNumber"))) {
lv.add(number);
}
LinkedHashSet<Lecturer> lecturer = new LinkedHashSet<>();
for (JSONObject l: UtilsJson.jsonArrayToIterableObject(json.getJSONArray("lecturer"))) {
lecturer.add(new Lecturer(l));
}
JSONObject obj = json.optJSONObject("semester");
this.semester = obj != null ? new Semester(obj) : null;
this.lvNumber = lv;
this.title = json.getString("title");
this.lecturer = new ArrayList<>(lecturer);
this.type = json.getString("type");
this.description = json.getString("description");
this.ID = json.getString("ID");
JSONArray arr = json.optJSONArray("announcements");
if (obj != null) {
ArrayList<Announcement> announce = new ArrayList<>();
for (JSONObject l: UtilsJson.jsonArrayToIterableObject(arr)) {
announce.add(new Announcement(l));
}
this.announcements = announce;
}
arr = json.optJSONArray("assignments");
if (obj != null) {
AssignmentList assign = new AssignmentList();
for (JSONObject l: UtilsJson.jsonArrayToIterableObject(arr)) {
assign.add(new Assignment(l));
}
this.assignments = assign;
}
arr = json.optJSONArray("events");
if (obj != null) {
EventList events = new EventList();
for (JSONObject l: UtilsJson.jsonArrayToIterableObject(arr)) {
events.add(new Event(l));
}
this.events = events;
}
arr = json.optJSONArray("gradebook");
if (obj != null) {
ArrayList<Grade> grades = new ArrayList<>();
for (JSONObject l: UtilsJson.jsonArrayToIterableObject(arr)) {
grades.add(new Grade(l));
}
this.gradebook = grades;
}
arr = json.optJSONArray("resources");
if (obj != null) {
ArrayList<Resource> res = new ArrayList<>();
for (JSONObject l: UtilsJson.jsonArrayToIterableObject(arr)) {
if (l.getString("restype").equals("file"))
res.add(new Resource.File(l));
else
res.add(new Resource.Folder(l));
}
this.resources = res;
}
}
@NonNull
public String getID() {
return ID;
}
@NonNull
@Override
public String toString() {
return "Semester: "+semester+
"\nlvNumber: "+lvNumber.toString()+
"\ntitle: "+title+
"\nlecturer: "+lecturer.toString()+
"\ntype: "+type+
"\nID: "+ID;
}
}
}

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