408 Commits

Author SHA1 Message Date
hsteller
2585fbc01b versionName "1.5.16" -> versionName "1.5.16_2" 2019-10-12 01:50:12 +00:00
Hendrik Steller
e7e8da5db3 an URL contained one slash too many 2019-10-11 21:34:05 +02:00
Hendrik Steller
44ca33ae0f Version 39 (1.5.16) 2019-10-11 18:19:27 +02:00
Hendrik Steller
a8817babf8 Renamed kvv.imp.fu-berlin.de->mycampus.imp.fu-berlin.de
extracted URL into constant
2019-10-11 18:06:02 +02:00
Caesar2011
7a39a0b854 Version 38 (1.5.15) 2019-02-15 23:12:42 +01:00
Caesar2011
c87241da65 Name in Navigation Header 2019-02-15 23:12:14 +01:00
Caesar2011
6a837d8719 Version 37 reconstructed #2 2019-02-15 18:04:50 +01:00
Caesar2011
8655dc9927 Version 37 reconstructed 2019-02-15 17:56:29 +01:00
Caesar2011
e31d6cd042 Version 36 (1.5.13) 2019-02-11 12:27:42 +01:00
Caesar2011
8f2052180d Lecturer feching delayed #2 2019-02-11 12:26:43 +01:00
Caesar2011
e366e55143 Lecturer feching delayed 2019-02-11 11:16:41 +01:00
Caesar2011
34db5bf901 Event number cache 2019-02-11 00:56:52 +01:00
Caesar2011
ab77ad4974 Logs removed 2019-02-11 00:48:30 +01:00
Caesar2011
d098093c8a HTTP service implemented 2019-02-11 00:20:38 +01:00
Caesar2011
b4e11e3d10 Unbind KVV service 2019-02-10 20:25:44 +01:00
Caesar2011
6048714563 Version 35 (1.5.12) 2019-02-09 01:25:09 +01:00
Caesar2011
30f1cc9a67 Unused imports 2019-02-09 01:23:03 +01:00
Caesar2011
489212c26a KVV as a service to connect SyncAdapter with MainActivity 2019-02-09 01:14:20 +01:00
Caesar2011
28e99f1fa5 More error proof events 2019-02-08 23:44:48 +01:00
Caesar2011
9062035956 cache vvNumber matching 2019-02-08 23:05:57 +01:00
Caesar2011
9769365e19 Cache KVV courses #2 2019-02-08 21:35:00 +01:00
Caesar2011
2441286165 Merge remote-tracking branch 'origin/master' 2019-02-08 20:58:09 +01:00
Sebastian Seedorf
14d5ca6759 Cache KVV courses 2019-02-08 16:29:10 +01:00
Sebastian Seedorf
c7fe1dc3f7 KVV not available implemented 2019-02-08 15:18:10 +01:00
Sebastian Seedorf
72216ea7d6 Load module list and parts only if logged in 2019-02-08 15:17:28 +01:00
Caesar2011
24dcb0e9e0 Version 34 (1.5.11) 2019-02-08 08:56:48 +01:00
Caesar2011
e8c3ca0b0e NumberFormatException 2019-02-08 08:56:19 +01:00
Caesar2011
261878b6e3 Tablet support 2019-02-07 23:36:48 +01:00
Caesar2011
041c46284b NullPointerException and NumberFormatException 2019-02-07 22:47:51 +01:00
Caesar2011
0776447d3b Version 33 (1.5.10) 2019-02-07 22:06:29 +01:00
Caesar2011
a6b5469058 Blackboard "Refresh failed" Fix 2019-02-07 21:53:40 +01:00
Caesar2011
b8be51d6fb Module save fix 2019-02-07 20:36:44 +01:00
Caesar2011
019bdf80ce Schedule fragment for old BB modules cleaned up 2019-02-07 18:47:57 +01:00
Caesar2011
1041144b4f Semester length fix 2019-02-07 18:19:04 +01:00
Caesar2011
5f28f64efc No lecturers fix 2019-02-07 18:15:50 +01:00
Caesar2011
8690aa4a5c App compatibility for Kitkat devices 2019-02-07 00:21:52 +01:00
Caesar2011
9e3fa30366 Version 32 (1.5.9) 2019-02-06 23:54:33 +01:00
Caesar2011
e54a2c8957 Logs removed 2019-02-06 23:54:14 +01:00
Caesar2011
8cdb52417b Fixed sync failure when login not loaded 2019-02-06 23:42:20 +01:00
Caesar2011
40e4612f27 Android Kitkat (19) and below does not support TLS 1.2 2019-02-06 23:25:43 +01:00
Caesar2011
7b85147d74 setNavigationSelection error fixed when ModulePart appended to fragment data 2019-02-06 21:51:29 +01:00
Caesar2011
94ea032389 Blackboard Gradebook (untested) 2019-02-06 20:42:00 +01:00
Caesar2011
94e05ef8ba Added Blackboard Course Cache 2019-02-06 16:08:34 +01:00
Caesar2011
2ec6a3c08d Version 31 (1.5.8) 2019-02-06 12:13:21 +01:00
Caesar2011
123da07d35 Merge remote-tracking branch 'origin/master' 2019-02-06 12:05:32 +01:00
Caesar2011
31850e100d Expandable CardView Empty 2019-02-06 11:37:39 +01:00
Caesar2011
c589889db4 Version 30 (1.5.7) 2019-02-06 09:48:21 +01:00
Caesar2011
8f026f95d8 Sync frequency bug fixed 2019-02-06 09:47:04 +01:00
Caesar2011
c6d9a288a6 Version 29 (1.5.6) 2019-02-06 01:47:21 +01:00
Caesar2011
74e5b16af6 NullPointerException 2019-02-06 01:46:58 +01:00
Caesar2011
8d0f3a83cf Code cleanup 2019-02-06 01:39:05 +01:00
Caesar2011
1414ad8a0e Remove FU cookie on logout 2019-02-06 01:10:30 +01:00
Caesar2011
1d08534f4e Do not sync while login not finished 2019-02-06 00:26:35 +01:00
Caesar2011
e305605583 Lecturer Load Fix 2019-02-05 23:59:09 +01:00
Caesar2011
1e6ef4d6fc Version 28 (1.5.5) 2019-02-05 23:12:37 +01:00
Caesar2011
de9f6a5540 Blackboard event pulling error handling 2019-02-05 23:05:58 +01:00
Caesar2011
25004f8784 Reuse testLoginToken result if set in last 30 secounds 2019-02-05 22:53:42 +01:00
Caesar2011
d78948d349 Login via settings working 2019-02-05 21:07:35 +01:00
Caesar2011
1bbb09499d Module description 2019-02-05 20:21:14 +01:00
Caesar2011
c086a6c6b2 Login code fix 2019-02-05 19:31:59 +01:00
Caesar2011
c9daa8b01b Version 27 (1.5.4) 2019-02-05 17:58:37 +01:00
Caesar2011
91eb7acd72 Lecturer Storage Test 2019-02-05 17:57:21 +01:00
Caesar2011
631bd0dd51 tiny trace log 2019-02-05 17:46:06 +01:00
Caesar2011
c5fc592169 Notification Click Handling #2 2019-02-05 17:41:43 +01:00
Caesar2011
d67542d93a Notification Click Handling 2019-02-05 17:20:57 +01:00
Caesar2011
42f460a2e1 Event Notification Fix 2019-02-05 16:51:42 +01:00
Caesar2011
bde4b0cbd0 Shortcuts on overview fix 2019-02-05 16:36:11 +01:00
Caesar2011
2801261400 Lecturer Storage implemented 2019-02-05 16:35:38 +01:00
Caesar2011
46429d3afe Removed mavenLocal 2019-02-05 15:50:24 +01:00
Sebastian Seedorf
d5d867f4b5 BB Lecturers (not tested) 2019-02-04 17:40:25 +01:00
Caesar2011
f0d090fb8d Better event listing 2019-02-04 02:14:13 +01:00
Caesar2011
a8997a2e99 Grouping events by location 2019-02-04 02:14:13 +01:00
Caesar2011
7ae8a13c9f Fixed double re-login (again) 2019-02-03 23:32:42 +01:00
Caesar2011
a94ce6c755 Notification click implemented 2019-02-03 23:01:44 +01:00
Caesar2011
5bf6573baa Small bug fixes 2019-02-03 21:09:43 +01:00
Caesar2011
0f6a11cf5f Double login fixed 2019-02-03 20:25:01 +01:00
Caesar2011
79f76ccefa Code clean up 2019-02-03 19:56:51 +01:00
Caesar2011
593fb78ef2 Reuse FU identity session 2019-02-03 19:41:40 +01:00
Caesar2011
a6b34f7814 More efficient re-login 2019-02-03 17:11:58 +01:00
Caesar2011
3380e79232 Resource Sync Notification Fix 2019-02-03 16:18:14 +01:00
Caesar2011
d108d430df Version 26 2019-02-01 23:53:12 +01:00
Caesar2011
c7dd46a1a2 Blackboard Resources links 2019-02-01 23:51:35 +01:00
Caesar2011
d5d4b680e8 Merge remote-tracking branch 'origin/master' 2019-02-01 20:41:13 +01:00
Sebastian Seedorf
331c3059bb Notification activity stack fix 2019-02-01 17:43:17 +01:00
Sebastian Seedorf
011e31ac78 Removed unused depencencies 2019-02-01 15:53:07 +01:00
Caesar2011
7f6e794fc8 Blackboard Resources (no links) 2019-02-01 01:50:04 +01:00
Caesar2011
35bb4e1629 Gradebook not available 2019-01-31 21:58:24 +01:00
Caesar2011
48b034b63f More robust blackboard module checking 2019-01-31 21:30:01 +01:00
Caesar2011
ce322bd3ca Login Error Handeling 2019-01-31 20:57:54 +01:00
Caesar2011
c544adbd4b Added KVV projects 2019-01-30 15:12:33 +01:00
Caesar2011
9fe2cf32fd Version 25 2019-01-30 14:42:32 +01:00
Caesar2011
10dc391606 Blackboard Event support 2019-01-30 14:41:05 +01:00
Caesar2011
fac7cb238b Prepare sync if KVV is not supported 2019-01-30 09:49:02 +01:00
Caesar2011
d60ea357d8 Removed Login Error if Blackboard is unavailable 2019-01-30 09:44:24 +01:00
Caesar2011
7c36db2bee Version 24 2019-01-29 18:17:15 +01:00
Caesar2011
c85e510722 Login improved 2019-01-29 18:16:01 +01:00
Caesar2011
740cc192dd Reduced Blackboard Data Usage 2019-01-29 01:46:40 +01:00
Caesar2011
64624ca784 Version 23 2019-01-28 17:40:33 +01:00
Caesar2011
a25b387756 Selectable text (assignment/announcement/news) 2019-01-28 17:39:47 +01:00
Caesar2011
7651eb47e2 Removed unused methods and imports 2019-01-28 17:38:33 +01:00
Caesar2011
86d8078aca "Blackboard never used" check introduced 2019-01-28 17:30:08 +01:00
Caesar2011
068344ff5c Added Blackboard Announcements 2019-01-28 02:21:41 +01:00
Caesar2011
c15fc1339f Added Blackboard Courses to List 2019-01-28 02:03:31 +01:00
Caesar2011
72234d2439 Simplified Login Token 2019-01-27 15:24:12 +01:00
Caesar2011
79305fb5bb Added docs 2019-01-27 15:16:58 +01:00
Joshua
e9ef587821 Merge remote-tracking branch 'origin/master' 2019-01-22 17:08:21 +01:00
Joshua
801b90e489 Modulbeschreibung markierbar 2019-01-22 17:07:55 +01:00
Caesar2011
0610140cf6 Reload If outdated before auto sync 2019-01-17 19:05:31 +01:00
Caesar2011
afcd6afd00 Version 22 2019-01-17 18:48:16 +01:00
Caesar2011
3224946f43 Sync frequency adjustable 2019-01-17 18:46:32 +01:00
Caesar2011
7fe38cd60f Changed event identifier: added type 2019-01-17 17:44:45 +01:00
Caesar2011
31cea12d16 Updated Gradle 2019-01-16 22:44:58 +01:00
Caesar2011
b8fe45e75e Changed event identifier from Id to StartDate 2019-01-16 22:44:35 +01:00
Caesar2011
729d3165f7 Event end time optimized 2019-01-14 14:04:30 +01:00
Caesar2011
6f1705ccf4 Handle register sync properly 2019-01-14 13:53:42 +01:00
Caesar2011
6a56573c6b Version 21 2019-01-14 13:21:32 +01:00
Caesar2011
322cf6f8a5 UnknownFormatException while syncing fixed 2019-01-14 13:19:56 +01:00
Caesar2011
6d30050eae Unused logs and permissions removed 2019-01-14 12:33:34 +01:00
Caesar2011
45a5513b54 Version 20 2019-01-09 15:52:51 +01:00
Caesar2011
e98e9f5e2f Merge branch 'kvvservice2' 2019-01-09 15:52:28 +01:00
Caesar2011
7d803777f8 Notifications added 2019-01-09 15:52:09 +01:00
Caesar2011
7cc0709133 New KVV login working 2019-01-09 14:58:08 +01:00
Caesar2011
21d247052d New KVV login preparation (works only via VPN or internal) 2019-01-09 01:22:13 +01:00
Caesar2011
57853eccf1 Sync notifications for announcements 2019-01-07 23:34:48 +01:00
Caesar2011
ceccd77f18 Periodic sync, reload from memory if outdated 2019-01-07 21:14:59 +01:00
Caesar2011
ae53e94108 Periodic sync, reload from memory if outdated 2019-01-07 14:59:48 +01:00
Caesar2011
786c001bbd unfinished code 2019-01-07 13:28:59 +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
d4275aad90 Version 19 2018-12-19 15:58:12 +01:00
Caesar2011
f9fb7e8351 Merge branch 'kvvservice' 2018-12-19 15:55:39 +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
889007ab42 Version 18 2018-12-18 15:50:54 +01:00
Caesar2011
cde5fc3582 Login Testing 2018-12-17 16:14:50 +01:00
Joshua
c549bb39a4 fehler bei anzeigen von Ordnern mit umlauten behoben 2018-12-17 16:06:41 +01:00
Caesar2011
1e0b2a8952 Version 17 2018-12-06 13:38:26 +01:00
Caesar2011
c807dda73c Activity Active Bugfix 2018-12-06 13:32:39 +01:00
Joshua
54196f3794 Projekt Fehler behoben 2018-12-04 16:43:32 +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
261 changed files with 11145 additions and 3678 deletions

5
.gitignore vendored
View File

@@ -1,9 +1,10 @@
*.iml *.iml
.gradle .gradle
/local.properties /local.properties
/.idea/workspace.xml /.idea/*
/.idea/libraries /.idea - PC/*
.DS_Store .DS_Store
/build /build
/captures /captures
.externalNativeBuild .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 { android {
compileSdkVersion 28 compileSdkVersion 28
buildToolsVersion '27.0.3' buildToolsVersion '28.0.3'
defaultConfig { defaultConfig {
applicationId "de.sebse.fuplanner" applicationId "de.sebse.fuplanner"
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 28 targetSdkVersion 28
versionCode 1 versionCode 39
versionName "1.0" versionName "1.5.16_2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
release { release {
@@ -27,28 +27,15 @@ android {
dependencies { dependencies {
implementation 'com.android.support:recyclerview-v7:28.0.0-rc01'
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { implementation 'androidx.recyclerview:recyclerview:1.0.0'
exclude group: 'com.android.support', module: 'support-annotations' implementation 'androidx.preference:preference:1.0.0'
}) implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3'
implementation 'com.android.support:appcompat-v7:28.0.0-rc01' implementation 'com.android.volley:volley:1.1.0'
implementation 'com.android.support:preference-v7:28.0.0-rc01' implementation 'com.github.Cutta:TagView:1.3'
implementation 'com.android.support:design:28.0.0-rc01' implementation 'com.google.android.material:material:1.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.2' implementation 'com.google.android.gms:play-services-base:16.1.0'
implementation 'com.android.volley:volley:1.0.0'
//noinspection GradleDependency
implementation 'com.google.android.gms:play-services-auth:15.0.0'
implementation 'com.android.support:support-v4:28.0.0-rc01'
testImplementation 'junit:junit:4.12'
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0-rc01'
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 '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' implementation 'com.ms-square:expandableTextView:0.1.4'
// https://github.com/bignerdranch/expandable-recycler-view implementation 'org.jetbrains:annotations-java5:15.0'
implementation 'com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1' //implementation 'com.github.quivr:android-week-view:2.0.2' //com.github.alamkanak:android-week-view:1.2.6
implementation files('libs/jericho-html-3.4.jar')
} }

View File

@@ -1,26 +0,0 @@
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 static org.junit.Assert.*;
/**
* Instrumentation test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("de.sebse.fuplanner", appContext.getPackageName());
}
}

View File

@@ -2,25 +2,100 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.sebse.fuplanner"> 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.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.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:fullBackupContent="@xml/backup_descriptor"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/FUTheme" android:theme="@style/FUTheme">
android:fullBackupContent="@xml/backup_descriptor">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:label="@string/app_name"> android:label="@string/app_name"
android:clearTaskOnLaunch="true"
android:launchMode="singleTop">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </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>
<service
android:name=".services.kvv.KVV"
android:exported="false">
<intent-filter>
<action android:name="android.app.Service" />
</intent-filter>
</service>
<service
android:name=".tools.network.HTTPNetwork"
android:exported="false">
<intent-filter>
<action android:name="android.app.Service" />
</intent-filter>
</service>
</application> </application>
</manifest> </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,132 +1,325 @@
package de.sebse.fuplanner; package de.sebse.fuplanner;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.os.IBinder;
import android.support.annotation.StringRes; import android.text.TextUtils;
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.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.TextView; 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.ArrayList;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Locale;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.arch.core.util.Function;
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.CanteensFragment;
import de.sebse.fuplanner.fragments.LoginFragment;
import de.sebse.fuplanner.fragments.ModulesFragment; import de.sebse.fuplanner.fragments.ModulesFragment;
import de.sebse.fuplanner.fragments.NewsFragment;
import de.sebse.fuplanner.fragments.PrefsFragment; import de.sebse.fuplanner.fragments.PrefsFragment;
import de.sebse.fuplanner.fragments.ScheduleFragment; import de.sebse.fuplanner.fragments.ScheduleFragment;
import de.sebse.fuplanner.fragments.StartupFragment; import de.sebse.fuplanner.fragments.StartupFragment;
import de.sebse.fuplanner.fragments.canteen.DaySwitcherFragment; import de.sebse.fuplanner.fragments.canteen.DaySwitcherFragment;
import de.sebse.fuplanner.fragments.moddetails.ModDetailFragment; import de.sebse.fuplanner.fragments.moddetails.ModDetailFragment;
import de.sebse.fuplanner.services.Canteen.CanteenBrowser; import de.sebse.fuplanner.services.canteen.CanteenBrowser;
import de.sebse.fuplanner.services.Canteen.types.Canteen; import de.sebse.fuplanner.services.canteen.types.Canteen;
import de.sebse.fuplanner.services.GoogleAuth.GoogleAuth; import de.sebse.fuplanner.services.canteen.types.CanteenListener;
import de.sebse.fuplanner.services.KVV.KVV; import de.sebse.fuplanner.services.fulogin.AccountGeneral;
import de.sebse.fuplanner.services.KVV.types.LoginToken; import de.sebse.fuplanner.services.kvv.KVV;
import de.sebse.fuplanner.services.KVV.types.Modules; import de.sebse.fuplanner.services.kvv.KVVListener;
import de.sebse.fuplanner.tools.MainAcitivityListener; import de.sebse.fuplanner.services.kvv.Login;
import de.sebse.fuplanner.services.kvv.sync.KVVContentProvider;
import de.sebse.fuplanner.services.kvv.types.LoginTokenBB;
import de.sebse.fuplanner.services.kvv.types.LoginTokenKVV;
import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.services.news.NewsManager;
import de.sebse.fuplanner.tools.CustomAccountManager;
import de.sebse.fuplanner.tools.CustomNotificationManager;
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.logging.Logger;
import de.sebse.fuplanner.tools.types.News;
public class MainActivity extends AppCompatActivity public class MainActivity extends AppCompatActivity
implements MainAcitivityListener, implements MainActivityListener, KVVListener, CanteenListener,
NavigationView.OnNavigationItemSelectedListener, NavigationView.OnNavigationItemSelectedListener,
LoginFragment.OnLoginFragmentInteractionListener,
ModulesFragment.OnModulesFragmentInteractionListener, ModulesFragment.OnModulesFragmentInteractionListener,
CanteensFragment.OnCanteensFragmentInteractionListener { CanteensFragment.OnCanteensFragmentInteractionListener {
private static final int FRAGMENT_NONE = -1; public static final int FRAGMENT_NONE = -1;
private static final int FRAGMENT_STARTUP = 0; public static final int FRAGMENT_STARTUP = 0;
private static final int FRAGMENT_MODULES = 1; public static final int FRAGMENT_MODULES = 1;
private static final int FRAGMENT_MODULES_DETAILS = 2; public static final int FRAGMENT_MODULES_DETAILS = 2;
private static final int FRAGMENT_LOGIN = 3; public static final int FRAGMENT_SCHEDULE = 4;
private static final int FRAGMENT_SCHEDULE = 4; public static final int FRAGMENT_CANTEENS = 5;
private static final int FRAGMENT_CANTEENS = 5; public static final int FRAGMENT_CANTEENS_DETAILS = 6;
private static final int FRAGMENT_CANTEENS_DETAILS = 6; public static final int FRAGMENT_PREFERENCES = 7;
private static final int FRAGMENT_PREFERENCES = 7; public static final int FRAGMENT_NEWS = 8;
private static final String ARG_FRAGMENT_PAGE = "fragment_page"; private static final String ARG_FRAGMENT_PAGE = "fragment_page";
private static final String ARG_FRAGMENT_STATUS = "fragment_status"; private static final String ARG_FRAGMENT_STATUS = "fragment_status";
private static final int DOUBLE_CLICK_TO_EXIT_MILLIS = 2000;
private FragmentManager mFragmentManager; private FragmentManager mFragmentManager;
private GoogleAuth mGoogleAuth; private NewsManager mNewsManager;
private KVV mKVV; private CanteenBrowser mCanteenBrowser;
private final Logger log = new Logger(this); private final Logger log = new Logger(this);
private NavigationView mNavigationView; private NavigationView mNavigationView;
private int fragmentPage = FRAGMENT_NONE; private boolean mAlreadyCreated = false;
private String fragmentData = "";
private CanteenBrowser mCanteenBrowser; // KVV service
private boolean mOfflineMode = false; private ArrayList<KVVCallback> mCallbacks = new ArrayList<>();
@Nullable private KVV mKVV;
boolean mBound = false;
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
KVV.LocalBinder binder = (KVV.LocalBinder) service;
mKVV = binder.getService();
mKVV.addListener("mainactivity", MainActivity.this);
for (KVVCallback callback : mCallbacks) {
callback.get(mKVV);
}
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mKVV.removeListener("mainactivity");
mBound = false;
mKVV = null;
}
};
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;
private boolean isDrawerFixed = false;
private Function<Modules, Void> mMolduleListUpdateFunction;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
int newFragmentPage = FRAGMENT_NONE; mAccountManager = new CustomAccountManager(AccountManager.get(this), () -> MainActivity.this);
String newFragmentData = ""; isDrawerFixed = getResources().getBoolean(R.bool.isDrawerFixed);
if (savedInstanceState != null) { int desiredPage = getDefaultFragmentAfterLogin();
newFragmentPage = savedInstanceState.getInt(ARG_FRAGMENT_PAGE, fragmentPage); String desiredData = "";
newFragmentData = savedInstanceState.getString(ARG_FRAGMENT_STATUS, fragmentData); Intent intent = getIntent();
if (intent != null && CustomNotificationManager.NOTIFICATION_TYPE_NAVIGATE.equals(intent.getStringExtra(CustomNotificationManager.NOTIFICATION_INTENT))) {
int page = intent.getIntExtra(CustomNotificationManager.NOTIFICATION_PAGE, 0);
if (page == FRAGMENT_STARTUP || page == FRAGMENT_NONE)
page = getDefaultFragmentAfterLogin();
desiredPage = page;
desiredData = intent.getStringExtra(CustomNotificationManager.NOTIFICATION_DATA);
} else if (savedInstanceState != null) {
desiredPage = savedInstanceState.getInt(ARG_FRAGMENT_PAGE, desiredPage);
desiredData = savedInstanceState.getString(ARG_FRAGMENT_STATUS, desiredData);
} }
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
DrawerLayout drawer = findViewById(R.id.drawer_layout); if (!isDrawerFixed) {
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( DrawerLayout drawer = findViewById(R.id.drawer_layout);
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
drawer.addDrawerListener(toggle); this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
toggle.syncState(); drawer.addDrawerListener(toggle);
toggle.syncState();
}
mNavigationView = findViewById(R.id.nav_view); mNavigationView = findViewById(R.id.nav_view);
mNavigationView.setNavigationItemSelectedListener(this); mNavigationView.setNavigationItemSelectedListener(this);
mFragmentManager = getSupportFragmentManager(); mFragmentManager = getSupportFragmentManager();
LoginToken loginToken = getKVV().easyLogin(); //if (mAccountManager.getAccountsByType(AccountGeneral.ACCOUNT_TYPE).length == 0) {
if (newFragmentPage != FRAGMENT_LOGIN && newFragmentPage != FRAGMENT_STARTUP && newFragmentPage != FRAGMENT_NONE) { if (!mAccountManager.hasAccounts(AccountGeneral.ACCOUNT_TYPE)) {
if (loginToken != null) desiredPage = getDefaultFragmentAfterLogout();
toLoginState(loginToken, newFragmentPage, newFragmentData); desiredData = "";
else mAccountManager.getTokenByType(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_BLACKBOARD, null, null);
checkAndDoLogin(); updateNavigation();
changeFragment(desiredPage, desiredData);
} else { } else {
if (loginToken != null) updateNavigation();
toLoginState(loginToken, getDefaultFragmentAfterLogin(), ""); changeFragment(FRAGMENT_STARTUP);
else int targetPage = desiredPage;
checkAndDoLogin(); String targetData = desiredData;
getKVV(kvv -> {
kvv.account().restoreOnlineLogin(restoreResult -> {
updateNavigation();
if (restoreResult != Login.RESTORE_STATUS_INVALID_PASSWORD)
changeFragment(targetPage, targetData);
else
changeFragment(getDefaultFragmentAfterLogout());
});
});
} }
/*this.getCanteenBrowser().getCanteens(success -> { if (!mAlreadyCreated) {
Canteen canteen = success.get(0); Intent serviceIntent = new Intent(this, KVV.class);
this.getCanteenBrowser().getCanteen(canteen, success1 -> { bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
this.getCanteenBrowser().getDay(canteen.get(0), log::d, log::e, true); }
}, log::e, true); mAlreadyCreated = true;
}, log::e, true);*/
if (!Preferences.getBoolean(this, R.string.pref_set_auto_sync_on_startup)) {
registerSync(true);
Preferences.setBoolean(this, R.string.pref_set_auto_sync_on_startup, true);
} else {
registerSync(false);
}
CustomNotificationManager.createNotificationChannel(this);
/*getKVV(kvv -> {
kvv.modules().list().recv(list -> {
Modules.Module module = list.getByIndex(0);
CustomNotificationManager.sendNotification(this, "Test", module.title, FRAGMENT_MODULES_DETAILS, module.getID()+"."+ ModulePart.getPageByPart(ModulePart.EVENT));
}, log::e);
});*/
}
@Override
protected void onStart() {
super.onStart();
if (!mAlreadyCreated) {
Intent serviceIntent = new Intent(this, KVV.class);
bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
}
mAlreadyCreated = true;
}
@Override
protected void onStop() {
super.onStop();
if (mKVV != null)
mKVV.removeListener("mainactivity");
if (mBound)
unbindService(mConnection);
mKVV = null;
mBound = false;
mAlreadyCreated = false;
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (CustomNotificationManager.NOTIFICATION_TYPE_NAVIGATE.equals(intent.getStringExtra(CustomNotificationManager.NOTIFICATION_INTENT))) {
int page = intent.getIntExtra(CustomNotificationManager.NOTIFICATION_PAGE, 0);
String data = intent.getStringExtra(CustomNotificationManager.NOTIFICATION_DATA);
if (page == FRAGMENT_STARTUP || page == FRAGMENT_NONE)
page = getDefaultFragmentAfterLogin();
changeFragment(page, data);
}
}
@Override
protected void onPause() {
super.onPause();
isPaused = true;
getKVV(kvv -> isLoggedInBeforePause = kvv.account().isLoggedIn());
}
@Override
protected void onResume() {
super.onResume();
if (isPaused) {
getKVV(kvv -> {
kvv.account().restoreOnlineLogin(restoreResult -> {
updateNavigation();
if (restoreResult == Login.RESTORE_STATUS_SUCCESS && !isLoggedInBeforePause) {
changeFragment(getDefaultFragmentAfterLogin());
registerSync(true);
} else if (restoreResult == Login.RESTORE_STATUS_INVALID_PASSWORD && isLoggedInBeforePause) {
kvv.account().logout(false);
changeFragment(getDefaultFragmentAfterLogout());
}
});
kvv.modules().list().reloadIfOutdated();
});
}
isPaused = false;
} }
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if (mDoubleBackToExitPressedOnce + DOUBLE_CLICK_TO_EXIT_MILLIS > System.currentTimeMillis()) {
super.onBackPressed();
return;
}
DrawerLayout drawer = findViewById(R.id.drawer_layout); DrawerLayout drawer = findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) { if (drawer.isDrawerOpen(GravityCompat.START) && !isDrawerFixed) {
drawer.closeDrawer(GravityCompat.START); drawer.closeDrawer(GravityCompat.START);
} else { } else {
super.onBackPressed(); getKVV(kvv -> {
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 (kvv.account().isLoggedIn() && mFragmentPage != getDefaultFragmentAfterLogin()) {
changeFragment(getDefaultFragmentAfterLogin());
} else {
mDoubleBackToExitPressedOnce = System.currentTimeMillis();
showToast(R.string.back_to_exit);
//getTokenForAccountCreateIfNeeded(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_BLACKBOARD);
}
});
} }
} }
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present. // 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); getMenuInflater().inflate(R.menu.options_schedule, menu);
return true; return true;
} }
@@ -162,7 +355,6 @@ public class MainActivity extends AppCompatActivity
public boolean onNavigationItemSelected(@NonNull MenuItem item) { public boolean onNavigationItemSelected(@NonNull MenuItem item) {
// Handle navigation view item clicks here. // Handle navigation view item clicks here.
int id = item.getItemId(); int id = item.getItemId();
switch (id) { switch (id) {
case R.id.nav_modules: case R.id.nav_modules:
changeFragment(FRAGMENT_MODULES); changeFragment(FRAGMENT_MODULES);
@@ -170,9 +362,12 @@ public class MainActivity extends AppCompatActivity
case R.id.nav_schedule: case R.id.nav_schedule:
changeFragment(FRAGMENT_SCHEDULE); changeFragment(FRAGMENT_SCHEDULE);
break; break;
case R.id.nav_dining: case R.id.nav_canteens:
changeFragment(FRAGMENT_CANTEENS); changeFragment(FRAGMENT_CANTEENS);
break; break;
case R.id.nav_news:
changeFragment(FRAGMENT_NEWS);
break;
case R.id.nav_settings: case R.id.nav_settings:
changeFragment(FRAGMENT_PREFERENCES); changeFragment(FRAGMENT_PREFERENCES);
break; break;
@@ -184,18 +379,17 @@ public class MainActivity extends AppCompatActivity
startActivity(sendIntent); startActivity(sendIntent);
break; break;
case R.id.nav_logout: case R.id.nav_logout:
this.getKVV().logout(); getKVV(kvv -> {
this.getGoogleAuth().getLoginState(credentials -> { kvv.account().logout(true);
if (credentials != null) { kvv.modules().list().delete();
this.getGoogleAuth().deleteLoginState(credentials.getUsername(), credentials.getPassword());
}
this.toLogoutState();
}); });
break; break;
}
DrawerLayout drawer = findViewById(R.id.drawer_layout); }
drawer.closeDrawer(GravityCompat.START); if (!isDrawerFixed) {
DrawerLayout drawer = findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
}
int size = mNavigationView.getMenu().size(); int size = mNavigationView.getMenu().size();
for (int k = 0; k < size; k++) { for (int k = 0; k < size; k++) {
@@ -206,35 +400,46 @@ public class MainActivity extends AppCompatActivity
return true; return true;
} }
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
this.getGoogleAuth().onActivityResult(requestCode, resultCode, data);
}
@Override @Override
protected void onSaveInstanceState(Bundle savedInstanceState) { protected void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt(ARG_FRAGMENT_PAGE, fragmentPage); if (mFragmentPage != FRAGMENT_STARTUP && mFragmentPage != FRAGMENT_NONE) {
savedInstanceState.putString(ARG_FRAGMENT_STATUS, fragmentData); 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); 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);
} }
return this.mGoogleAuth;
} }
public KVV getKVV() { /* --------------------------------------------*/
if (this.mKVV == null) { /* --------------------------------------------*/
this.mKVV = new KVV(this); /* --------------------------------------------*/
public NewsManager getNewsManager() {
if (this.mNewsManager == null) {
this.mNewsManager = new NewsManager(this);
}
return this.mNewsManager;
}
public void getKVV(KVVCallback callback) {
if (this.mKVV == null) {
mCallbacks.add(callback);
} else {
callback.get(this.mKVV);
} }
return this.mKVV;
} }
public CanteenBrowser getCanteenBrowser() { public CanteenBrowser getCanteenBrowser() {
@@ -248,41 +453,38 @@ public class MainActivity extends AppCompatActivity
return FRAGMENT_MODULES; return FRAGMENT_MODULES;
} }
private int getDefaultFragmentAfterLogout() {
return FRAGMENT_CANTEENS;
}
private void toLogoutState() { private void toLogoutState() {
changeFragment(FRAGMENT_LOGIN); setRefreshFailedBanner(false);
setOfflineBanner(true); updateNavigation();
changeFragment(getDefaultFragmentAfterLogout());
//mAccountManager.getTokenByType(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_BLACKBOARD, null, null);
} }
private void toLoginState(LoginToken loginToken, int newFragment, String newData) { private void toLoginState(String fullName, String email, int newFragment) {
if (loginToken == null) { updateNavigation();
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);
View header = mNavigationView.getHeaderView(0); 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); ((TextView) header.findViewById(R.id.login_mail)).setText(email);
setOfflineBanner(onlineMode);
changeFragment(newFragment);
} }
private void checkAndDoLogin() { private void registerSync(boolean onLogin) {
changeFragment(FRAGMENT_STARTUP); Account accountByType = mAccountManager.getAccountByType(AccountGeneral.ACCOUNT_TYPE);
getGoogleAuth().getLoginState(credentials -> { if (accountByType != null) {
if (credentials == null || credentials.getUsername() == null || credentials.getPassword() == null) { if (onLogin)
toLogoutState(); ContentResolver.setSyncAutomatically(accountByType, KVVContentProvider.PROVIDER_NAME, true);
return; ContentResolver.addPeriodicSync(
} accountByType,
this.getKVV().login(credentials.getUsername(), credentials.getPassword(), success -> toLoginState(success, getDefaultFragmentAfterLogin(), ""), KVVContentProvider.PROVIDER_NAME,
error -> { Bundle.EMPTY,
log.e(error); Long.parseLong(Preferences.getStringArray(this, R.array.pref_sync_frequency))*60*60);
toLogoutState(); }
});
});
} }
private void changeFragment(int newFragment) { private void changeFragment(int newFragment) {
@@ -290,26 +492,35 @@ public class MainActivity extends AppCompatActivity
} }
private void changeFragment(int newFragment, String newData) { 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; Fragment fragment;
switch (newFragment) { switch (newFragment) {
case FRAGMENT_MODULES:
fragment = ModulesFragment.newInstance();
break;
case FRAGMENT_MODULES_DETAILS: case FRAGMENT_MODULES_DETAILS:
fragment = ModDetailFragment.newInstance(newData); fragment = ModDetailFragment.newInstance(newData);
break; break;
case FRAGMENT_LOGIN: case FRAGMENT_MODULES:
fragment = LoginFragment.newInstance(); fragment = ModulesFragment.newInstance();
break; break;
case FRAGMENT_SCHEDULE: case FRAGMENT_SCHEDULE:
fragment = ScheduleFragment.newInstance(); fragment = ScheduleFragment.newInstance();
break; break;
case FRAGMENT_CANTEENS_DETAILS:
fragment = DaySwitcherFragment.newInstance(newData);
break;
case FRAGMENT_CANTEENS: case FRAGMENT_CANTEENS:
fragment = CanteensFragment.newInstance(); fragment = CanteensFragment.newInstance();
break; break;
case FRAGMENT_CANTEENS_DETAILS: case FRAGMENT_NEWS:
fragment = DaySwitcherFragment.newInstance(Integer.parseInt(newData)); Preferences.setLong(this, R.string.pref_last_visited_news, System.currentTimeMillis());
updateNavigation();
fragment = NewsFragment.newInstance();
break; break;
case FRAGMENT_PREFERENCES: case FRAGMENT_PREFERENCES:
fragment = PrefsFragment.newInstance(); fragment = PrefsFragment.newInstance();
@@ -324,98 +535,132 @@ public class MainActivity extends AppCompatActivity
fragmentTransaction.commit(); fragmentTransaction.commit();
if (newFragment == FRAGMENT_STARTUP) { if (newFragment == FRAGMENT_STARTUP) {
findViewById(R.id.app_bar_layout).setVisibility(View.GONE); findViewById(R.id.app_bar_include).setVisibility(View.GONE);
} else { } 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 -> {
DrawerLayout drawer = findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
}
changeFragment(FRAGMENT_LOGIN);
});
mNavigationView.getMenu().clear();
mNavigationView.inflateMenu(R.menu.activity_main_drawer);
afterAnyMenuInflate();
} // switch to login
else if ((fragmentPage == FRAGMENT_STARTUP || fragmentPage == FRAGMENT_LOGIN || fragmentPage == FRAGMENT_NONE) && (newFragment != FRAGMENT_STARTUP && newFragment != FRAGMENT_LOGIN && newFragment != FRAGMENT_CANTEENS && newFragment != FRAGMENT_CANTEENS_DETAILS)) {
View header = mNavigationView.getHeaderView(0);
//header.findViewById(R.id.imageView).setVisibility(View.VISIBLE);
header.findViewById(R.id.login_name).setVisibility(View.VISIBLE);
header.findViewById(R.id.login_mail).setVisibility(View.VISIBLE);
header.findViewById(R.id.btn_login_page).setVisibility(View.GONE);
mNavigationView.getMenu().clear();
mNavigationView.inflateMenu(R.menu.activity_main_drawer_login);
mNavigationView.setCheckedItem(R.id.nav_modules);
afterAnyMenuInflate();
getKVV().getModuleList(success -> {
int i = 0;
for (Iterator<Modules.Module> it = success.latestSemesterIterator(); it.hasNext(); ) {
Modules.Module module = it.next();
MenuItem menuItem = mNavigationView.getMenu().add(Menu.NONE, Menu.NONE, 101 + i, module.title);
menuItem.setOnMenuItemClickListener(item -> {
onModulesFragmentInteraction(module.getID());
return false;
});
i++;
}
}, log::e);
}
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.mFragmentPage = newFragment;
this.fragmentData = newData; this.mFragmentData = newData;
invalidateOptionsMenu(); 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) { private void setRefreshFailedBanner(boolean refreshFailed) {
View viewNoConnection = findViewById(R.id.no_connection_msg); View viewNoConnection = findViewById(R.id.no_connection_msg);
if (!mOfflineMode && refreshFailed) if (refreshFailed)
viewNoConnection.setVisibility(View.VISIBLE); viewNoConnection.setVisibility(View.VISIBLE);
else else
viewNoConnection.setVisibility(View.GONE); viewNoConnection.setVisibility(View.GONE);
} }
private void setNavigationSelection() {
MenuItem item = null;
int dotPos = mFragmentData.indexOf(".");
String moduleId = mFragmentData;
if (dotPos != -1)
moduleId = moduleId.substring(0, dotPos);
switch (mFragmentPage) {
case FRAGMENT_MODULES_DETAILS:
String finalModuleId = moduleId;
getKVV(kvv -> {
kvv.modules().list().find(finalModuleId, 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();
try {
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;
}
}
} catch (NumberFormatException e) {
e.printStackTrace();
}
}, 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 afterAnyMenuInflate() { 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) && !isDrawerFixed) {
drawer.closeDrawer(GravityCompat.START);
}
mAccountManager.getTokenByType(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_BLACKBOARD, null, null);
});
}
private void afterAnyMenuInflate(boolean isLoggedIn, Runnable done) {
int MAX_COUNT = isLoggedIn ? 3 : 2;
final int[] count = {0};
if (isLoggedIn) {
getKVV(kvv -> {
kvv.modules().list().recv(success -> {
int i = 0;
for (Iterator<Modules.Module> it = success.latestSemesterIterator(); it.hasNext(); ) {
Modules.Module module = it.next();
MenuItem menuItem = mNavigationView.getMenu().add(Menu.NONE, Menu.NONE, 101 + i, module.title);
menuItem.setOnMenuItemClickListener(item -> {
onModulesFragmentInteraction(module.getID());
return false;
});
i++;
}
if (++count[0] == MAX_COUNT) done.run();
}, error -> {
if (++count[0] == MAX_COUNT) done.run();
log.e(error);
});
});
}
getCanteenBrowser().getCanteens(success -> { getCanteenBrowser().getCanteens(success -> {
int i = 0; int i = 0;
for (Canteen canteen: success) { for (Canteen canteen: success) {
@@ -426,7 +671,48 @@ public class MainActivity extends AppCompatActivity
}); });
i++; 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(() -> {
getKVV(kvv -> {
boolean isLoggedIn = kvv.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 +721,119 @@ 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) { public void onModulesFragmentInteraction(final String itemID) {
changeFragment(FRAGMENT_MODULES_DETAILS, itemID); changeFragment(FRAGMENT_MODULES_DETAILS, itemID);
setNavigationSelection();
} }
@Override
public void onModulesFragmentListUpdate(Function<Modules, Void> update) {
this.mMolduleListUpdateFunction = update;
}
@Override
public void onCanteensFragmentInteraction(final int itemID) { public void onCanteensFragmentInteraction(final int itemID) {
changeFragment(FRAGMENT_CANTEENS_DETAILS, String.valueOf(itemID)); changeFragment(FRAGMENT_CANTEENS_DETAILS, String.valueOf(itemID));
setNavigationSelection();
} }
@Override
public void onTitleTextChange(String newTitle) { public void onTitleTextChange(String newTitle) {
setTitle(newTitle); setTitle(newTitle);
} }
@Override
public void onTitleTextChange(@StringRes int titleId) { public void onTitleTextChange(@StringRes int titleId) {
setTitle(titleId); setTitle(titleId);
} }
public void loginTokenInvalid(boolean doPrecheck) { @Override
getKVV().invalidate(); public void onCanteenRefreshCompleted(boolean isFailed) {
checkAndDoLogin();
}
public void refreshFailed(boolean isFailed) {
setRefreshFailedBanner(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(LoginTokenKVV tokenKVV, LoginTokenBB tokenBB, boolean isOnlyRefresh) {
String fullName = "";
if (tokenKVV != null && !TextUtils.isEmpty(tokenKVV.getFullName())) {
fullName = tokenKVV.getFullName();
} else if (tokenBB != null && !TextUtils.isEmpty(tokenBB.getUsername())) {
fullName = tokenBB.getUsername();
}
String email = "";
if (tokenKVV != null && !TextUtils.isEmpty(tokenKVV.getEmail()) && !"null".equals(tokenKVV.getEmail())) {
email = tokenKVV.getEmail();
} else if (tokenBB != null && !TextUtils.isEmpty(tokenBB.getUsername())) {
email = tokenBB.getUsername() + "@zedat.fu-berlin.de";
}
toLoginState(fullName, email, 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;
}
public interface KVVCallback {
void get(KVV kvv);
}
@Override
public void onModuleListPartiallyUpdated(Modules modules) {
if (mMolduleListUpdateFunction != null) {
mMolduleListUpdateFunction.apply(modules);
}
}
} }

View File

@@ -1,20 +1,19 @@
package de.sebse.fuplanner.fragments; package de.sebse.fuplanner.fragments;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import de.sebse.fuplanner.R; import de.sebse.fuplanner.R;
import de.sebse.fuplanner.fragments.CanteensFragment.OnCanteensFragmentInteractionListener; import de.sebse.fuplanner.fragments.CanteensFragment.OnCanteensFragmentInteractionListener;
import de.sebse.fuplanner.services.Canteen.types.Canteen; import de.sebse.fuplanner.services.canteen.types.Canteen;
import de.sebse.fuplanner.services.Canteen.types.Canteens; import de.sebse.fuplanner.services.canteen.types.Canteens;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.ui.ItemViewHolder; 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}. * specified {@link OnCanteensFragmentInteractionListener}.
*/ */
class CanteensAdapter extends RecyclerView.Adapter<ItemViewHolder> { class CanteensAdapter extends RecyclerView.Adapter<ItemViewHolder> {

View File

@@ -2,19 +2,19 @@ package de.sebse.fuplanner.fragments;
import android.content.Context; import android.content.Context;
import android.os.Bundle; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.MainActivity;
import de.sebse.fuplanner.R; import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.Canteen.CanteenBrowser; import de.sebse.fuplanner.services.canteen.CanteenBrowser;
import de.sebse.fuplanner.tools.MainAcitivityListener; import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger; import de.sebse.fuplanner.tools.logging.Logger;
/** /**
@@ -28,6 +28,7 @@ public class CanteensFragment extends Fragment {
private final Logger log = new Logger(this); private final Logger log = new Logger(this);
private CanteensAdapter adapter; private CanteensAdapter adapter;
private SwipeRefreshLayout swipeLayout; private SwipeRefreshLayout swipeLayout;
private MainActivityListener mMainActivityListener;
/** /**
* Mandatory empty constructor for the fragment manager to instantiate the * 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, public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { 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 // Set the adapter
Context context = view.getContext(); Context context = view.getContext();
RecyclerView recyclerView = view.findViewById(R.id.list); RecyclerView recyclerView = view.findViewById(R.id.list);
@@ -69,6 +70,8 @@ public class CanteensFragment extends Fragment {
CanteenBrowser browser = ((MainActivity) getActivity()).getCanteenBrowser(); CanteenBrowser browser = ((MainActivity) getActivity()).getCanteenBrowser();
browser.getCanteens(success -> { browser.getCanteens(success -> {
adapter.setCanteens(success); adapter.setCanteens(success);
//if (mMainActivityListener != null)
// mMainActivityListener.refreshNavigation();
swipeLayout.setRefreshing(false); swipeLayout.setRefreshing(false);
}, error -> { }, error -> {
log.e(error.toString()); log.e(error.toString());
@@ -87,16 +90,18 @@ public class CanteensFragment extends Fragment {
throw new RuntimeException(context.toString() throw new RuntimeException(context.toString()
+ " must implement OnCanteensFragmentInteractionListener"); + " must implement OnCanteensFragmentInteractionListener");
} }
if (context instanceof MainAcitivityListener) if (context instanceof MainActivityListener) {
((MainAcitivityListener) context).onTitleTextChange(R.string.canteens); mMainActivityListener = (MainActivityListener) context;
else mMainActivityListener.onTitleTextChange(R.string.canteens);
throw new RuntimeException(context.toString() + "must implement MainActivityListener"); } else
throw new RuntimeException(context.toString() + " must implement MainActivityListener");
} }
@Override @Override
public void onDetach() { public void onDetach() {
super.onDetach(); super.onDetach();
mListener = null; mListener = null;
mMainActivityListener = null;
} }
public interface OnCanteensFragmentInteractionListener { 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; package de.sebse.fuplanner.fragments;
import android.support.annotation.NonNull; import android.util.Pair;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.R;
import de.sebse.fuplanner.fragments.ModulesFragment.OnModulesFragmentInteractionListener; 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.ItemViewHolder;
import de.sebse.fuplanner.tools.ui.StringViewHolder;
/** /**
* {@link RecyclerView.Adapter} that can display a {@link Modules.Module} and makes a call to the * {@link RecyclerView.Adapter} that can display a {@link Modules.Module} and makes a call to the
* specified {@link OnModulesFragmentInteractionListener}. * 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 Modules mValues;
private final OnModulesFragmentInteractionListener mListener; private final OnModulesFragmentInteractionListener mListener;
private final ArrayList<Pair<Integer, Object>> mPositionalData;
ModulesAdapter(OnModulesFragmentInteractionListener listener) { ModulesAdapter(OnModulesFragmentInteractionListener listener) {
mValues = null; mValues = null;
mListener = listener; mListener = listener;
mPositionalData = new ArrayList<>();
} }
public void setModules(Modules modules) { public void setModules(Modules modules) {
mValues = modules; mValues = modules;
mPositionalData.clear();
Semester lastSemester = new Semester(Semester.SEM_WS, 0);
for (Modules.Module module : mValues) {
Semester semester = module.semester;
if (semester != null && !semester.equals(lastSemester) || semester == null && lastSemester != null) {
mPositionalData.add(new Pair<>(TYPE_HEADER, semester));
lastSemester = semester;
}
mPositionalData.add(new Pair<>(TYPE_ITEM, module));
}
this.notifyDataSetChanged(); this.notifyDataSetChanged();
} }
@Override
public int getItemViewType(int position) {
return mPositionalData.get(position).first;
}
@NonNull @NonNull
@Override @Override
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public CustomViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()) if (viewType == TYPE_HEADER) {
.inflate(R.layout.list_all_items, parent, false); View view = LayoutInflater.from(parent.getContext())
return new ItemViewHolder(view); .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 @Override
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) { public void onBindViewHolder(@NonNull CustomViewHolder holder, int position) {
if (mValues == null) Pair<Integer, Object> pair = mPositionalData.get(holder.getAdapterPosition());
return; if (pair.first == TYPE_HEADER) {
Modules.Module module = mValues.getByIndex(holder.getAdapterPosition()); StringViewHolder sHolder = (StringViewHolder) holder;
holder.mTitle.setText(module.title); String localizedSemester;
holder.mSubLeft.setText(module.semester); Semester semester = (Semester) pair.second;
holder.mSubRight.setText(module.type); if (semester == null)
localizedSemester = holder.mView.getResources().getString(R.string.others);
holder.mView.setOnClickListener(v -> { else if (semester.getType() == Semester.SEM_WS)
if (null != mListener) { localizedSemester = holder.mView.getResources().getString(R.string.winter_semester, semester.getYear(), semester.getYear()+1);
// Notify the active callbacks interface (the activity, if the else
// fragment is attached to one) that an item has been selected. localizedSemester = holder.mView.getResources().getString(R.string.summer_semester, semester.getYear());
mListener.onModulesFragmentInteraction(module.getID()); 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 @Override
public int getItemCount() { public int getItemCount() {
if (mValues != null) { return mPositionalData.size();
return mValues.size();
}
return 0;
} }
} }

View File

@@ -2,19 +2,20 @@ package de.sebse.fuplanner.fragments;
import android.content.Context; import android.content.Context;
import android.os.Bundle; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import de.sebse.fuplanner.MainActivity; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.arch.core.util.Function;
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.R;
import de.sebse.fuplanner.services.KVV.KVV; import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.tools.MainAcitivityListener; import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger; import de.sebse.fuplanner.tools.logging.Logger;
/** /**
@@ -28,6 +29,7 @@ public class ModulesFragment extends Fragment {
private final Logger log = new Logger(this); private final Logger log = new Logger(this);
private ModulesAdapter adapter; private ModulesAdapter adapter;
private SwipeRefreshLayout swipeLayout; private SwipeRefreshLayout swipeLayout;
@Nullable private MainActivityListener mMainActivityListener;
/** /**
* Mandatory empty constructor for the fragment manager to instantiate the * Mandatory empty constructor for the fragment manager to instantiate the
@@ -47,7 +49,7 @@ public class ModulesFragment extends Fragment {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { 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 // Set the adapter
Context context = view.getContext(); Context context = view.getContext();
RecyclerView recyclerView = view.findViewById(R.id.list); RecyclerView recyclerView = view.findViewById(R.id.list);
@@ -65,15 +67,16 @@ public class ModulesFragment extends Fragment {
} }
private void refresh(boolean forceRefresh) { private void refresh(boolean forceRefresh) {
if (getActivity() != null) { if (mMainActivityListener != null) {
KVV kvv = ((MainActivity) getActivity()).getKVV(); mMainActivityListener.getKVV(kvv -> {
kvv.getModuleList(success -> { kvv.modules().list().recv(success -> {
adapter.setModules(success); adapter.setModules(success);
swipeLayout.setRefreshing(false); swipeLayout.setRefreshing(false);
}, error -> { }, error -> {
log.e(error.toString()); log.e(error.toString());
swipeLayout.setRefreshing(false); swipeLayout.setRefreshing(false);
}, forceRefresh); }, forceRefresh);
});
} }
} }
@@ -83,23 +86,31 @@ public class ModulesFragment extends Fragment {
super.onAttach(context); super.onAttach(context);
if (context instanceof OnModulesFragmentInteractionListener) { if (context instanceof OnModulesFragmentInteractionListener) {
mListener = (OnModulesFragmentInteractionListener) context; mListener = (OnModulesFragmentInteractionListener) context;
mListener.onModulesFragmentListUpdate(modules -> {
if (adapter != null) adapter.setModules(modules);
return null;
});
} else { } else {
throw new RuntimeException(context.toString() throw new RuntimeException(context.toString()
+ " must implement OnModulesFragmentInteractionListener"); + " must implement OnModulesFragmentInteractionListener");
} }
if (context instanceof MainAcitivityListener) if (context instanceof MainActivityListener) {
((MainAcitivityListener) context).onTitleTextChange(R.string.courses); mMainActivityListener = (MainActivityListener) context;
mMainActivityListener.onTitleTextChange(R.string.courses);
}
else else
throw new RuntimeException(context.toString() + "must implement MainActivityListener"); throw new RuntimeException(context.toString() + " must implement MainActivityListener");
} }
@Override @Override
public void onDetach() { public void onDetach() {
super.onDetach(); super.onDetach();
mListener = null; mListener = null;
mMainActivityListener = null;
} }
public interface OnModulesFragmentInteractionListener { public interface OnModulesFragmentInteractionListener {
void onModulesFragmentInteraction(String id); void onModulesFragmentInteraction(String id);
void onModulesFragmentListUpdate(Function<Modules, Void> update);
} }
} }

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

@@ -1,18 +1,22 @@
package de.sebse.fuplanner.fragments; package de.sebse.fuplanner.fragments;
import android.accounts.Account;
import android.content.ContentResolver;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; 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.MainActivity;
import de.sebse.fuplanner.R; import de.sebse.fuplanner.R;
import de.sebse.fuplanner.tools.logging.Logger; import de.sebse.fuplanner.services.fulogin.AccountGeneral;
import de.sebse.fuplanner.services.kvv.sync.KVVContentProvider;
import de.sebse.fuplanner.tools.CustomAccountManager;
import de.sebse.fuplanner.tools.Preferences;
public class PrefsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener { public class PrefsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener {
private Logger log = new Logger(this);
public static PrefsFragment newInstance() { public static PrefsFragment newInstance() {
PrefsFragment fragment = new PrefsFragment(); PrefsFragment fragment = new PrefsFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
@@ -46,5 +50,20 @@ public class PrefsFragment extends PreferenceFragmentCompat implements SharedPre
Preference preference = getPreferenceScreen().findPreference(s); Preference preference = getPreferenceScreen().findPreference(s);
if (preference instanceof ListPreference) if (preference instanceof ListPreference)
preference.setSummary(((ListPreference) preference).getEntry()); preference.setSummary(((ListPreference) preference).getEntry());
if (getActivity() != null && getActivity() instanceof MainActivity) {
CustomAccountManager accountManager = ((MainActivity) getActivity()).getAccountManager();
if (accountManager != null) {
Account accountByType = accountManager.getAccountByType(AccountGeneral.ACCOUNT_TYPE);
if (accountByType != null) {
ContentResolver.setSyncAutomatically(accountByType, KVVContentProvider.PROVIDER_NAME, true);
ContentResolver.addPeriodicSync(
accountByType,
KVVContentProvider.PROVIDER_NAME,
Bundle.EMPTY,
Long.parseLong(Preferences.getStringArray(getActivity(), R.array.pref_sync_frequency)));
}
}
}
} }
} }

View File

@@ -4,8 +4,6 @@ import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.graphics.RectF; import android.graphics.RectF;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -15,12 +13,14 @@ import java.util.Calendar;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import de.sebse.fuplanner.R; 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.Event; import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.services.KVV.types.Modules; import de.sebse.fuplanner.services.kvv.types.Semester;
import de.sebse.fuplanner.tools.DateUtils; import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.MainAcitivityListener; import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.logging.Logger; import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.ui.weekview.MonthLoader; import de.sebse.fuplanner.tools.ui.weekview.MonthLoader;
import de.sebse.fuplanner.tools.ui.weekview.WeekView; import de.sebse.fuplanner.tools.ui.weekview.WeekView;
@@ -29,14 +29,18 @@ import de.sebse.fuplanner.tools.ui.weekview.WeekViewEvent;
/** /**
* A simple {@link Fragment} subclass. * A simple {@link Fragment} subclass.
* Activities that contain this fragment must implement the * Activities that contain this fragment must implement the
* {@link MainAcitivityListener} interface * {@link MainActivityListener} interface
* to handle interaction events. * to handle interaction events.
* Use the {@link ScheduleFragment#newInstance} factory method to * Use the {@link ScheduleFragment#newInstance} factory method to
* create an instance of this fragment. * create an instance of this fragment.
* *
*/ */
public class ScheduleFragment extends Fragment implements MonthLoader.MonthChangeListener, WeekView.ScrollListener, WeekView.DoubleTapListener, WeekView.EventClickListener { public class ScheduleFragment extends Fragment implements
private MainAcitivityListener mListener; MonthLoader.MonthChangeListener,
WeekView.ScrollListener,
WeekView.DoubleTapLeftRightListener,
WeekView.EventClickListener {
private MainActivityListener mListener;
private WeekView mWeekView; private WeekView mWeekView;
private final Logger log = new Logger(this); private final Logger log = new Logger(this);
private Modules mModules = null; private Modules mModules = null;
@@ -59,13 +63,15 @@ public class ScheduleFragment extends Fragment implements MonthLoader.MonthChang
} }
public void invalidate(boolean forceRefresh) { public void invalidate(boolean forceRefresh) {
if (mListener != null) { if (mListener == null) return;
KVV kvv = mListener.getKVV(); mListener.getKVV(kvv -> {
kvv.getModuleList((Modules success) -> { kvv.modules().list().recv(modules -> {
mModules = success; mModules = modules;
final int[] i = {0}; final int[] i = {0};
for (Modules.Module module: mModules) { Iterator<Modules.Module> iter = mModules.latestSemesterIterator();
kvv.getModuleEvents(module, success1 -> { while (iter.hasNext()) {
Modules.Module module = iter.next();
kvv.modules().events().recv(module, success1 -> {
i[0]++; i[0]++;
if (i[0] >= mModules.size()) { if (i[0] >= mModules.size()) {
if (mWeekView != null) { if (mWeekView != null) {
@@ -76,7 +82,7 @@ public class ScheduleFragment extends Fragment implements MonthLoader.MonthChang
}, log::e, forceRefresh); }, log::e, forceRefresh);
} }
}, log::e, forceRefresh); }, log::e, forceRefresh);
} });
} }
@Override @Override
@@ -86,7 +92,7 @@ public class ScheduleFragment extends Fragment implements MonthLoader.MonthChang
mWeekView = v.findViewById(R.id.weekView); mWeekView = v.findViewById(R.id.weekView);
mWeekView.setMonthChangeListener(this); mWeekView.setMonthChangeListener(this);
mWeekView.setScrollListener(this); mWeekView.setScrollListener(this);
mWeekView.setDoubleTapListener(this); mWeekView.setDoubleTapLeftRightListener(this);
mWeekView.setOnEventClickListener(this); mWeekView.setOnEventClickListener(this);
invalidate(false); invalidate(false);
return v; return v;
@@ -99,8 +105,8 @@ public class ScheduleFragment extends Fragment implements MonthLoader.MonthChang
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
super.onAttach(context); super.onAttach(context);
if (context instanceof MainAcitivityListener) { if (context instanceof MainActivityListener) {
mListener = (MainAcitivityListener) context; mListener = (MainActivityListener) context;
} else { } else {
throw new RuntimeException(context.toString() throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener"); + " must implement OnFragmentInteractionListener");
@@ -118,7 +124,11 @@ public class ScheduleFragment extends Fragment implements MonthLoader.MonthChang
public List<? extends WeekViewEvent> onMonthChange(int newYear, int newMonth) { public List<? extends WeekViewEvent> onMonthChange(int newYear, int newMonth) {
ArrayList<WeekViewEvent> events = new ArrayList<>(); ArrayList<WeekViewEvent> events = new ArrayList<>();
if (mModules != null){ if (mModules != null){
Semester latestSemester = mModules.getLatestSemester();
for (Modules.Module mod: mModules) { for (Modules.Module mod: mModules) {
if (mod.getModuleType() == Modules.TYPE_BB && mod.semester != null && !mod.semester.equals(latestSemester)) {
continue;
}
if (mod.events != null) { if (mod.events != null) {
Iterator<Event> it_modEvents = mod.events.getEventsOfMonth(newYear, newMonth); Iterator<Event> it_modEvents = mod.events.getEventsOfMonth(newYear, newMonth);
while (it_modEvents.hasNext()) { while (it_modEvents.hasNext()) {
@@ -144,7 +154,7 @@ public class ScheduleFragment extends Fragment implements MonthLoader.MonthChang
public void onFirstVisibleDayChanged(Calendar newFirstVisibleDay, Calendar oldFirstVisibleDay) { public void onFirstVisibleDayChanged(Calendar newFirstVisibleDay, Calendar oldFirstVisibleDay) {
Calendar newLastVisibleDay = (Calendar) newFirstVisibleDay.clone(); Calendar newLastVisibleDay = (Calendar) newFirstVisibleDay.clone();
newLastVisibleDay.add(Calendar.HOUR, 24*mWeekView.getNumberOfVisibleDays()); 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 +163,23 @@ public class ScheduleFragment extends Fragment implements MonthLoader.MonthChang
} }
@Override @Override
public void onDoubleTapListener(Calendar time) { public void onDoubleTapLeftRightListener(boolean isRight) {
Calendar firstVisibleDay = mWeekView.getFirstVisibleDay(); Calendar firstVisibleDay = mWeekView.getFirstVisibleDay();
Calendar c = Calendar.getInstance(); // skip to next week
c.set(firstVisibleDay.get(Calendar.YEAR), firstVisibleDay.get(Calendar.MONTH), firstVisibleDay.get(Calendar.DAY_OF_MONTH), 0,0 ); if (isRight){
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{
firstVisibleDay.add(Calendar.DATE, 1); firstVisibleDay.add(Calendar.DATE, 1);
while (firstVisibleDay.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) { while (firstVisibleDay.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
firstVisibleDay.add(Calendar.DATE, 1); 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); mWeekView.goToDate(firstVisibleDay);
onFirstVisibleDayChanged(firstVisibleDay, mWeekView.getLastVisibleDay());
} }
@Override @Override
@@ -180,17 +188,16 @@ public class ScheduleFragment extends Fragment implements MonthLoader.MonthChang
String moduleId = idParts[0]; String moduleId = idParts[0];
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext()); AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext());
if (mListener != null) { if (mListener == null) return;
KVV kvv = mListener.getKVV(); mListener.getKVV(kvv -> {
kvv.getModuleList((Modules success) -> { kvv.modules().list().find(moduleId, module -> {
Modules.Module module = success.get(moduleId);
String moduleName = module.title; String moduleName = module.title;
alertDialogBuilder alertDialogBuilder
.setTitle(event.getName()) .setTitle(event.getName())
.setMessage( .setMessage(
getResources().getString(R.string.module_name, moduleName) + "\n" + getResources().getString(R.string.module_name, moduleName) + "\n" +
getResources().getString(R.string.location_name, event.getLocation()) + "\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)) getResources().getString(R.string.date_scale, UtilsDate.getModifiedTime(getContext(), event.getStartTime().getTimeInMillis()), UtilsDate.getModifiedTime(getContext(), event.getEndTime().getTimeInMillis()+1))
) )
.setCancelable(true) .setCancelable(true)
.setNeutralButton(R.string.close, (dialog, id) -> dialog.cancel()); .setNeutralButton(R.string.close, (dialog, id) -> dialog.cancel());
@@ -198,6 +205,6 @@ public class ScheduleFragment extends Fragment implements MonthLoader.MonthChang
AlertDialog alertDialog = alertDialogBuilder.create(); AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show(); alertDialog.show();
}, log::e); }, log::e);
} });
} }
} }

View File

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

View File

@@ -1,11 +1,11 @@
package de.sebse.fuplanner.fragments.canteen; package de.sebse.fuplanner.fragments.canteen;
import android.support.v4.app.Fragment; import androidx.fragment.app.Fragment;
import android.support.v4.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter; import androidx.fragment.app.FragmentStatePagerAdapter;
import de.sebse.fuplanner.services.Canteen.types.Canteen; import de.sebse.fuplanner.services.canteen.types.Canteen;
import de.sebse.fuplanner.tools.DateUtils; import de.sebse.fuplanner.tools.UtilsDate;
class DaySwitcherAdapter extends FragmentStatePagerAdapter { class DaySwitcherAdapter extends FragmentStatePagerAdapter {
private Canteen mCanteen = null; private Canteen mCanteen = null;
@@ -39,7 +39,7 @@ class DaySwitcherAdapter extends FragmentStatePagerAdapter {
// Returns the page title for the top indicator // Returns the page title for the top indicator
@Override @Override
public CharSequence getPageTitle(int position) { 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.content.Context;
import android.os.Bundle; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.MainActivity;
import de.sebse.fuplanner.R; import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.Canteen.CanteenBrowser; import de.sebse.fuplanner.services.canteen.CanteenBrowser;
import de.sebse.fuplanner.services.Canteen.types.Canteen; import de.sebse.fuplanner.services.canteen.types.Canteen;
import de.sebse.fuplanner.tools.MainAcitivityListener; import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger; import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.network.NetworkCallback; import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback; import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
@@ -21,7 +21,7 @@ import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
/** /**
* A simple {@link Fragment} subclass. * A simple {@link Fragment} subclass.
* Activities that contain this fragment must implement the * Activities that contain this fragment must implement the
* {@link MainAcitivityListener} interface * {@link MainActivityListener} interface
* to handle interaction events. * to handle interaction events.
* Use the {@link DaySwitcherFragment#newInstance} factory method to * Use the {@link DaySwitcherFragment#newInstance} factory method to
* create an instance of this fragment. * create an instance of this fragment.
@@ -31,11 +31,12 @@ public class DaySwitcherFragment extends Fragment implements DaySwitcherListener
// Parameters // Parameters
private int mCanteenId; private int mCanteenId;
private String mPageRestoreRequest = null;
private MainAcitivityListener mListener; private MainActivityListener mListener;
private final Logger log = new Logger(this); private final Logger log = new Logger(this);
private ViewPager mViewPager;
private DaySwitcherAdapter adapterViewPager; private DaySwitcherAdapter adapterViewPager;
private ViewPager mViewPager;
public DaySwitcherFragment() { public DaySwitcherFragment() {
// Required empty public constructor // Required empty public constructor
@@ -48,10 +49,14 @@ public class DaySwitcherFragment extends Fragment implements DaySwitcherListener
* @param canteenId Canteen id in canteens list. * @param canteenId Canteen id in canteens list.
* @return A new instance of fragment DaySwitcherFragment. * @return A new instance of fragment DaySwitcherFragment.
*/ */
public static Fragment newInstance(int canteenId) { public static Fragment newInstance(String canteenId) {
DaySwitcherFragment fragment = new DaySwitcherFragment(); DaySwitcherFragment fragment = new DaySwitcherFragment();
Bundle args = new Bundle(); 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); fragment.setArguments(args);
return fragment; return fragment;
} }
@@ -60,7 +65,15 @@ public class DaySwitcherFragment extends Fragment implements DaySwitcherListener
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (getArguments() != null) { 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) { if (mListener != null) {
mListener.onTitleTextChange(R.string.canteens); mListener.onTitleTextChange(R.string.canteens);
@@ -81,8 +94,8 @@ public class DaySwitcherFragment extends Fragment implements DaySwitcherListener
mViewPager = v.findViewById(R.id.vpPager); mViewPager = v.findViewById(R.id.vpPager);
adapterViewPager = new DaySwitcherAdapter(getChildFragmentManager()); adapterViewPager = new DaySwitcherAdapter(getChildFragmentManager());
mViewPager.setAdapter(adapterViewPager); mViewPager.setAdapter(adapterViewPager);
applyPageRequest();
refresh(false); refresh();
return v; return v;
} }
@@ -90,8 +103,8 @@ public class DaySwitcherFragment extends Fragment implements DaySwitcherListener
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
super.onAttach(context); super.onAttach(context);
if (context instanceof MainAcitivityListener) { if (context instanceof MainActivityListener) {
mListener = (MainAcitivityListener) context; mListener = (MainActivityListener) context;
} else { } else {
throw new RuntimeException(context.toString() throw new RuntimeException(context.toString()
+ " must implement MainActivityListener"); + " must implement MainActivityListener");
@@ -104,8 +117,8 @@ public class DaySwitcherFragment extends Fragment implements DaySwitcherListener
mListener = null; mListener = null;
} }
private void refresh(boolean forceRefresh) { private void refresh() {
refresh(forceRefresh, null, null); refresh(false, null, null);
} }
private void refresh(boolean forceRefresh, NetworkCallback<Canteen> callback, NetworkErrorCallback errorCallback) { 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(); CanteenBrowser browser = ((MainActivity) getActivity()).getCanteenBrowser();
browser.getCanteens(canteens -> { browser.getCanteens(canteens -> {
Canteen canteen = canteens.getCanteen(mCanteenId); Canteen canteen = canteens.getCanteen(mCanteenId);
canteen.cleanUpDays();
adapterViewPager.setModule(canteen); adapterViewPager.setModule(canteen);
applyPageRequest();
browser.getCanteen(canteen, success -> { browser.getCanteen(canteen, success -> {
adapterViewPager.setModule(); adapterViewPager.setModule();
applyPageRequest();
if (callback != null) if (callback != null)
callback.onResponse(success); callback.onResponse(success);
}, error -> { }, error -> {
@@ -136,4 +152,20 @@ public class DaySwitcherFragment extends Fragment implements DaySwitcherListener
refresh(true, callback, errorCallback); 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; 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.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback; import de.sebse.fuplanner.tools.network.NetworkErrorCallback;

View File

@@ -4,16 +4,28 @@ import android.content.Context;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.R;
import de.sebse.fuplanner.services.Canteen.types.Day; import de.sebse.fuplanner.services.canteen.types.Day;
import de.sebse.fuplanner.services.Canteen.types.Meal; import de.sebse.fuplanner.services.canteen.types.Meal;
import de.sebse.fuplanner.tools.Preferences; 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; 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 Day mDay = null;
private final Context mContext; private final Context mContext;
@@ -22,105 +34,104 @@ class MealAdapter extends BaseExpandableListAdapter {
mContext = context; mContext = context;
} }
@NonNull
@Override @Override
public String getChild(int groupPosition, int childPosititon) { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
StringBuilder sb = new StringBuilder(); if (viewType == 0) {
sb.append("\n\n"); View view = LayoutInflater.from(viewGroup.getContext())
for (String s : this.getGroup(groupPosition).getNotes()) .inflate(R.layout.list_canteen_items, viewGroup, false);
{ return new MealViewHolder(view);
sb.append(s); } else {
sb.append("\n\n"); View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.list_all_caption, viewGroup, false);
return new StringViewHolder(view);
} }
return sb.toString();
} }
@Override @Override
public long getChildId(int groupPosition, int childPosition) { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
return childPosition; if (holder.getItemViewType() == 0) {
} MealViewHolder viewHolder = ((MealViewHolder) holder);
viewHolder.reset();
@Override Meal meal = getMeal(position);
public View getChildView(int groupPosition, final int childPosition, viewHolder.mTitle.setText(meal.getName());
boolean isLastChild, View convertView, ViewGroup parent) { String value;
switch (Preferences.getStringArray(mContext, R.array.pref_price_group)) {
final String childText = getChild(groupPosition, childPosition); case "student":
if (meal.getPriceStdnt() < 0)
if (convertView == null) { value = mContext.getResources().getString(R.string.no_price_available);
convertView = LayoutInflater.from(parent.getContext()) else
.inflate(R.layout.list_all_string, parent, false); 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 @Override
public int getChildrenCount(int groupPosition) { public int getItemCount() {
return 1; return matches.size();
} }
@Override @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) if (this.mDay != null)
return this.mDay.get(groupPosition); return this.mDay.get((Integer) matches.get(position));
else else
return null; return null;
} }
@Override private String getHeading(int position) {
public int getGroupCount() { return (String) matches.get(position);
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;
} }
public void setDay(Day day) { public void setDay(Day day) {
@@ -129,6 +140,37 @@ class MealAdapter extends BaseExpandableListAdapter {
} }
public void setDay() { 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(); this.notifyDataSetChanged();
} }
} }

View File

@@ -3,19 +3,19 @@ package de.sebse.fuplanner.fragments.canteen;
import android.content.Context; import android.content.Context;
import android.os.Bundle; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.MainActivity;
import de.sebse.fuplanner.R; import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.Canteen.CanteenBrowser; import de.sebse.fuplanner.services.canteen.CanteenBrowser;
import de.sebse.fuplanner.services.Canteen.types.Canteen; import de.sebse.fuplanner.services.canteen.types.Canteen;
import de.sebse.fuplanner.services.Canteen.types.Day; import de.sebse.fuplanner.services.canteen.types.Day;
import de.sebse.fuplanner.tools.logging.Logger; 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 * Use this factory method to create a new instance of
* this fragment using the provided parameters. * this fragment using the provided parameters.
* *
* @param canteenId Item position in module list. * @param canteenId ID od current canteen.
* @param dayPosition Item position in module list. * @param dayPosition day to show.
* @return A new instance of fragment ModDetailAnnounceFragment. * @return A new instance of fragment MealFragment.
*/ */
public static MealFragment newInstance(int canteenId, int dayPosition) { public static MealFragment newInstance(int canteenId, int dayPosition) {
MealFragment fragment = new MealFragment(); MealFragment fragment = new MealFragment();
@@ -69,9 +69,9 @@ public class MealFragment extends Fragment {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
// Inflate the layout for this fragment // 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 // Set the adapter
ExpandableListView expandableListView = view.findViewById(R.id.list); RecyclerView expandableListView = view.findViewById(R.id.list);
adapter = new MealAdapter(getContext()); adapter = new MealAdapter(getContext());
expandableListView.setAdapter(adapter); expandableListView.setAdapter(adapter);

View File

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

View File

@@ -1,107 +1,36 @@
package de.sebse.fuplanner.fragments.moddetails; package de.sebse.fuplanner.fragments.moddetails;
import android.content.res.Resources;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.R;
import de.sebse.fuplanner.services.KVV.types.Announcement; import de.sebse.fuplanner.services.kvv.ui.Download;
import de.sebse.fuplanner.services.KVV.types.Modules; import de.sebse.fuplanner.services.kvv.types.Announcement;
import de.sebse.fuplanner.tools.DateUtils; import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.tools.ui.ItemViewHolder; import de.sebse.fuplanner.tools.Regex;
import de.sebse.fuplanner.tools.ui.StringViewHolder; import de.sebse.fuplanner.tools.UtilsDate;
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;
@NonNull private final Download.OnDownloadRequestInterface requestInterface;
@Override ModDetailAnnounceAdapter(@NonNull Download.OnDownloadRequestInterface requestInterface) {
public String getChild(int groupPosition, int childPosititon) { this.requestInterface = requestInterface;
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;
} }
public void setModule(Modules.Module module) { public void setModule(Modules.Module module) {
@@ -109,7 +38,90 @@ class ModDetailAnnounceAdapter extends BaseExpandableListAdapter {
this.setModule(); this.setModule();
} }
public void setModule() { private void setModule() {
this.notifyDataSetChanged(); 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; package de.sebse.fuplanner.fragments.moddetails;
import android.content.Context;
import android.os.Bundle; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.MainActivity;
import de.sebse.fuplanner.R; 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; 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 * Use the {@link ModDetailAnnounceFragment#newInstance} factory method to
* create an instance of this fragment. * 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 static final String ARG_POSITION = "itemPosition";
private String mItemPos; private String mItemPos;
private final Logger log = new Logger(this); private final Logger log = new Logger(this);
private ModDetailAnnounceAdapter adapter; private ModDetailAnnounceAdapter adapter;
private SwipeRefreshLayout swipeLayout; private SwipeRefreshLayout swipeLayout;
private Download download;
@Nullable private MainActivityListener mListener;
public ModDetailAnnounceFragment() { public ModDetailAnnounceFragment() {
@@ -61,10 +66,10 @@ public class ModDetailAnnounceFragment extends Fragment {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
// Inflate the layout for this fragment // 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 // Set the adapter
ExpandableListView expandableListView = view.findViewById(R.id.list); RecyclerView expandableListView = view.findViewById(R.id.list);
adapter = new ModDetailAnnounceAdapter(); adapter = new ModDetailAnnounceAdapter(this);
expandableListView.setAdapter(adapter); expandableListView.setAdapter(adapter);
// Getting SwipeContainerLayout // Getting SwipeContainerLayout
@@ -77,22 +82,55 @@ public class ModDetailAnnounceFragment extends Fragment {
} }
private void refresh(boolean forceRefresh) { private void refresh(boolean forceRefresh) {
if (getActivity() != null) { if (mListener != null) {
KVV kvv = ((MainActivity) getActivity()).getKVV(); mListener.getKVV(kvv -> {
kvv.getModule(mItemPos, (Modules.Module module) -> { kvv.modules().details().recv(mItemPos, pair -> {
adapter.setModule(module); adapter.setModule(pair.first);
kvv.getModuleAnnouncements(module, success1 -> { if (pair.second)
adapter.setModule(); swipeLayout.setRefreshing(false);
swipeLayout.setRefreshing(false);
}, error -> { }, error -> {
swipeLayout.setRefreshing(false); swipeLayout.setRefreshing(false);
log.e(error); log.e(error);
}, forceRefresh); }, forceRefresh);
}, error -> { });
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
} }
} }
@Override
public void request(String title, String url) {
if (mListener == null)
return;
mListener.getKVV(kvv -> {
kvv.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,36 @@
package de.sebse.fuplanner.fragments.moddetails; package de.sebse.fuplanner.fragments.moddetails;
import android.content.res.Resources;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.R;
import de.sebse.fuplanner.services.KVV.types.Assignment; import de.sebse.fuplanner.services.kvv.ui.Download;
import de.sebse.fuplanner.services.KVV.types.Modules; import de.sebse.fuplanner.services.kvv.types.Assignment;
import de.sebse.fuplanner.tools.DateUtils; import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.tools.ui.ItemViewHolder; import de.sebse.fuplanner.tools.Regex;
import de.sebse.fuplanner.tools.ui.StringViewHolder; import de.sebse.fuplanner.tools.UtilsDate;
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;
@NonNull private final Download.OnDownloadRequestInterface requestInterface;
@Override ModDetailAssignmentAdapter(@NonNull Download.OnDownloadRequestInterface requestInterface) {
public String getChild(int groupPosition, int childPosititon) { this.requestInterface = requestInterface;
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;
} }
public void setModule(Modules.Module module) { public void setModule(Modules.Module module) {
@@ -112,7 +38,90 @@ class ModDetailAssignmentAdapter extends BaseExpandableListAdapter {
this.setModule(); this.setModule();
} }
public void setModule() { private void setModule() {
this.notifyDataSetChanged(); 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; package de.sebse.fuplanner.fragments.moddetails;
import android.content.Context;
import android.os.Bundle; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.MainActivity;
import de.sebse.fuplanner.R; import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.KVV; import de.sebse.fuplanner.services.kvv.ui.Download;
import de.sebse.fuplanner.services.KVV.types.Modules; import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger; 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 * Use the {@link ModDetailAssignmentFragment#newInstance} factory method to
* create an instance of this fragment. * 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 static final String ARG_POSITION = "itemPosition";
private String mItemPos; private String mItemPos;
private final Logger log = new Logger(this); private final Logger log = new Logger(this);
private ModDetailAssignmentAdapter adapter; private ModDetailAssignmentAdapter adapter;
private SwipeRefreshLayout swipeLayout; private SwipeRefreshLayout swipeLayout;
private Download download;
@Nullable private MainActivityListener mListener;
public ModDetailAssignmentFragment() { public ModDetailAssignmentFragment() {
@@ -61,10 +66,10 @@ public class ModDetailAssignmentFragment extends Fragment {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
// Inflate the layout for this fragment // 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 // Set the adapter
ExpandableListView expandableListView = view.findViewById(R.id.list); RecyclerView expandableListView = view.findViewById(R.id.list);
adapter = new ModDetailAssignmentAdapter(); adapter = new ModDetailAssignmentAdapter(this);
expandableListView.setAdapter(adapter); expandableListView.setAdapter(adapter);
// Getting SwipeContainerLayout // Getting SwipeContainerLayout
@@ -77,22 +82,55 @@ public class ModDetailAssignmentFragment extends Fragment {
} }
private void refresh(boolean forceRefresh) { private void refresh(boolean forceRefresh) {
if (getActivity() != null) { if (mListener != null) {
KVV kvv = ((MainActivity) getActivity()).getKVV(); mListener.getKVV(kvv -> {
kvv.getModule(mItemPos, (Modules.Module module) -> { kvv.modules().details().recv(mItemPos, pair -> {
adapter.setModule(module); adapter.setModule(pair.first);
kvv.getModuleAssignments(module, success1 -> { if (pair.second)
adapter.setModule(); swipeLayout.setRefreshing(false);
swipeLayout.setRefreshing(false);
}, error -> { }, error -> {
swipeLayout.setRefreshing(false); swipeLayout.setRefreshing(false);
log.e(error); log.e(error);
}, forceRefresh); }, forceRefresh);
}, error -> { });
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
} }
} }
@Override
public void request(String title, String url) {
if (mListener == null)
return;
mListener.getKVV(kvv -> {
kvv.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(), "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,46 @@
package de.sebse.fuplanner.fragments.moddetails; package de.sebse.fuplanner.fragments.moddetails;
import android.support.annotation.NonNull; import android.app.AlertDialog;
import android.support.v7.widget.RecyclerView; import android.content.Context;
import android.content.res.Resources;
import android.text.TextUtils;
import android.util.Pair; import android.util.Pair;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList; 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.R;
import de.sebse.fuplanner.services.KVV.types.Event; import de.sebse.fuplanner.services.kvv.types.Event;
import de.sebse.fuplanner.services.KVV.types.Modules; import de.sebse.fuplanner.services.kvv.types.EventList;
import de.sebse.fuplanner.tools.DateUtils; 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.ui.CustomViewHolder; import de.sebse.fuplanner.tools.ui.CustomViewHolder;
import de.sebse.fuplanner.tools.ui.ItemViewHolder; import de.sebse.fuplanner.tools.ui.ItemViewHolder;
import de.sebse.fuplanner.tools.ui.StringViewHolder;
class ModDetailEventAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { class ModDetailEventAdapter extends RecyclerView.Adapter<CustomViewHolder> {
private static final int TYPE_HEADER = 0; private static final String VALUE_LECTURE = "Class section - Lecture";
private static final int TYPE_ITEM = 1; 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 TYPE_NONE = 0;
private static final int SECTION_PAST = 1; 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 Modules.Module mValue;
private final ArrayList<Pair<Integer, Integer>> mPositionalData; private final ArrayList<Pair<Integer, Object>> mPositionalData;
ModDetailEventAdapter() { ModDetailEventAdapter() {
mValue = null; mValue = null;
@@ -38,94 +53,196 @@ class ModDetailEventAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
this.setModule(); this.setModule();
} }
public void setModule() { private void setModule() {
mPositionalData.clear(); LinkedHashMap<String, GroupedEvents> listsGrouped = new LinkedHashMap<>();
addPositionalListData(getUpcomingEventsCount(), SECTION_UPCOMING); LinkedHashMap<String, EventList> listsUngrouped = new LinkedHashMap<>();
mPositionalData.add(new Pair<>(TYPE_HEADER, SECTION_PAST)); for (String value : VALUES_GROUPED) listsGrouped.put(value, new GroupedEvents());
addPositionalListData(getPastEventsCount(), SECTION_PAST); for (String value : VALUES_UNGROUPED) listsUngrouped.put(value, new EventList());
listsUngrouped.put(VALUE_OTHER, new EventList());
this.notifyDataSetChanged(); for (int i = 0; i < getEventsCount(); i++) {
} assert mValue.events != null;
Event event = mValue.events.get(i);
private void addPositionalListData(int count, int category) { String type = event.getType();
for (int i = 0; i < count; i++) { GroupedEvents groupedEvents = listsGrouped.get(type);
mPositionalData.add(new Pair<>(TYPE_ITEM, category+1024*i)); 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()));
ArrayList<GroupedEvents.Group> ungroupedGroups = new ArrayList<>();
for (GroupedEvents.Group group: value.getValue().getGroups()) {
boolean showAsGrouped = true;
if (group.getSkippedDates().size() > 3 || group.size() < 3) {
showAsGrouped = false;
}
if (showAsGrouped) {
mPositionalData.add(new Pair<>(TYPE_GROUPED, group));
} else {
ungroupedGroups.add(group);
}
}
for (GroupedEvents.Group ungroupedGroup : ungroupedGroups) {
for (Event event: ungroupedGroup) {
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 @NonNull
@Override @Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public CustomViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view; View view;
switch (viewType) { switch (viewType) {
case TYPE_HEADER: case TYPE_GROUPED:
view = LayoutInflater.from(parent.getContext()) case TYPE_UNGROUPED:
.inflate(R.layout.list_all_caption, parent, false);
return new HeaderViewHolder(view);
case TYPE_ITEM:
view = LayoutInflater.from(parent.getContext()) view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_items, parent, false); .inflate(R.layout.list_all_items, parent, false);
return new ItemViewHolder(view); 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: default:
//noinspection ConstantConditions view = LayoutInflater.from(parent.getContext())
return null; .inflate(R.layout.list_all_no_items, parent, false);
return new CustomViewHolder(view);
} }
} }
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
// Note that unlike in ListView adapters, types don't have to be contiguous // Note that unlike in ListView adapters, types don't have to be contiguous
if (position < mPositionalData.size()) return mPositionalData.get(position).first;
return mPositionalData.get(position).first;
else return -1;
} }
@Override @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()) if (mValue == null || position > mPositionalData.size())
return; 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) { switch (data.first) {
case TYPE_HEADER: case TYPE_HEADER:
HeaderViewHolder h = (HeaderViewHolder) holder; StringViewHolder s = (StringViewHolder) holder;
switch (data.second) { String title = "";
case SECTION_PAST: if (VALUE_LECTURE.equals(data.second)) {
h.mCaption.setText(R.string.past_events); title = resources.getString(R.string.lecture);
break;
case SECTION_UPCOMING: } else if (VALUE_TUTORIAL.equals(data.second)) {
h.mCaption.setText(R.string.upcoming_events); title = resources.getString(R.string.tutorial);
break;
} 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; s.mString.setText(title);
case TYPE_ITEM: return;
int section = data.second % 1024; case TYPE_GROUPED:
int index = data.second / 1024; ItemViewHolder ig = (ItemViewHolder) holder;
ItemViewHolder i = (ItemViewHolder) holder; GroupedEvents.Group group = ((GroupedEvents.Group) data.second);
Event event = null; long firstDateTime = group.getFirstDate()+group.getStartTime();
switch (section) { long lastDateTime = group.getLastDate()+group.getStartTime()+group.getDuration();
case SECTION_UPCOMING: String start, end, weekday, startTime, endTime;
event = mValue.events.getUpcoming(index); StringBuilder excepts = null;
break; start = UtilsDate.getModifiedDate(firstDateTime);
case SECTION_PAST: end = UtilsDate.getModifiedDate(lastDateTime);
event = mValue.events.getPast(index); weekday = UtilsDate.getModifiedDate(context, firstDateTime, "E");
break; startTime = UtilsDate.getModifiedTime(context, firstDateTime);
endTime = UtilsDate.getModifiedTime(context, lastDateTime+1);
for (long skippedDate : group.getSkippedDates()) {
if (excepts == null) {
excepts = new StringBuilder(UtilsDate.getModifiedDate(skippedDate));
} else {
excepts.append(", ").append(UtilsDate.getModifiedDate(skippedDate));
}
} }
//noinspection ConstantConditions String groupTitle = context.getString(R.string.event_scale, weekday, startTime, endTime);
i.mTitle.setText(event.getTitle()); if (!TextUtils.isEmpty(group.getTitle()) && !group.getTitle().equals(mValue.title))
i.mSubLeft.setText(event.getType()); groupTitle += " ("+group.getTitle()+")";
String start, end; ig.mTitle.setText(groupTitle);
if (DateUtils.dateEquals(event.getStartDate(), System.currentTimeMillis())) ig.mSubLeft.setText(context.getString(R.string.date_scale, start, end));
start = DateUtils.getModifiedTime(i.mView.getContext(), event.getStartDate()); ig.mSubRight.setText(excepts != null ? context.getString(R.string.except_list, excepts.toString()) : "");
String moduleName = mValue.title;
StringBuilder finalExcepts = excepts;
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(ig.mView.getContext());
ig.mView.setOnClickListener(v -> {
alertDialogBuilder
.setTitle(R.string.events)
.setMessage(
ig.mView.getResources().getString(R.string.module_name, moduleName) + "\n" +
ig.mView.getResources().getString(R.string.location_name, group.getLocation()) + "\n" +
context.getString(R.string.event_scale, weekday, startTime, endTime) + "\n" +
context.getString(R.string.date_scale, start, end) + "\n" +
(finalExcepts != null ? context.getString(R.string.except_list, finalExcepts.toString()) : "")
)
.setCancelable(true)
.setNeutralButton(R.string.close, (dialog, id) -> dialog.cancel());
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
});
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 else
start = DateUtils.getModifiedDateTime(i.mView.getContext(), event.getStartDate()); start = UtilsDate.getModifiedDateTime(context, event.getStartDate());
if (DateUtils.dateEquals(event.getStartDate(), event.getEndDate())) if (UtilsDate.dateEquals(event.getStartDate(), event.getEndDate()))
end = DateUtils.getModifiedTime(i.mView.getContext(), event.getEndDate()); end = UtilsDate.getModifiedTime(context, event.getEndDate()+1);
else else
end = DateUtils.getModifiedDateTime(i.mView.getContext(), event.getEndDate()); end = UtilsDate.getModifiedDateTime(context, event.getEndDate()+1);
i.mSubRight.setText(i.mView.getResources().getString(R.string.date_scale, date = context.getString(R.string.date_scale, start, end);
start, end iu.mTitle.setText(event.getTitle());
)); iu.mSubLeft.setText(date);
break; iu.mSubRight.setText(event.getLocation());
String moduleName1 = mValue.title;
AlertDialog.Builder alertDialogBuilder1 = new AlertDialog.Builder(iu.mView.getContext());
iu.mView.setOnClickListener(v -> {
alertDialogBuilder1
.setTitle(moduleName1)
.setMessage(
iu.mView.getResources().getString(R.string.module_name, moduleName1) + "\n" +
iu.mView.getResources().getString(R.string.location_name, event.getLocation()) + "\n" +
date
)
.setCancelable(true)
.setNeutralButton(R.string.close, (dialog, id) -> dialog.cancel());
AlertDialog alertDialog = alertDialogBuilder1.create();
alertDialog.show();
});
} }
} }
@@ -134,38 +251,9 @@ class ModDetailEventAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
return mPositionalData.size(); return mPositionalData.size();
} }
private int getUpcomingEventsCount() { private int getEventsCount() {
if (mValue.events != null) if (mValue.events != null)
return mValue.events.sizeUpcoming(); return mValue.events.size();
return 0; 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.content.Context;
import android.os.Bundle; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.R;
import de.sebse.fuplanner.services.KVV.KVV; import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.logging.Logger; import de.sebse.fuplanner.tools.logging.Logger;
/** /**
@@ -30,6 +29,7 @@ public class ModDetailEventFragment extends Fragment {
private final Logger log = new Logger(this); private final Logger log = new Logger(this);
private ModDetailEventAdapter adapter; private ModDetailEventAdapter adapter;
private SwipeRefreshLayout swipeLayout; private SwipeRefreshLayout swipeLayout;
@Nullable private MainActivityListener mListener;
public ModDetailEventFragment() { public ModDetailEventFragment() {
@@ -81,22 +81,34 @@ public class ModDetailEventFragment extends Fragment {
} }
private void refresh(boolean forceRefresh) { private void refresh(boolean forceRefresh) {
if (getActivity() != null) { if (mListener != null) {
KVV kvv = ((MainActivity) getActivity()).getKVV(); mListener.getKVV(kvv -> {
kvv.getModule(mItemPos, (Modules.Module module) -> { kvv.modules().details().recv(mItemPos, pair -> {
adapter.setModule(module); adapter.setModule(pair.first);
kvv.getModuleEvents(module, success1 -> { if (pair.second)
adapter.setModule(); swipeLayout.setRefreshing(false);
swipeLayout.setRefreshing(false);
}, error -> { }, error -> {
swipeLayout.setRefreshing(false); swipeLayout.setRefreshing(false);
log.e(error); log.e(error);
}, forceRefresh); }, 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.content.Context;
import android.os.Bundle; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.R;
import de.sebse.fuplanner.services.KVV.types.Modules; import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.tools.MainAcitivityListener; import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger; import de.sebse.fuplanner.tools.logging.Logger;
/** /**
* A simple {@link Fragment} subclass. * A simple {@link Fragment} subclass.
* Activities that contain this fragment must implement the * Activities that contain this fragment must implement the
* {@link MainAcitivityListener} interface * {@link MainActivityListener} interface
* to handle interaction events. * to handle interaction events.
* Use the {@link ModDetailFragment#newInstance} factory method to * Use the {@link ModDetailFragment#newInstance} factory method to
* create an instance of this fragment. * create an instance of this fragment.
@@ -28,9 +28,10 @@ public class ModDetailFragment extends Fragment implements ModDetailListener {
// Parameters // Parameters
private String mItemPos; private String mItemPos;
private MainAcitivityListener mListener; private MainActivityListener mListener;
private final Logger log = new Logger(this); private final Logger log = new Logger(this);
private ViewPager mViewPager; private ViewPager mViewPager;
private String mPageRestoreRequest = null;
public ModDetailFragment() { public ModDetailFragment() {
// Required empty public constructor // Required empty public constructor
@@ -46,7 +47,11 @@ public class ModDetailFragment extends Fragment implements ModDetailListener {
public static Fragment newInstance(String itemPosition) { public static Fragment newInstance(String itemPosition) {
ModDetailFragment fragment = new ModDetailFragment(); ModDetailFragment fragment = new ModDetailFragment();
Bundle args = new Bundle(); 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); fragment.setArguments(args);
return fragment; return fragment;
} }
@@ -55,15 +60,25 @@ public class ModDetailFragment extends Fragment implements ModDetailListener {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (getArguments() != null) { 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) { if (mListener != null) {
mListener.onTitleTextChange(R.string.courses); mListener.onTitleTextChange(R.string.courses);
mListener.getKVV().getModuleList(success -> { mListener.getKVV(kvv -> {
Modules.Module module = success.get(mItemPos); kvv.modules().list().recv(success -> {
if (mListener != null && module != null) Modules.Module module = success.get(mItemPos);
mListener.onTitleTextChange(module.title); if (mListener != null && module != null)
}, log::e); mListener.onTitleTextChange(module.title);
}, log::e);
});
} }
} }
@@ -76,14 +91,16 @@ public class ModDetailFragment extends Fragment implements ModDetailListener {
mViewPager = v.findViewById(R.id.vpPager); mViewPager = v.findViewById(R.id.vpPager);
ModDetailAdapter adapterViewPager = new ModDetailAdapter(getChildFragmentManager(), mItemPos, getContext()); ModDetailAdapter adapterViewPager = new ModDetailAdapter(getChildFragmentManager(), mItemPos, getContext());
mViewPager.setAdapter(adapterViewPager); mViewPager.setAdapter(adapterViewPager);
if (mPageRestoreRequest != null)
mViewPager.setCurrentItem(Integer.parseInt(mPageRestoreRequest));
return v; return v;
} }
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
super.onAttach(context); super.onAttach(context);
if (context instanceof MainAcitivityListener) { if (context instanceof MainActivityListener) {
mListener = (MainAcitivityListener) context; mListener = (MainActivityListener) context;
} else { } else {
throw new RuntimeException(context.toString() throw new RuntimeException(context.toString()
+ " must implement MainActivityListener"); + " must implement MainActivityListener");
@@ -100,4 +117,8 @@ public class ModDetailFragment extends Fragment implements ModDetailListener {
public void gotoFragmentPart(int part, int index) { public void gotoFragmentPart(int part, int index) {
mViewPager.setCurrentItem(ModulePart.getPageByPart(part), true); 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; package de.sebse.fuplanner.fragments.moddetails;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.util.Pair; import android.util.Pair;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -11,10 +9,12 @@ import android.widget.TextView;
import java.util.ArrayList; import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import de.sebse.fuplanner.R; import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.types.Gradebook; import de.sebse.fuplanner.services.kvv.types.Grade;
import de.sebse.fuplanner.services.KVV.types.Modules; import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.tools.ui.CustomViewHolder; import de.sebse.fuplanner.tools.ui.StringViewHolder;
class ModDetailGradebookAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { class ModDetailGradebookAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_TOTAL = 0; private static final int TYPE_TOTAL = 0;
@@ -36,20 +36,16 @@ class ModDetailGradebookAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
this.setModule(); this.setModule();
} }
public void setModule() { private void setModule() {
mPositionalData.clear(); mPositionalData.clear();
mPositionalData.add(new Pair<>(TYPE_TOTAL, SECTION_GRADE)); 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(); 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 @NonNull
@Override @Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -58,11 +54,11 @@ class ModDetailGradebookAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
case TYPE_TOTAL: case TYPE_TOTAL:
view = LayoutInflater.from(parent.getContext()) view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_caption, parent, false); .inflate(R.layout.list_all_caption, parent, false);
return new HeaderViewHolder(view); return new StringViewHolder(view);
case TYPE_GRADE: case TYPE_GRADE:
view = LayoutInflater.from(parent.getContext()) view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_modetails_gradebook, parent, false); .inflate(R.layout.list_moddetails_gradebook, parent, false);
return new GradebookViewHolder(view); return new GradeViewHolder(view);
default: default:
//noinspection ConstantConditions //noinspection ConstantConditions
return null; return null;
@@ -85,13 +81,13 @@ class ModDetailGradebookAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
Pair<Integer, Integer> data = mPositionalData.get(position); Pair<Integer, Integer> data = mPositionalData.get(position);
switch (data.first) { switch (data.first) {
case TYPE_TOTAL: case TYPE_TOTAL:
HeaderViewHolder h = (HeaderViewHolder) holder; StringViewHolder h = (StringViewHolder) holder;
h.mCaption.setText(h.mView.getResources().getString(R.string.current_percentage, mValue.getGradebookPercent()*100)); h.mString.setText(h.mView.getResources().getString(R.string.current_percentage, mValue.getGradebookPercent()*100));
break; break;
case TYPE_GRADE: case TYPE_GRADE:
int index = data.second / 1024; int index = data.second / 1024;
GradebookViewHolder i = (GradebookViewHolder) holder; GradeViewHolder i = (GradeViewHolder) holder;
Gradebook gradebook = mValue.gradebook.get(index); Grade gradebook = mValue.gradebook.get(index);
i.mTitle.setText(gradebook.getItemName()); i.mTitle.setText(gradebook.getItemName());
i.mGrade.setText(String.valueOf(gradebook.getPoints())); 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) { private class GradeViewHolder extends RecyclerView.ViewHolder {
super(view);
mCaption = view.findViewById(R.id.caption);
}
@Override
public String toString() {
return super.toString() + " '" + mCaption.getText() + "'";
}
}
private class GradebookViewHolder extends RecyclerView.ViewHolder {
private final TextView mGrade; private final TextView mGrade;
private final TextView mGradeMax; private final TextView mGradeMax;
private final TextView mTitle; private final TextView mTitle;
GradebookViewHolder(View view) { GradeViewHolder(View view) {
super(view); super(view);
mTitle = view.findViewById(R.id.title); mTitle = view.findViewById(R.id.title);
mGrade = view.findViewById(R.id.grade); mGrade = view.findViewById(R.id.grade);
mGradeMax = view.findViewById(R.id.grade_max); mGradeMax = view.findViewById(R.id.grade_max);
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
return super.toString() + " '" + mTitle.getText() + " '" + mGrade.getText() + " '" + mGradeMax.getText() + "'"; 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.content.Context;
import android.os.Bundle; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.R;
import de.sebse.fuplanner.services.KVV.KVV; import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.logging.Logger; import de.sebse.fuplanner.tools.logging.Logger;
/** /**
@@ -30,6 +29,7 @@ public class ModDetailGradebookFragment extends Fragment {
private final Logger log = new Logger(this); private final Logger log = new Logger(this);
private ModDetailGradebookAdapter adapter; private ModDetailGradebookAdapter adapter;
private SwipeRefreshLayout swipeLayout; private SwipeRefreshLayout swipeLayout;
@Nullable private MainActivityListener mListener;
public ModDetailGradebookFragment() { public ModDetailGradebookFragment() {
@@ -81,22 +81,34 @@ public class ModDetailGradebookFragment extends Fragment {
} }
private void refresh(boolean forceRefresh) { private void refresh(boolean forceRefresh) {
if (getActivity() != null) { if (mListener != null) {
KVV kvv = ((MainActivity) getActivity()).getKVV(); mListener.getKVV(kvv -> {
kvv.getModule(mItemPos, (Modules.Module module) -> { kvv.modules().details().recv(mItemPos, pair -> {
adapter.setModule(module); adapter.setModule(pair.first);
kvv.getModuleGradebook(module, success1 -> { if (pair.second)
adapter.setModule(); swipeLayout.setRefreshing(false);
swipeLayout.setRefreshing(false);
}, error -> { }, error -> {
swipeLayout.setRefreshing(false); swipeLayout.setRefreshing(false);
log.e(error); log.e(error);
}, forceRefresh); }, 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; package de.sebse.fuplanner.fragments.moddetails;
import android.support.annotation.NonNull; import android.content.Intent;
import android.support.annotation.Nullable; import android.net.Uri;
import android.support.v7.widget.RecyclerView; import android.text.TextUtils;
import android.util.Pair; import android.util.Pair;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView;
import com.ms.square.android.expandabletextview.ExpandableTextView; import com.ms.square.android.expandabletextview.ExpandableTextView;
import java.util.ArrayList; 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.R;
import de.sebse.fuplanner.services.KVV.types.Announcement; import de.sebse.fuplanner.services.kvv.types.Announcement;
import de.sebse.fuplanner.services.KVV.types.Assignment; import de.sebse.fuplanner.services.kvv.types.Assignment;
import de.sebse.fuplanner.services.KVV.types.Event; import de.sebse.fuplanner.services.kvv.types.Event;
import de.sebse.fuplanner.services.KVV.types.Modules; import de.sebse.fuplanner.services.kvv.types.Lecturer;
import de.sebse.fuplanner.tools.DateUtils; import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.tools.logging.Logger; import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.ui.CustomViewHolder; import de.sebse.fuplanner.tools.ui.CustomViewHolder;
import de.sebse.fuplanner.tools.ui.ItemViewHolder; 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> { class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int MAX_ITEMS_PER_PREVIEW = 2; private static final int MAX_ITEMS_PER_PREVIEW = 2;
@@ -30,12 +35,14 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
private static final int TYPE_DESCRIPTION = 1; private static final int TYPE_DESCRIPTION = 1;
private static final int TYPE_ITEM = 2; private static final int TYPE_ITEM = 2;
private static final int TYPE_SHOW_MORE = 3; private static final int TYPE_SHOW_MORE = 3;
private static final int TYPE_MAIL = 4;
private static final int TYPE_SHORTCUTS = 5;
private static final int TYPE_NO_ITEMS = 6;
@Nullable private final ModDetailListener mListener; @Nullable private final ModDetailListener mListener;
private Modules.Module mValue; private Modules.Module mValue;
private final ArrayList<Pair<Integer, Object>> mPositionalData; private final ArrayList<Pair<Integer, Object>> mPositionalData;
private Logger log = new Logger(this);
ModDetailOverviewAdapter(@Nullable final ModDetailListener listener) { ModDetailOverviewAdapter(@Nullable final ModDetailListener listener) {
mValue = null; mValue = null;
@@ -48,10 +55,17 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
this.setModule(); this.setModule();
} }
public void setModule() { private void setModule() {
mPositionalData.clear(); mPositionalData.clear();
mPositionalData.add(new Pair<>(TYPE_HEADER, ModulePart.DESCRIPTION)); if (!TextUtils.isEmpty(mValue.description)) {
mPositionalData.add(new Pair<>(TYPE_DESCRIPTION, null)); 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)); mPositionalData.add(new Pair<>(TYPE_HEADER, ModulePart.ANNOUNCEMENT));
addPositionalListData(getAnnounceCount(), ModulePart.ANNOUNCEMENT); addPositionalListData(getAnnounceCount(), ModulePart.ANNOUNCEMENT);
mPositionalData.add(new Pair<>(TYPE_HEADER, ModulePart.ASSIGNMENT)); mPositionalData.add(new Pair<>(TYPE_HEADER, ModulePart.ASSIGNMENT));
@@ -67,6 +81,9 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
} }
if (count > MAX_ITEMS_PER_PREVIEW) if (count > MAX_ITEMS_PER_PREVIEW)
mPositionalData.add(new Pair<>(TYPE_SHOW_MORE, category)); mPositionalData.add(new Pair<>(TYPE_SHOW_MORE, category));
if (count == 0) {
mPositionalData.add(new Pair<>(TYPE_NO_ITEMS, category));
}
} }
@NonNull @NonNull
@@ -77,7 +94,7 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
case TYPE_HEADER: case TYPE_HEADER:
view = LayoutInflater.from(parent.getContext()) view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_caption, parent, false); .inflate(R.layout.list_all_caption, parent, false);
return new HeaderViewHolder(view); return new StringViewHolder(view);
case TYPE_DESCRIPTION: case TYPE_DESCRIPTION:
view = LayoutInflater.from(parent.getContext()) view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_moddetails_description, parent, false); .inflate(R.layout.list_moddetails_description, parent, false);
@@ -90,6 +107,18 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
view = LayoutInflater.from(parent.getContext()) view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_show_more, parent, false); .inflate(R.layout.list_all_show_more, parent, false);
return new CustomViewHolder(view); return new CustomViewHolder(view);
case TYPE_NO_ITEMS:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_no_items, 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: default:
//noinspection ConstantConditions //noinspection ConstantConditions
return null; return null;
@@ -111,19 +140,22 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
Pair<Integer, Object> data = mPositionalData.get(position); Pair<Integer, Object> data = mPositionalData.get(position);
switch (data.first) { switch (data.first) {
case TYPE_HEADER: case TYPE_HEADER:
HeaderViewHolder h = (HeaderViewHolder) holder; StringViewHolder h = (StringViewHolder) holder;
switch ((Integer) data.second) { switch ((Integer) data.second) {
case ModulePart.DESCRIPTION: 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; break;
case ModulePart.ANNOUNCEMENT: 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; break;
case ModulePart.ASSIGNMENT: 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; break;
case ModulePart.EVENT: 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;
} }
break; break;
@@ -141,7 +173,7 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
Announcement announce = mValue.announcements.get(index); Announcement announce = mValue.announcements.get(index);
i.mTitle.setText(announce.getTitle()); i.mTitle.setText(announce.getTitle());
i.mSubLeft.setText(announce.getCreatedBy()); 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 -> { i.mView.setOnClickListener(view -> {
if (mListener != null) mListener.gotoFragmentPart(section, index); if (mListener != null) mListener.gotoFragmentPart(section, index);
}); });
@@ -154,7 +186,7 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
i.mSubLeft.setText(i.mView.getResources().getText(R.string.open)); i.mSubLeft.setText(i.mView.getResources().getText(R.string.open));
else else
i.mSubLeft.setText(i.mView.getResources().getText(R.string.closed)); 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 -> { i.mView.setOnClickListener(view -> {
if (mListener != null) mListener.gotoFragmentPart(section, index); if (mListener != null) mListener.gotoFragmentPart(section, index);
}); });
@@ -165,14 +197,14 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
i.mTitle.setText(event.getTitle()); i.mTitle.setText(event.getTitle());
i.mSubLeft.setText(event.getType()); i.mSubLeft.setText(event.getType());
String start, end; String start, end;
if (DateUtils.dateEquals(event.getStartDate(), System.currentTimeMillis())) if (UtilsDate.dateEquals(event.getStartDate(), System.currentTimeMillis()))
start = DateUtils.getModifiedTime(i.mView.getContext(), event.getStartDate()); start = UtilsDate.getModifiedTime(i.mView.getContext(), event.getStartDate());
else else
start = DateUtils.getModifiedDateTime(i.mView.getContext(), event.getStartDate()); start = UtilsDate.getModifiedDateTime(i.mView.getContext(), event.getStartDate());
if (DateUtils.dateEquals(event.getStartDate(), event.getEndDate())) if (UtilsDate.dateEquals(event.getStartDate(), event.getEndDate()))
end = DateUtils.getModifiedTime(i.mView.getContext(), event.getEndDate()); end = UtilsDate.getModifiedTime(i.mView.getContext(), event.getEndDate()+1);
else else
end = DateUtils.getModifiedDateTime(i.mView.getContext(), event.getEndDate()); end = UtilsDate.getModifiedDateTime(i.mView.getContext(), event.getEndDate()+1);
i.mSubRight.setText(i.mView.getResources().getString(R.string.date_scale, i.mSubRight.setText(i.mView.getResources().getString(R.string.date_scale,
start, end start, end
)); ));
@@ -182,7 +214,38 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
break; break;
} }
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: case TYPE_SHOW_MORE:
case TYPE_NO_ITEMS:
CustomViewHolder c = (CustomViewHolder) holder; CustomViewHolder c = (CustomViewHolder) holder;
c.mView.setOnClickListener(view -> { c.mView.setOnClickListener(view -> {
if (mListener != null) mListener.gotoFragmentPart((Integer) data.second, -1); if (mListener != null) mListener.gotoFragmentPart((Integer) data.second, -1);
@@ -221,19 +284,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 { class DescriptionViewHolder extends CustomViewHolder {
final ExpandableTextView mText; final ExpandableTextView mText;
@@ -243,6 +294,7 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
mText = view.findViewById(R.id.expand_text_view);//.findViewById(R.id.description); mText = view.findViewById(R.id.expand_text_view);//.findViewById(R.id.description);
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
return super.toString() + " '" + mText.getText() + "'"; return super.toString() + " '" + mText.getText() + "'";

View File

@@ -3,20 +3,18 @@ package de.sebse.fuplanner.fragments.moddetails;
import android.content.Context; import android.content.Context;
import android.os.Bundle; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.R;
import de.sebse.fuplanner.services.KVV.KVV; import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.logging.Logger; import de.sebse.fuplanner.tools.logging.Logger;
/** /**
@@ -31,7 +29,8 @@ public class ModDetailOverviewFragment extends Fragment {
private final Logger log = new Logger(this); private final Logger log = new Logger(this);
private ModDetailOverviewAdapter adapter; private ModDetailOverviewAdapter adapter;
private SwipeRefreshLayout swipeLayout; private SwipeRefreshLayout swipeLayout;
@Nullable private ModDetailListener mListener; @Nullable private ModDetailListener mDetailListener;
@Nullable private MainActivityListener mListener;
public ModDetailOverviewFragment() { public ModDetailOverviewFragment() {
@@ -70,7 +69,7 @@ public class ModDetailOverviewFragment extends Fragment {
Context context = view.getContext(); Context context = view.getContext();
RecyclerView recyclerView = view.findViewById(R.id.list); RecyclerView recyclerView = view.findViewById(R.id.list);
recyclerView.setLayoutManager(new LinearLayoutManager(context)); recyclerView.setLayoutManager(new LinearLayoutManager(context));
adapter = new ModDetailOverviewAdapter(mListener); adapter = new ModDetailOverviewAdapter(mDetailListener);
recyclerView.setAdapter(adapter); recyclerView.setAdapter(adapter);
// Getting SwipeContainerLayout // Getting SwipeContainerLayout
@@ -82,37 +81,47 @@ public class ModDetailOverviewFragment extends Fragment {
return view; return view;
} }
private void refresh(boolean forceRefresh) { private void refresh(boolean forceRefresh) {
if (getActivity() != null) { if (mListener != null) {
KVV kvv = ((MainActivity) getActivity()).getKVV(); mListener.getKVV(kvv -> {
kvv.getModule(mItemPos, (Modules.Module module) -> { kvv.modules().details().recv(mItemPos, pair -> {
adapter.setModule(module); adapter.setModule(pair.first);
kvv.getModuleDetails(module, pair -> {
adapter.setModule();
if (pair.second) if (pair.second)
swipeLayout.setRefreshing(false); swipeLayout.setRefreshing(false);
}, error -> { }, error -> {
swipeLayout.setRefreshing(false); swipeLayout.setRefreshing(false);
log.e(error); log.e(error);
}, forceRefresh); }, forceRefresh);
}, error -> { });
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
} }
} }
@Override @Override
public void onAttach(Context context) { 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); super.onAttach(context);
Fragment parentFragment = getParentFragment(); Fragment parentFragment = getParentFragment();
if (parentFragment != null) { if (parentFragment != null) {
if (parentFragment instanceof ModDetailListener) { if (parentFragment instanceof ModDetailListener) {
mListener = (ModDetailListener) parentFragment; mDetailListener = (ModDetailListener) parentFragment;
} else { } else {
throw new RuntimeException(context.toString() throw new RuntimeException(context.toString() + " must implement ModDetailListener");
+ " must implement ModDetailListener");
} }
} else log.w("No parent fragment!"); } 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,182 @@
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.DocumentNodeBinder;
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 implements Download.OnDownloadRequestInterface {
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(), new DocumentNodeBinder(this)));
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(kvv -> {
kvv.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 request(String title, String url) {
if (mListener == null)
return;
mListener.getKVV(kvv -> {
kvv.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(), "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) {
mListener.getKVV(kvv -> {
kvv.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

@@ -1,13 +1,15 @@
package de.sebse.fuplanner.fragments.moddetails; package de.sebse.fuplanner.fragments.moddetails;
class ModulePart { public class ModulePart {
static final int DESCRIPTION = 0; static final int DESCRIPTION = 0;
static final int OVERVIEW = 1; static final int OVERVIEW = 1;
static final int ANNOUNCEMENT = 2; public static final int ANNOUNCEMENT = 2;
static final int ASSIGNMENT = 3; public static final int ASSIGNMENT = 3;
static final int EVENT = 4; public static final int EVENT = 4;
static final int GRADEBOOK = 5; public static final int GRADEBOOK = 5;
private static final int[] pages = new int[]{OVERVIEW, ANNOUNCEMENT, ASSIGNMENT, EVENT, GRADEBOOK}; public 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() { static int getPageCount() {
return pages.length; 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,336 +0,0 @@
package de.sebse.fuplanner.services.KVV;
import android.content.Context;
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.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 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);
}
} 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);
}
private 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 -> {
String identJSESSIONID = success11.get("JSESSIONID");
String ident_idp_authn_lc_key = success11.get("_idp_authn_lc_key");
String identROUTEID = success11.get("ROUTEID");
loginIdent(true, username, password, identJSESSIONID, ident_idp_authn_lc_key, identROUTEID, success111 -> loginIdent(false, username, password, identJSESSIONID, ident_idp_authn_lc_key, identROUTEID, success11112 -> {
String ident_idp_session = success11112.get("_idp_session");
getSAMLResponse(identJSESSIONID, ident_idp_authn_lc_key, identROUTEID, ident_idp_session, success1111 -> loginKVV(success1111.get("RelayState"), success1111.get("SAMLResponse"), kvvJSESSIONID, success111112 -> {
LoginToken token = new LoginToken(username, success111112.get("shibsessionKey"), success111112.get("shibsessionName"), kvvJSESSIONID);
finishKVVlogin(token, success11111 -> callback.onResponse(token), error);
}, error), error);
}, error), error);
}, error), error);
}, 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
*/
private void startKVVSession(final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
get("https://kvv.imp.fu-berlin.de/portal/login", null, response -> {
String cookies = response.getHeaders().get("Set-Cookie");
if (cookies==null) {
errorCallback.onError(new NetworkError(100101, -1, "Error on starting KVV session!"));
return;
}
HashMap<String, String> object;
try {
object = getCookie(cookies, new String[]{"JSESSIONID"});
} catch (NoSuchFieldException e) {
errorCallback.onError(new NetworkError(100102, -1, "Error on starting KVV session!"));
return;
}
callback.onResponse(object);
}, error -> errorCallback.onError(new NetworkError(100100, error.networkResponse.statusCode, "Error on starting KVV session!")));
}
/*
GET https://kvv.imp.fu-berlin.de/sakai-login-tool/container
<- JSESSIONID
-> (Location-Header) https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO
?SAMLRequest=fZLLb.....Q8yre3X1IHwkJKE0Mnpy/V9TH4A
&RelayState=ss:mem:7ea01e29157b8bd906f7002176.....0d1a505f2c8bf
*/
private void getSAMLRequest(String JSESSIONID, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
HashMap<String, String> cookies = new HashMap<>();
cookies.put("JSESSIONID", JSESSIONID);
get("https://kvv.imp.fu-berlin.de/sakai-login-tool/container", cookies, response -> {
String location = response.getHeaders().get("Location");
if (location==null) {
errorCallback.onError(new NetworkError(100111, -1, "Error on getting SAML request!"));
return;
}
HashMap<String, String> object = new HashMap<>();
object.put("Location", location);
callback.onResponse(object);
}, error -> errorCallback.onError(new NetworkError(100110, error.networkResponse.statusCode, "Error on getting SAML request!")));
}
/*
GET https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO
?SAMLRequest=fZLLbsIwEEV/JfI+cWJAUIsgpbAoEi2IpF10UznxUKw6dupxaPn7hkdb2LD29bkzRzNGUeuGZ63fmjV8toA++K61QX58SEnrDLcCFXIjakDuK55njwvOopg3znpbWU2CDBGcV9ZMrcG2BpeD26kKnteLlGy9b5BT+rHbRapuok0bluC0MpEEmm9VWVoNfhshWnpgM7pa5gUJZt0wyogD9h+iJBiv/P6aomQTbtqSdhNtlIYzZg1SOag8zfMlCeazlLyNqpHsy1gO2V1fVsNBMuqJoUyAJaxXDUaiiyG2MDfohfEpYXEyDJM4ZKxgCe/FPI5fSbA6L36vjFTm/bal8hRC/lAUq/C02gs4PK7VBchkfHDNj8Xuwv5trPhVTiY3BeOf4DG96DmVNvypA89nK6tVtQ8yre3X1IHwkJKE0Mnpy/V9TH4A
&RelayState=ss:mem:7ea01e29157b8bd906f7002176213b6db5e1f45ebb88716a9820d1a505f2c8bf
-> JSESSIONID C4B6A428BA1F50746235D03F5D107A57
-> _idp_authn_lc_key 57a6ae26067f374cc3d0ccfc47e27b04b47752d2a3d4eb2782af0d3994535395
-> ROUTEID .1
*/
private void startIdentSession(String url, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
get(url, null, response -> {
String cookies = response.getHeaders().get("Set-Cookie");
if (cookies==null) {
errorCallback.onError(new NetworkError(100121, -1, "Error on starting Ident session!"));
return;
}
HashMap<String, String> object;
try {
object = getCookie(cookies, new String[]{"JSESSIONID", "_idp_authn_lc_key", "ROUTEID"});
} catch (NoSuchFieldException e) {
errorCallback.onError(new NetworkError(100122, -1, "Error on starting Ident session!"));
return;
}
callback.onResponse(object);
}, error -> errorCallback.onError(new NetworkError(100120, error.networkResponse.statusCode, "Error on starting Ident session!")));
}
/*
POST https://identity.fu-berlin.de/idp-fub/Authn/UserPassword
<- j_username seedorf96
<- j_password neinhieristpatrick
<- (Header-"Content-Type") application/x-www-form-urlencoded
<- JSESSIONID
<- _idp_authn_lc_key
<- ROUTEID
-> _idp_session OTMuMTkzLjg1LjMz|LQ==|OGYxOWI4MjA2NTQ4YWUwYzJkOWM4Mjk4YzcwZDMwZmJiZjBmMTdmMzkyZGU2OWIwY2JkNmZlNjlmNTRmNzBlMQ==|wLlzQal7VqyntmG2vLNn06wt8wQ=
*/
private void loginIdent(final boolean first, String username, String password, String JSESSIONID, String _idp_authn_lc_key, String ROUTEID, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
HashMap<String, String> cookies = new HashMap<>();
cookies.put("JSESSIONID", JSESSIONID);
cookies.put("_idp_authn_lc_key", _idp_authn_lc_key);
cookies.put("ROUTEID", ROUTEID);
HashMap<String, String> body = new HashMap<>();
body.put("j_username", username);
body.put("j_password", password);
post("https://identity.fu-berlin.de/idp-fub/Authn/UserPassword", cookies, body, response -> {
if (first) {
callback.onResponse(new HashMap<>());
return;
}
String cookies1 = response.getHeaders().get("Set-Cookie");
if (cookies1 ==null) {
errorCallback.onError(new NetworkError(100131, -1, "Error on logging in to Identity Server!"));
return;
}
HashMap<String, String> object;
try {
object = getCookie(cookies1, new String[]{"_idp_session"});
} catch (NoSuchFieldException e) {
errorCallback.onError(new NetworkError(100132, -1, "Error on logging in to Identity Server!"));
return;
}
callback.onResponse(object);
}, error -> errorCallback.onError(new NetworkError(100130, error.networkResponse.statusCode, "Error on logging in to Identity Server!")));
}
/*
GET https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO
<- JSESSIONID
<- _idp_authn_lc_key
<- ROUTEID
<- _idp_session
-> (BODY) RelayState 7ea01e29157b8bd906f7002176213b6db5e1f45ebb88716a9820d1a505f2c8bf
-> (BODY) SAMLResponse PD94bWwgdmVyc2lvbj0...........wvc2FtbDJwOlJlc3BvbnNlPg==
*/
private void getSAMLResponse(String JSESSIONID, String _idp_authn_lc_key, String ROUTEID, String _idp_session, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
HashMap<String, String> cookies = new HashMap<>();
cookies.put("JSESSIONID", JSESSIONID);
cookies.put("_idp_authn_lc_key", _idp_authn_lc_key);
cookies.put("ROUTEID", ROUTEID);
cookies.put("_idp_session", _idp_session);
get("https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO", cookies, response -> {
HashMap<String, String> object = new HashMap<>();
Pattern pattern = Pattern.compile("ss&#x3a;mem&#x3a;([0-9a-f]+)");
Matcher matcher = pattern.matcher(response.getParsed());
if (!matcher.find()) {
errorCallback.onError(new NetworkError(100142, -1, "Error on getting SAML response!"));
return;
}
object.put("RelayState", "ss:mem:"+matcher.group(1));
pattern = Pattern.compile("name=\"SAMLResponse\" value=\"([0-9a-zA-Z+]+=*)");
matcher = pattern.matcher(response.getParsed());
if (!matcher.find()) {
errorCallback.onError(new NetworkError(100141, -1, "Error on getting SAML response!"));
return;
}
object.put("SAMLResponse", matcher.group(1));
callback.onResponse(object);
}, error -> errorCallback.onError(new NetworkError(100140, error.networkResponse.statusCode, "Error on getting SAML response!")));
}
/*
POST https://kvv.imp.fu-berlin.de/Shibboleth.sso/SAML2/POST
<- RelayState 7ea01e29157b8bd906f7002176213b6db5e1f45ebb88716a9820d1a505f2c8bf
<- SAMLResponse PD94bWwgdmVyc2lvbj0...........wvc2FtbDJwOlJlc3BvbnNlPg==
<- JSESSIONID
-> _shibsession_64656661756c7468747470733a2f2f6b76762e696d702e66752d6265726c696e2e64652f73686962626f6c657468
_b1912c5a03d733a80bd3fee772bf68d4
*/
private void loginKVV(String RelayState, String SAMLResponse, String JSESSIONID, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
HashMap<String, String> cookies = new HashMap<>();
cookies.put("JSESSIONID", JSESSIONID);
HashMap<String, String> body = new HashMap<>();
body.put("RelayState", RelayState);
body.put("SAMLResponse", SAMLResponse);
post("https://kvv.imp.fu-berlin.de/Shibboleth.sso/SAML2/POST", cookies, body, response -> {
String cookies1 = response.getHeaders().get("Set-Cookie");
if (cookies1 ==null) {
errorCallback.onError(new NetworkError(100151, -1, "Error on starting KVV session!"));
return;
}
HashMap<String, String> object = new HashMap<>();
Pattern pattern = Pattern.compile("(_shibsession_[0-9a-f]+)=([^;]+);");
Matcher matcher = pattern.matcher(cookies1);
if (!matcher.find()) {
errorCallback.onError(new NetworkError(100152, -1, "Error on starting Ident session!"));
}
object.put("shibsessionKey", matcher.group(1));
object.put("shibsessionName", matcher.group(2));
callback.onResponse(object);
}, error -> errorCallback.onError(new NetworkError(100150, error.networkResponse.statusCode, "Error on starting Ident session!")));
}
/*
GET https://kvv.imp.fu-berlin.de/sakai-login-tool/container
<- JSESSIONID
<- _shibsession_64656661756c7468747470733a2f2f6b76762e696d702e66752d6265726c696e2e64652f73686962626f6c657468
_b1912c5a03d733a80bd3fee772bf68d4
*/
private void finishKVVlogin(LoginToken loginToken, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
get("https://kvv.imp.fu-berlin.de/sakai-login-tool/container", loginToken.getCookies(), response -> callback.onResponse(new HashMap<>()), error -> errorCallback.onError(new NetworkError(100160, error.networkResponse.statusCode, "Cannot finish login process!")));
}
private String getCookie(String cookies, String name) throws NoSuchFieldException {
Pattern pattern = Pattern.compile(name+"=([^;]+);");
Matcher matcher = pattern.matcher(cookies);
if (!matcher.find()) {
log.e("GETcookie failed", name);
log.e("GETcookie failed", cookies);
throw new NoSuchFieldException();
}
return matcher.group(1);
}
private HashMap<String, String> getCookie(String cookies, String[] names) throws NoSuchFieldException {
HashMap<String, String> result = new HashMap<>();
for (String name: names) {
result.put(name,this.getCookie(cookies, name));
}
return result;
}
}

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,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,6 +1,7 @@
package de.sebse.fuplanner.services.Canteen; package de.sebse.fuplanner.services.canteen;
import android.content.Context; import android.content.Context;
import android.os.Build;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
@@ -8,12 +9,11 @@ import org.json.JSONObject;
import java.io.IOException; import java.io.IOException;
import de.sebse.fuplanner.MainActivity; import de.sebse.fuplanner.services.canteen.types.Canteen;
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.Canteens;
import de.sebse.fuplanner.services.Canteen.types.Day; import de.sebse.fuplanner.services.canteen.types.Day;
import de.sebse.fuplanner.tools.AsyncQueue; 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.HTTPService;
import de.sebse.fuplanner.tools.network.NetworkCallback; import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkError; import de.sebse.fuplanner.tools.network.NetworkError;
@@ -23,15 +23,15 @@ public class CanteenBrowser extends HTTPService {
private Canteens canteens; private Canteens canteens;
private final AsyncQueue queue = new AsyncQueue(); private final AsyncQueue queue = new AsyncQueue();
private final Context context; private final Context context;
private MainAcitivityListener mListener; private CanteenListener mListener;
public CanteenBrowser(Context context) { public CanteenBrowser(Context context) {
super(context); super(context);
this.context = context; this.context = context;
if (context instanceof MainAcitivityListener) if (context instanceof CanteenListener)
mListener = (MainActivity) context; mListener = (CanteenListener) context;
else else
throw new RuntimeException(context.toString() + "must implement MainActivityListener"); throw new RuntimeException(context.toString() + " must implement CanteenListener");
try { try {
this.canteens = Canteens.load(context); this.canteens = Canteens.load(context);
} catch (IOException e) { } catch (IOException e) {
@@ -65,7 +65,12 @@ public class CanteenBrowser extends HTTPService {
} }
private void upgradeCanteens(final NetworkCallback<Canteens> callback, final NetworkErrorCallback errorCallback) { private void upgradeCanteens(final NetworkCallback<Canteens> callback, final NetworkErrorCallback errorCallback) {
get("https://openmensa.org/api/v2/canteens", null, response -> { // TSL 1.2 not supported (https://github.com/google/volley/issues/77)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
callback.onResponse(new Canteens());
return;
}
get("https://openmensa.org/api/v2/canteens?near[lat]=52.449743&near[lng]=13.282245&near[dist]=7", null, response -> {
String body = response.getParsed(); String body = response.getParsed();
if (body == null) { if (body == null) {
errorCallback.onError(new NetworkError(201101, 403, "No canteen list retrieved!")); errorCallback.onError(new NetworkError(201101, 403, "No canteen list retrieved!"));
@@ -126,6 +131,11 @@ public class CanteenBrowser extends HTTPService {
} }
private void upgradeCanteen(Canteen canteen, final NetworkCallback<Canteen> callback, final NetworkErrorCallback errorCallback) { private void upgradeCanteen(Canteen canteen, final NetworkCallback<Canteen> callback, final NetworkErrorCallback errorCallback) {
// TSL 1.2 not supported (https://github.com/google/volley/issues/77)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
callback.onResponse(canteen);
return;
}
get(String.format("https://openmensa.org/api/v2/canteens/%s/days", canteen.getId()), null, response -> { get(String.format("https://openmensa.org/api/v2/canteens/%s/days", canteen.getId()), null, response -> {
String body = response.getParsed(); String body = response.getParsed();
if (body == null) { if (body == null) {
@@ -173,6 +183,11 @@ public class CanteenBrowser extends HTTPService {
} }
private void upgradeDay(Day day, final NetworkCallback<Day> callback, final NetworkErrorCallback errorCallback) { private void upgradeDay(Day day, final NetworkCallback<Day> callback, final NetworkErrorCallback errorCallback) {
// TSL 1.2 not supported (https://github.com/google/volley/issues/77)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
callback.onResponse(day);
return;
}
get(String.format("https://openmensa.org/api/v2/canteens/%s/days/%s/meals/", day.getCanteenId(), Canteen.calendarToKey(day.getCalendar())), null, response -> { get(String.format("https://openmensa.org/api/v2/canteens/%s/days/%s/meals/", day.getCanteenId(), Canteen.calendarToKey(day.getCalendar())), null, response -> {
String body = response.getParsed(); String body = response.getParsed();
if (body == null) { if (body == null) {
@@ -192,9 +207,9 @@ public class CanteenBrowser extends HTTPService {
double priceEmply = 0; double priceEmply = 0;
double priceOther = 0; double priceOther = 0;
if (prices != null) { if (prices != null) {
priceStdnt = prices.getDouble("students"); priceOther = prices.optDouble("others", -1);
priceEmply = prices.getDouble("employees"); priceEmply = prices.optDouble("employees", priceOther);
priceOther = prices.getDouble("others"); priceStdnt = prices.optDouble("students", priceEmply);
} }
JSONArray noteArray = meal.getJSONArray("notes"); JSONArray noteArray = meal.getJSONArray("notes");
String[] notes = new String[noteArray.length()]; String[] notes = new String[noteArray.length()];
@@ -224,17 +239,14 @@ public class CanteenBrowser extends HTTPService {
private<T> NetworkCallback<T> saveOnCallback(NetworkCallback<T> callback, boolean forceRefresh){ private<T> NetworkCallback<T> saveOnCallback(NetworkCallback<T> callback, boolean forceRefresh){
return (success -> { return (success -> {
if (forceRefresh) if (forceRefresh)
mListener.refreshFailed(false); mListener.onCanteenRefreshCompleted(false);
callback.onResponse(success); callback.onResponse(success);
}); });
} }
private NetworkErrorCallback errorOnCallback(NetworkErrorCallback errorCallback){ private NetworkErrorCallback errorOnCallback(NetworkErrorCallback errorCallback){
return (error -> { return (error -> {
if (error.getHttpStatus() == 401 || error.getHttpStatus() == 403) mListener.onCanteenRefreshCompleted(true);
mListener.loginTokenInvalid(false);
else
mListener.refreshFailed(true);
errorCallback.onError(error); 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.io.Serializable;
import java.util.Calendar; 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() { public int size() {
return this.list.size(); return this.list.size();
} }
@@ -110,6 +121,7 @@ public class Canteen implements Serializable, Iterable<Day> {
return lng; return lng;
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
return id+": "+name+"\n"+list.toString()+"\n"; 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.content.Context;
import android.support.annotation.NonNull; import androidx.annotation.NonNull;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@@ -12,7 +12,7 @@ import java.io.Serializable;
import java.util.Iterator; import java.util.Iterator;
public class Canteens implements Serializable, Iterable<Canteen> { 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 static final String FILE_NAME = "CanteensSaving";
private SortedListCanteen list = new SortedListCanteen(); private SortedListCanteen list = new SortedListCanteen();
@@ -68,7 +68,7 @@ public class Canteens implements Serializable, Iterable<Canteen> {
@Override @Override
public void remove() { 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); context.deleteFile(FILE_NAME);
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
return this.list.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.io.Serializable;
import java.util.Calendar; import java.util.Calendar;
@@ -58,6 +58,7 @@ public class Day implements Serializable, Iterable<Meal> {
return canteenId; return canteenId;
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
return Canteen.calendarToKey(getCalendar())+"\n"+this.list+"\n"; 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.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import androidx.annotation.NonNull;
public class Meal implements Serializable { public class Meal implements Serializable {
public static final int LIGHT_NONE = 0; private static final int LIGHT_NONE = 0;
public static final int LIGHT_GREEN = 1; private static final int LIGHT_GREEN = 1;
public static final int LIGHT_YELLOW = 2; private static final int LIGHT_YELLOW = 2;
public static final int LIGHT_RED = 3; private static final int LIGHT_RED = 3;
public static final int VEGAN_NONE = 0; private static final int VEGAN_NONE = 0;
public static final int VEGAN_VEGETERIAN = 1; public static final int VEGAN_VEGETARIAN = 1;
public static final int VEGAN_VEGAN = 2; 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_BIO = 0b0001;
public static final int CERT_MSC = 0b0010; public static final int CERT_MSC = 0b0010;
@@ -42,7 +44,7 @@ public class Meal implements Serializable {
for (String note : notes) { for (String note : notes) {
switch (note.toLowerCase()) { switch (note.toLowerCase()) {
case "vegetarisch": case "vegetarisch":
vegan = VEGAN_VEGETERIAN; vegan = VEGAN_VEGETARIAN;
break; break;
case "vegan": case "vegan":
vegan = VEGAN_VEGAN; vegan = VEGAN_VEGAN;
@@ -112,6 +114,7 @@ public class Meal implements Serializable {
return certificates; return certificates;
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
return name + " (" + category + ")"; 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; 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; 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; 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,119 @@
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 AccountManager am = AccountManager.get(mContext);
if (am.getAccountsByType(accountType).length > 0) {
final Bundle bundle = new Bundle();
bundle.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION);
bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "Already an account added!");
return bundle;
}
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();
// Login when auth/re-auth
if (!TextUtils.isEmpty(authToken) && authToken.startsWith("Error: ")) {
// if password is not wrong -> Networking error
if (!authToken.contains("100343"))
throw new NetworkErrorException(authToken);
else authToken = null;
}
} 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,191 @@
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.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
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.TextView;
import com.google.android.material.textfield.TextInputEditText;
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 {
/**
* Keep track of the login task to ensure we can cancel it if requested.
*/
UserLoginTask mAuthTask = null;
// UI references.
private TextInputEditText mEmailView;
TextInputEditText mPasswordView;
TextView mErrorView;
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);
mAuthTokenType = mAuthTokenType != null ? mAuthTokenType : AccountGeneral.AUTHTOKEN_TYPE_KVV;
mIsAddingNewAccount = getIntent().getBooleanExtra(ARG_IS_ADDING_NEW_ACCOUNT, false);
setContentView(R.layout.activity_fu_authenticator);
mErrorView = findViewById(R.id.error_textview);
// Set up the login form.
mEmailView = findViewById(R.id.input_username);
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);
}
/**
* 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);
}
});
}
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,157 @@
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.sync.BBLogin;
import de.sebse.fuplanner.services.kvv.sync.FULogin;
import de.sebse.fuplanner.services.kvv.sync.KVVLogin;
import de.sebse.fuplanner.services.kvv.types.LoginTokenKVV;
import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.network.NetworkError;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
/**
* 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 mUsername;
private final String mPassword;
private final KVVLogin mKVVLogin;
private final BBLogin mBBLogin;
private String mTokenType;
private Logger log = new Logger(this);
@SuppressLint("StaticFieldLeak")
@Nullable
private FUAuthenticatorActivity mActivity;
UserLoginTask(String username, String password, @NotNull String tokenType, @NotNull Context context) {
mUsername = username;
mPassword = password;
mTokenType = tokenType;
FULogin mFULogin = new FULogin(context);
mKVVLogin = new KVVLogin(context, mFULogin);
mBBLogin = new BBLogin(context, mFULogin);
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<String> login = new AtomicReference<>();
NetworkErrorCallback errorFunc = error -> {
log.e(error);
login.set("Error: "+String.valueOf(error.getCode()));
latch.countDown();
};
switch (mTokenType) {
case AccountGeneral.AUTHTOKEN_TYPE_KVV:
mKVVLogin.doLogin(mUsername, mPassword, success -> {
mKVVLogin.testLoginToken(success, success1 -> {
login.set(success1.toJsonString());
latch.countDown();
}, errorFunc);
}, error -> {
if (error.getCode() == 100101) {
// KVV never used
LoginTokenKVV loginTokenKVV = new LoginTokenKVV(mUsername, "");
loginTokenKVV.setNotAvailable();
login.set(loginTokenKVV.toJsonString());
latch.countDown();
} else {
errorFunc.onError(error);
}
});
break;
case AccountGeneral.AUTHTOKEN_TYPE_BLACKBOARD:
mBBLogin.doLogin(mUsername, mPassword, success -> {
mBBLogin.testLoginToken(success, success1 -> {
login.set(success1.toJsonString());
latch.countDown();
}, error -> {
if (error.getCode() == 100270) {
// Blackboard never used
success.setNotAvailable();
login.set(success.toJsonString());
latch.countDown();
} else {
errorFunc.onError(error);
}
});
}, errorFunc);
break;
default:
errorFunc.onError(new NetworkError(104100, 400, "Invalid auth token type"));
return null;
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return login.get();
}
@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 && !success.startsWith("Error: ")) {
final Intent res = new Intent();
res.putExtra(AccountManager.KEY_ACCOUNT_NAME, mUsername);
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 if (success != null && (success.contains("100343"))) {
mActivity.mPasswordView.setError(mActivity.getString(R.string.error_incorrect_password));
mActivity.mPasswordView.requestFocus();
} else if (success != null) {
mActivity.mErrorView.setText(mActivity.getString(R.string.network_error_parameter, success.substring(7)));
} else {
mActivity.mErrorView.setText(R.string.network_error);
}
}
@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,5 @@
package de.sebse.fuplanner.services.kvv;
public class Constants {
public static final String KVV_SERVER_URL = "https://mycampus.imp.fu-berlin.de/";
}

View File

@@ -0,0 +1,120 @@
package de.sebse.fuplanner.services.kvv;
import android.accounts.AccountManager;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import com.android.volley.NetworkResponse;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import de.sebse.fuplanner.services.kvv.types.LoginTokenBB;
import de.sebse.fuplanner.services.kvv.types.LoginTokenKVV;
import de.sebse.fuplanner.tools.CustomAccountManager;
public class KVV extends Service {
private final HashMap<String, Object> addons = new HashMap<>();
private final HashMap<String, KVVListener> mListeners = new HashMap<>();
private final KVVListener mListener = new KVVListener() {
CustomAccountManager accountManager = null;
@Override
public CustomAccountManager getAccountManager() {
if (accountManager == null)
accountManager = new CustomAccountManager(AccountManager.get(KVV.this.getApplicationContext()), () -> null);
return accountManager;
}
@Override
public void onKVVNetworkResponse(NetworkResponse error) {
for (KVVListener listener : mListeners.values())
listener.onKVVNetworkResponse(error);
}
@Override
public void onLogin(LoginTokenKVV tokenKVV, LoginTokenBB tokenBB, boolean isOnlyRefresh) {
for (KVVListener listener : mListeners.values())
listener.onLogin(tokenKVV, tokenBB, isOnlyRefresh);
}
@Override
public void onLogout() {
for (KVVListener listener : mListeners.values())
listener.onLogout();
}
@Override
public void onModuleListChange() {
for (KVVListener listener : mListeners.values())
listener.onModuleListChange();
}
@Override
public void onModuleListPartiallyUpdated(de.sebse.fuplanner.services.kvv.types.Modules modules) {
for (KVVListener listener : mListeners.values())
listener.onModuleListPartiallyUpdated(modules);
}
};
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
@NotNull
public Login account() {
return (Login) addAndGet("account", () -> new Login(mListener, getApplicationContext()));
}
@NotNull
public Modules modules() {
return (Modules) addAndGet("module", () -> new Modules(account(), mListener, getApplicationContext()));
}
public void addListener(String key, KVVListener listener) {
mListeners.put(key, listener);
}
public void removeListener(String key) {
mListeners.remove(key);
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
public KVV getService() {
// Return this instance of LocalService so clients can call public methods
return KVV.this;
}
}
@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,22 @@
package de.sebse.fuplanner.services.kvv;
import com.android.volley.NetworkResponse;
import de.sebse.fuplanner.services.kvv.types.LoginTokenBB;
import de.sebse.fuplanner.services.kvv.types.LoginTokenKVV;
import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.tools.CustomAccountManager;
public interface KVVListener {
default void onLogin(LoginTokenKVV tokenKVV, LoginTokenBB tokenBB, boolean isOnlyRefresh) {}
default void onLogout() {}
default void onModuleListChange() {}
default void onModuleListPartiallyUpdated(Modules modules) {}
default void onKVVNetworkResponse(NetworkResponse error) {}
CustomAccountManager getAccountManager();
}

View File

@@ -0,0 +1,192 @@
package de.sebse.fuplanner.services.kvv;
import android.content.Context;
import android.util.Pair;
import org.jetbrains.annotations.NotNull;
import androidx.annotation.Nullable;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.fulogin.AccountGeneral;
import de.sebse.fuplanner.services.kvv.sync.BBLogin;
import de.sebse.fuplanner.services.kvv.sync.FULogin;
import de.sebse.fuplanner.services.kvv.sync.KVVLogin;
import de.sebse.fuplanner.services.kvv.types.LoginTokenBB;
import de.sebse.fuplanner.services.kvv.types.LoginTokenKVV;
import de.sebse.fuplanner.tools.CustomAccountManager;
import de.sebse.fuplanner.tools.NetworkCallbackCollector;
import de.sebse.fuplanner.tools.Preferences;
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 {
public static final int RESTORE_STATUS_SUCCESS = 1;
public static final int RESTORE_STATUS_ERROR = 2;
public static final int RESTORE_STATUS_INVALID_PASSWORD = 3;
public static final int LOGOUT_KVV = 2;
public static final int LOGOUT_BB = 1;
public static final int RELOGIN = 0;
private final KVVListener mListener;
@Nullable private LoginTokenKVV mTokenKVV;
@Nullable private LoginTokenBB mTokenBB;
private boolean mLoginPending = false;
private final NetworkCallbackCollector<Pair<LoginTokenKVV, LoginTokenBB>> mRefreshCallbacks = new NetworkCallbackCollector<>();
private final NetworkCallbackCollector<Integer> mRestoreCallbacks = new NetworkCallbackCollector<>();
Login(KVVListener listener, Context context) {
super(context);
this.mListener = listener;
}
public void restoreOnlineLogin(IntegerInterface callback) {
mRestoreCallbacks.add(new Pair<>(callback::run, null));
if (mLoginPending) {
return;
}
mLoginPending = true;
LoginTokenKVV.load(mListener.getAccountManager(), tokenKVV -> {
LoginTokenBB.load(mListener.getAccountManager(), tokenBB -> {
boolean result = setToken(tokenKVV, tokenBB);
mLoginPending = false;
mRestoreCallbacks.responseResponse(result ? RESTORE_STATUS_SUCCESS : RESTORE_STATUS_INVALID_PASSWORD);
}, e -> {
mLoginPending = false;
mRestoreCallbacks.responseResponse(RESTORE_STATUS_ERROR);
});
}, e -> {
mLoginPending = false;
mRestoreCallbacks.responseResponse(RESTORE_STATUS_ERROR);
});
}
public void isOfflineStoredAvailable(BooleanInterface callback) {
LoginTokenKVV.load(mListener.getAccountManager(), tokenKVV -> {
LoginTokenBB.load(mListener.getAccountManager(), tokenBB -> {
callback.run(tokenKVV != null && tokenBB != null);
}, e -> callback.run(false));
}, e -> callback.run(false));
}
public boolean logout(boolean delete) {
if (mLoginPending)
return false;
if (mTokenKVV == null || mTokenBB == null)
return true;
if (delete) {
Preferences.setString(getContext(), R.string.pref_shib_idp_session, "");
mTokenKVV.delete(mListener.getAccountManager());
mTokenBB.delete(mListener.getAccountManager());
}
mTokenKVV = null;
mTokenBB = null;
return handleCallbacks(false);
}
public boolean isLoginPending() {
return mLoginPending;
}
public boolean isLoggedIn() {
return mTokenKVV != null && mTokenBB != null;
}
public boolean isInOnlineMode() {
return isLoggedIn();
}
void testLoginToken(@NotNull NetworkCallback<Pair<LoginTokenKVV, LoginTokenBB>> callback, @NotNull NetworkErrorCallback errorCallback) {
if (mTokenKVV == null) {
errorCallback.onError(new NetworkError(100173, -1, "Not logged in!"));
return;
}
if (mTokenBB == null) {
errorCallback.onError(new NetworkError(100174, -1, "Not logged in!"));
return;
}
testLoginToken(mTokenKVV, mTokenBB, callback, errorCallback);
}
private void testLoginToken(@NotNull LoginTokenKVV tokenKVV, @NotNull LoginTokenBB tokenBB, @NotNull NetworkCallback<Pair<LoginTokenKVV, LoginTokenBB>> callback, @NotNull NetworkErrorCallback errorCallback) {
FULogin mFULogin = new FULogin(getContext());
new KVVLogin(getContext(), mFULogin).testLoginToken(tokenKVV, tokenKVV1 -> {
new BBLogin(getContext(), mFULogin).testLoginToken(tokenBB, tokenBB1 -> {
callback.onResponse(new Pair<>(tokenKVV1, tokenBB1));
}, errorCallback);
}, errorCallback);
}
@Nullable public LoginTokenKVV getLoginTokenKVV() {
return mTokenKVV;
}
@Nullable public LoginTokenBB getLoginTokenBB() {
return mTokenBB;
}
void refreshLogin(NetworkCallback<Pair<LoginTokenKVV, LoginTokenBB>> success, NetworkErrorCallback error, int flags) {
boolean isFirst = mRefreshCallbacks.isEmpty();
mRefreshCallbacks.add(success, error);
if (!isFirst)
return;
refreshLoginRunner(success, error, flags);
}
private void refreshLoginRunner(NetworkCallback<Pair<LoginTokenKVV, LoginTokenBB>> success, NetworkErrorCallback error, int flags) {
CustomAccountManager manager = mListener.getAccountManager();
if ((flags & LOGOUT_KVV) == LOGOUT_KVV) {
manager.doInvalidateToken(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_KVV, ignored -> {
refreshLoginRunner(success, error, flags & ~LOGOUT_KVV);
});
} else if ((flags & LOGOUT_BB) == LOGOUT_BB) {
manager.doInvalidateToken(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_BLACKBOARD, ignored -> {
refreshLoginRunner(success, error, flags & ~LOGOUT_BB);
});
} else if (flags == RELOGIN) {
restoreOnlineLogin(restoreResult -> {
if (restoreResult == RESTORE_STATUS_SUCCESS)
testLoginToken(mRefreshCallbacks::responseResponse, mRefreshCallbacks::responseError);
else if (restoreResult == RESTORE_STATUS_INVALID_PASSWORD) {
logout(true);
mRefreshCallbacks.responseError(new NetworkError(100180, 403, "Re-login failed (password)!"));
} else {
mRefreshCallbacks.responseError(new NetworkError(100181, 403, "Re-login failed (error)!"));
}
});
} else {
throw new IllegalArgumentException("Invalid flag in refreshLoginRunner: "+flags);
}
}
private boolean handleCallbacks(boolean isOnlyRefresh) {
if (mTokenKVV != null || mTokenBB != null) {
mListener.onLogin(mTokenKVV, mTokenBB, isOnlyRefresh);
return true;
} else {
mListener.onLogout();
return false;
}
}
private boolean setToken(@Nullable LoginTokenKVV tokenKVV, @Nullable LoginTokenBB tokenBB) {
if (tokenKVV == null || tokenBB == null)
return false;
boolean isOnlyRefresh = mTokenKVV != null;
mTokenKVV = tokenKVV;
mTokenBB = tokenBB;
return isOnlyRefresh || handleCallbacks(isOnlyRefresh);
}
public interface BooleanInterface {
void run(boolean bool);
}
public interface IntegerInterface {
void run(int integer);
}
}

View File

@@ -0,0 +1,95 @@
package de.sebse.fuplanner.services.kvv;
import android.content.Context;
import com.android.volley.NetworkResponse;
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 -> {
NetworkResponse networkResponse = error.networkResponse;
if ((networkResponse.statusCode == 400 || networkResponse.statusCode == 403) && networkResponse.headers.containsKey("X-Blackboard-product"))
mListener.onKVVNetworkResponse(null);
else
mListener.onKVVNetworkResponse(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 -> {
NetworkResponse networkResponse = error.networkResponse;
if ((networkResponse.statusCode == 400 || networkResponse.statusCode == 403) && networkResponse.headers.containsKey("X-Blackboard-product"))
mListener.onKVVNetworkResponse(null);
else
mListener.onKVVNetworkResponse(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,140 @@
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.UtilsDate;
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 upgradeKVV(final String ID, final NetworkCallback<ArrayList<Announcement>> callback, final NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginTokenKVV() == null || !mLogin.getLoginTokenKVV().isAvailable()) {
errorCallback.onError(new NetworkError(101204, 500, "Currently running in offline mode!"));
return;
}
super.get(String.format(Constants.KVV_SERVER_URL+"direct/announcement/site/%s.json?n=999999&d=999999999&_validateSession=", ID), mLogin.getLoginTokenKVV().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;
}
}
callback.onResponse(announcements);
}, error -> errorCallback.onError(new NetworkError(101203, error.networkResponse.statusCode, "Cannot get announcements!")));
}
@Override
protected void upgradeBB(String ID, NetworkCallback<ArrayList<Announcement>> callback, NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginTokenBB() == null || !mLogin.getLoginTokenBB().isAvailable()) {
errorCallback.onError(new NetworkError(101214, 500, "Currently running in offline mode!"));
return;
}
super.get(String.format("https://lms.fu-berlin.de/learn/api/v1/courses/%s/announcements?fields=id,title,body.rawText,startDateRestriction,creatorUserId", ID), mLogin.getLoginTokenBB().getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(101211, 403, "No announcements retrieved!"));
return;
}
ArrayList<Announcement> announcements = new ArrayList<>();
JSONArray sites;
try {
JSONObject json = new JSONObject(body);
sites = json.getJSONArray("results");
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101212, 403, "Cannot parse announcements!"));
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 text = site.getJSONObject("body").getString("rawText");
text = String.valueOf(fromHtml(text));
String createdBy = "";//site.getStringArray("createdByDisplayName");
long createdOn = UtilsDate.stringToMillis(site.getString("startDateRestriction"), "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
ArrayList<String> urls = new ArrayList<>();
announcements.add(new Announcement(id, title, text, createdBy, createdOn, urls));
} catch (JSONException e) {
log.e(new NetworkError(101215, 403, "Cannot parse announcements!"));
log.e("ID:", i, "JSON:", sites);
e.printStackTrace();
return;
}
}
callback.onResponse(announcements);
}, error -> {
if (error.networkResponse.statusCode == 400)
callback.onResponse(new ArrayList<>());
else
errorCallback.onError(new NetworkError(101213, error.networkResponse.statusCode, "Cannot get announcements!"));
});
}
}

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.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 upgradeKVV(final String ID, final NetworkCallback<AssignmentList> callback, final NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginTokenKVV() == null || !mLogin.getLoginTokenKVV().isAvailable()) {
errorCallback.onError(new NetworkError(101304, 500, "Currently running in offline mode!"));
return;
}
get(String.format(Constants.KVV_SERVER_URL+"direct/assignment/site/%s.json?_validateSession=", ID), mLogin.getLoginTokenKVV().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;
}
}
callback.onResponse(assignments);
}, error -> errorCallback.onError(new NetworkError(101303, error.networkResponse.statusCode, "Cannot get assignments!")));
}
@Override
protected void upgradeBB(String ID, NetworkCallback<AssignmentList> callback, NetworkErrorCallback errorCallback) {
callback.onResponse(new AssignmentList());
}
}

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,188 @@
package de.sebse.fuplanner.services.kvv;
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.regex.MatchResult;
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.services.kvv.types.Semester;
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 ModulesEvents extends PartModules<EventList> {
private ModulesEventsNumber mEventNumbers;
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 upgradeKVV(final String ID, final NetworkCallback<EventList> callback, final NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginTokenKVV() == null || !mLogin.getLoginTokenKVV().isAvailable()) {
errorCallback.onError(new NetworkError(101404, 500, "Currently running in offline mode!"));
return;
}
get(String.format(Constants.KVV_SERVER_URL+"direct/calendar/site/%s.json?detailed=true&_validateSession=", ID), mLogin.getLoginTokenKVV().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;
}
}
callback.onResponse(events);
}, error -> errorCallback.onError(new NetworkError(101403, error.networkResponse.statusCode, "Cannot get events!")));
}
@Override
protected void upgradeBB(String ID, NetworkCallback<EventList> callback, NetworkErrorCallback errorCallback) {
// TSL 1.2 not supported (https://github.com/google/volley/issues/77)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
callback.onResponse(new EventList());
return;
}
if (!mLogin.isInOnlineMode() || mLogin.getLoginTokenBB() == null || !mLogin.getLoginTokenBB().isAvailable()) {
errorCallback.onError(new NetworkError(101414, 500, "Currently running in offline mode!"));
return;
}
this.mList.recv(modules -> {
Modules.Module module = modules.get(ID);
if (module == null) {
errorCallback.onError(new NetworkError(101416, 400, "Cannot get events!"));
return;
}
Semester latestSemester = modules.getLatestSemester();
if (module.semester != null && !module.semester.equals(latestSemester)) {
if (module.events != null) {
callback.onResponse(module.events);
} else {
callback.onResponse(new EventList());
}
return;
}
if (module.lvNumber.size() == 0) {
// Events not available
callback.onResponse(new EventList());
return;
}
eventNumbers().getVVNumber(module.lvNumber.iterator().next(), vvNumber -> {
if (vvNumber.equals("")) {
// Events not available
callback.onResponse(new EventList());
//errorCallback.onError(new NetworkError(101410, 403, "Cannot get events!"));
return;
}
// 462854, 465661, 462126, 463782, 437050, 433843, 471614, 464205
//String[] tests = {"462854", "465661", "462126", "463782", "437050", "433843", "471614", "464205"};
//String[] tests = {"461459", "424564", "459494", "429737", "463765", "476477", "464082", "459577", "459743", "464318", "449358", "454327", "461784", "468081", "485919"};
//vvNumber = tests[new Random().nextInt(tests.length)];
//log.d("LAAAAAAST", vvNumber);
//vvNumber = "462126";
super.get(String.format("https://www.fu-berlin.de/vv/de/lv/%s", vvNumber), null, response1 -> {
String body = response1.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(101411, 400, "Cannot get events!"));
return;
}
EventList events = new EventList();
for (MatchResult match : Regex.allMatches("<span id=\"link_to_details_[^~]*?</span>", body)) {
String entry = match.group(0);
String date;
String title = "";
try {
date = Regex.regex("[A-Z][a-z], [0-9]{2}\\.[0-9]{2}\\.[0-9]{4} [0-9]{2}:[0-9]{2} - [0-9]{2}:[0-9]{2}", entry, 0);
} catch (NoSuchFieldException e) {
log.e(new NetworkError(101412, 400, "Cannot get events!"));
e.printStackTrace();
continue;
}
try {
title = Regex.regex("<div class=\"course_title\">([^<]*?)</div>", entry, 1);
} catch (NoSuchFieldException ignored) {}
if (TextUtils.isEmpty(title))
title = module.title;
ArrayList<String> docents = new ArrayList<>();
ArrayList<String> locations = new ArrayList<>();
for (MatchResult match1 : Regex.allMatches("<div class=\"appointment_details_column\">[^~]*?</div>", entry)) {
if (match1.group(0).contains("Dozenten:")) {
for (MatchResult match2 : Regex.allMatches(">([^>]*?)<small class=\"phone_portal\">", match1.group(0))) {
docents.add(match2.group(0).trim());
}
} else if (match1.group(0).contains("Räume:")) {
for (MatchResult match2 : Regex.allMatches("</b>([^~]*?)</p>", match1.group(0))) {
locations.add(match2.group(1).trim());
}
}
}
long start = UtilsDate.stringToMillis(date.substring(4, 20), "dd.MM.yyyy HH:mm");
long duration = (Integer.parseInt(date.substring(23, 25))-Integer.parseInt(date.substring(15, 17)))*60
+ Integer.parseInt(date.substring(26, 28))-Integer.parseInt(date.substring(18, 20));
duration = duration*60*1000-1;
String locationString = TextUtils.join(", ", locations);
events.add(new Event("", "Class section - Lecture", title, duration, start, ID, locationString));
}
callback.onResponse(events);
}, error -> errorCallback.onError(new NetworkError(101415, error.networkResponse.statusCode, "Cannot get events!")));
}, errorCallback);
}, errorCallback);
}
private ModulesEventsNumber eventNumbers() {
if (mEventNumbers == null)
mEventNumbers = new ModulesEventsNumber(getContext());
return mEventNumbers;
}
}

View File

@@ -0,0 +1,72 @@
package de.sebse.fuplanner.services.kvv;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import de.sebse.fuplanner.services.kvv.types.CacheBBEventNumber;
import de.sebse.fuplanner.services.kvv.types.CacheLecturer;
import de.sebse.fuplanner.services.kvv.types.Lecturer;
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;
class ModulesEventsNumber extends HTTPService {
private final CacheBBEventNumber mStorage;
ModulesEventsNumber(Context context) {
super(context);
CacheBBEventNumber storage = null;
try {
storage = CacheBBEventNumber.load(context);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if (storage == null) {
mStorage = new CacheBBEventNumber();
} else {
mStorage = storage;
}
}
void getVVNumber(String lvNumber, NetworkCallback<String> callback, NetworkErrorCallback errorCallback) {
String vvNumber = mStorage.getVVNumber(lvNumber);
if (vvNumber != null) {
callback.onResponse(vvNumber);
return;
}
super.head(String.format("https://www.fu-berlin.de/vv/de/search?utf8=✓&query=%s", lvNumber), null, response -> {
String location = response.getHeaders().get("Location");
if (location == null) {
// Events not available
callback.onResponse("");
//errorCallback.onError(new NetworkError(101410, 403, "Cannot get events!"));
return;
}
try {
String group = Regex.regex("lv/([0-9]+)\\?", location);
mStorage.setVVNumber(lvNumber, group);
try {
mStorage.save(getContext());
} catch (IOException e) {
e.printStackTrace();
}
callback.onResponse(group);
} catch (NoSuchFieldException e) {
errorCallback.onError(new NetworkError(102201, 400, "Cannot get events!"));
e.printStackTrace();
}
}, error -> errorCallback.onError(new NetworkError(102202, error.networkResponse.statusCode, "Error retrieving lecturer!")));
}
}

View File

@@ -0,0 +1,155 @@
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 upgradeKVV(final String ID, final NetworkCallback<ArrayList<Grade>> callback, final NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginTokenKVV() == null || !mLogin.getLoginTokenKVV().isAvailable()) {
errorCallback.onError(new NetworkError(101504, 500, "Currently running in offline mode!"));
return;
}
super.get(String.format(Constants.KVV_SERVER_URL+"direct/gradebook/site/%s.json", ID), mLogin.getLoginTokenKVV().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 -> {
if (error.networkResponse.statusCode == 400)
callback.onResponse(new ArrayList<>());
else
errorCallback.onError(new NetworkError(101503, error.networkResponse.statusCode, "Cannot get gradebook!"));
});
}
@Override
protected void upgradeBB(String ID, NetworkCallback<ArrayList<Grade>> callback, NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginTokenBB() == null || !mLogin.getLoginTokenBB().isAvailable()) {
errorCallback.onError(new NetworkError(101510, 500, "Currently running in offline mode!"));
return;
}
super.get(String.format("https://lms.fu-berlin.de/learn/api/public/v2/courses/%s/gradebook/columns", ID), mLogin.getLoginTokenBB().getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(101511, 400, "No gradebook columns retrieved!"));
return;
}
JSONArray gradeColumns;
try {
JSONObject json = new JSONObject(body);
gradeColumns = json.getJSONArray("results");
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101512, 400, "Cannot parse gradebook columns!"));
return;
}
get(String.format("https://lms.fu-berlin.de/learn/api/public/v2/courses/%s/gradebook/users/%s", ID, mLogin.getLoginTokenBB().getId()), mLogin.getLoginTokenBB().getCookies(), response1 -> {
String body1 = response1.getParsed();
if (body1 == null) {
errorCallback.onError(new NetworkError(101513, 400, "No gradebook entries retrieved!"));
return;
}
JSONArray grades;
try {
JSONObject json = new JSONObject(body1);
grades = json.getJSONArray("results");
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101514, 400, "Cannot parse gradebook entries!"));
return;
}
ArrayList<Grade> result = new ArrayList<>();
for (int i = 0; i < grades.length(); i++) {
for (int j = 0; j < gradeColumns.length(); j++) {
try {
JSONObject grade = grades.getJSONObject(i);
JSONObject column = gradeColumns.getJSONObject(j);
String grade_id = grade.getString("columnId");
String column_id = column.getString("id");
if (!grade_id.equals(column_id))
continue;
String name = column.getString("name");
JSONObject displayGrade = grade.optJSONObject("displayGrade");
double points = displayGrade != null ? displayGrade.optDouble("score", 0) : 0;
JSONObject score = column.optJSONObject("score");
double maxPoints = score != null ? score.optDouble("possible", 0) : 0;
result.add(new Grade(name, points, maxPoints));
} catch (JSONException e) {
log.e(new NetworkError(101515, 400, "Cannot parse grades!"));
log.e("ID:", i, "JSON-grades:", grades);
log.e("ID:", j, "JSON-gradeColumns:", gradeColumns);
e.printStackTrace();
return;
}
}
}
callback.onResponse(result);
}, error -> errorCallback.onError(new NetworkError(101516, error.networkResponse.statusCode, "Cannot get gradebook columns!")));
}, error -> {
if (error.networkResponse.statusCode == 403)
callback.onResponse(new ArrayList<>());
else
errorCallback.onError(new NetworkError(101517, error.networkResponse.statusCode, "Cannot get gradebook entries!"));
});
}
}

View File

@@ -0,0 +1,457 @@
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 java.util.regex.Matcher;
import androidx.arch.core.util.Function;
import de.sebse.fuplanner.services.kvv.types.CacheBBCourse;
import de.sebse.fuplanner.services.kvv.types.CacheKVVCourse;
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 ModulesListLecturer mLecturer;
private CacheBBCourse mBBCache;
private final NewAsyncQueue mQueue = new NewAsyncQueue();
private CacheKVVCourse mKVVCache;
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 reloadIfOutdated() {
try {
if (mModules != null && mModules.isNewerVersionInStorage(getContext())) {
restore();
}
} catch (IOException e) {
e.printStackTrace();
}
}
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.getLoginTokenKVV() != null && mLogin.getLoginTokenKVV().isOtherUser(mModules.getUsername()) &&
mLogin.getLoginTokenBB() != null && mLogin.getLoginTokenBB().isOtherUser(mModules.getUsername())
)
delete();
if (retries < 0) {
log.t("Too many retires", retries);
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();
}
if (this.mModules == null) {
recv(success -> {}, log::e);
}
}
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.getLoginTokenKVV() != null && mLogin.getLoginTokenKVV().isOtherUser(mModules.getUsername()) &&
mLogin.getLoginTokenBB() != null && mLogin.getLoginTokenBB().isOtherUser(mModules.getUsername())
)
delete();
mQueue.add(() -> {
if (mLogin.isLoginPending()) {
mLogin.restoreOnlineLogin(resCode -> {
mQueue.next();
});
} else {
mQueue.next();
}
});
mQueue.add(() -> {
if (this.mModules != null && !forceRefresh) {
callback.onResponse(this.mModules);
mQueue.next();
return;
}
Function<Integer, NetworkErrorCallback> errorFunc = ((Integer errorCode) -> (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();
}, errorCode);
return;
}
errorCallback.onError(error);
mQueue.next();
}));
this.upgradeKVV(successKVV -> {
this.upgradeBB(successKVV, success -> {
if (this.mModules == null)
this.mModules = success;
else if (this.mModules.updateList(success)) {
mListener.onModuleListChange();
store();
}
callback.onResponse(this.mModules);
mQueue.next();
}, errorFunc.apply(Login.LOGOUT_BB));
}, errorFunc.apply(Login.LOGOUT_KVV));
});
}
private void upgradeKVV(final NetworkCallback<Modules> callback, final NetworkErrorCallback errorCallback) {
NetworkCallback<Modules> successCallback = (modules -> {
try {
cacheKVVCourse().save(getContext());
} catch (IOException e) {
e.printStackTrace();
}
callback.onResponse(modules);
});
if (!mLogin.isInOnlineMode() || mLogin.getLoginTokenKVV() == null) {
errorCallback.onError(new NetworkError(101110, 500, "Currently running in offline mode!"));
return;
}
Modules modules = new Modules(mLogin.getLoginTokenKVV().getUsername());
if (!mLogin.getLoginTokenKVV().isAvailable()) {
callback.onResponse(modules);
return;
}
get(Constants.KVV_SERVER_URL+"direct/membership.json?_validateSession=", mLogin.getLoginTokenKVV().getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(101111, 403, "No membership list retrieved!"));
return;
}
JSONArray memberships;
try {
JSONObject json = new JSONObject(body);
memberships = json.getJSONArray("membership_collection");
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101112, 403, "Cannot parse membership list!"));
return;
}
final int[] latch = {memberships.length()};
if (latch[0] == 0) {
successCallback.onResponse(modules);
return;
}
for (int i = 0; i < memberships.length(); i++) {
try {
JSONObject membership = memberships.getJSONObject(i);
String locationReference = membership.getString("locationReference");
String courseId = Regex.regex("/site/([^/]*)", locationReference);
Modules.Module kvvCourse = cacheKVVCourse().getKVVCourse(courseId);
if (kvvCourse != null) {
kvvCourse = kvvCourse.clone();
modules.addModule(kvvCourse);
if (--latch[0] == 0) successCallback.onResponse(modules);
continue;
}
get(String.format(Constants.KVV_SERVER_URL+"direct/site/%s.json?_validateSession=", courseId), mLogin.getLoginTokenKVV().getCookies(), response1 -> {
String body1 = response1.getParsed();
if (body1 == null) {
errorCallback.onError(new NetworkError(101113, 403, "No site retrieved!"));
return;
}
try {
JSONObject site = new JSONObject(body1);
String semester_string = site.getJSONObject("props").optString("term_eid", null);
Semester semester;
if (semester_string == null)
semester = null;
else
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", "Projekt");
String description = site.optString("description", "");
description = String.valueOf(PartModules.fromHtml(description));
String id = site.getString("id");
Modules.Module module = modules.addModule(semester, lvNumbers, title, lecturers, type, description, id, Modules.TYPE_KVV);
cacheKVVCourse().setKVVCourse(courseId, module.clone());
if (--latch[0] == 0) successCallback.onResponse(modules);
} catch (JSONException e) {
log.e(new NetworkError(101114, 403, "Cannot parse site!"));
log.e("JSON:", body1);
e.printStackTrace();
} catch (NoSuchFieldException e) {
log.e(new NetworkError(101115, 403, "Cannot parse site!"));
e.printStackTrace();
}
}, error -> errorCallback.onError(new NetworkError(101116, error.networkResponse.statusCode, "Cannot get parse!")));
} catch (JSONException e) {
log.e("ID:", i, "JSON:", memberships);
e.printStackTrace();
errorCallback.onError(new NetworkError(101117, 403, "Cannot parse membership list!"));
return;
} catch (NoSuchFieldException e) {
log.e("ID:", i, "JSON:", memberships);
e.printStackTrace();
errorCallback.onError(new NetworkError(101118, 403, "Cannot parse membership list!"));
return;
}
}
}, error -> errorCallback.onError(new NetworkError(101119, error.networkResponse.statusCode, "Cannot get membership list!")));
}
private void upgradeBB(final Modules modulesKVV, final NetworkCallback<Modules> callback, final NetworkErrorCallback errorCallback) {
NetworkCallback<Modules> successCallback = (modules -> {
try {
cacheBBCourse().save(getContext());
} catch (IOException e) {
e.printStackTrace();
}
callback.onResponse(modules);
});
NetworkCallback<Modules> fastCallback = (modules -> {
mListener.onModuleListPartiallyUpdated(modules);
try {
cacheBBCourse().save(getContext());
} catch (IOException e) {
e.printStackTrace();
}
mListener.onModuleListPartiallyUpdated(modules);
});
if (!mLogin.isInOnlineMode() || mLogin.getLoginTokenBB() == null) {
errorCallback.onError(new NetworkError(101120, 500, "Currently running in offline mode!"));
return;
}
if (!mLogin.getLoginTokenBB().isAvailable()) {
callback.onResponse(modulesKVV);
return;
}
get(String.format("https://lms.fu-berlin.de/learn/api/public/v1/users/%s/courses?fields=courseId,dataSourceId", mLogin.getLoginTokenBB().getId()), mLogin.getLoginTokenBB().getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(101121, 403, "No module list retrieved!"));
return;
}
final JSONArray[] sites = new JSONArray[1];
try {
JSONObject json = new JSONObject(body);
sites[0] = json.getJSONArray("results");
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101122, 403, "Cannot parse module list!"));
return;
}
final int[] latch = {sites[0].length()};
// fetching lecturers takes a lot of time, send success twice: first without lecturers
final int[] latchNoLecturers = {sites[0].length()};
if (sites[0].length() == 0) {
successCallback.onResponse(modulesKVV);
return;
}
for (int i = 0; i < sites[0].length(); i++) {
try {
JSONObject site = sites[0].getJSONObject(i);
String courseId = site.getString("courseId");
if (cacheBBCourse().hasKVVCourseID(courseId)) {
if (--latchNoLecturers[0] == 0) fastCallback.onResponse(modulesKVV);
if (--latch[0] == 0) successCallback.onResponse(modulesKVV);
continue;
}
Modules.Module bbCourse = cacheBBCourse().getBBCourse(courseId);
if (bbCourse != null) {
bbCourse = bbCourse.clone();
modulesKVV.addModule(bbCourse);
if (--latchNoLecturers[0] == 0) fastCallback.onResponse(modulesKVV);
if (--latch[0] == 0) successCallback.onResponse(modulesKVV);
continue;
}
get(String.format("https://lms.fu-berlin.de/learn/api/v1/courses/%s?fields=name,courseId,description", courseId), mLogin.getLoginTokenBB().getCookies(), response1 -> {
String body1 = response1.getParsed();
if (body1 == null) {
errorCallback.onError(new NetworkError(101124, 403, "No course entry retrieved!"));
return;
}
try {
JSONObject json = new JSONObject(body1);
String name = json.getString("name");
String description = json.optString("description", null);
String type, lvNumber, semYear, semType;
Semester semester = null;
HashSet<String> lvNumberSet = new HashSet<>();
boolean found = false;
try {
Matcher match = Regex.match("[A-Za-z0-9]*_([A-Za-z0-9]*)_([A-Za-z0-9]*)_([0-9]{2,})([WS]+)", json.getString("courseId"));
type = match.group(1);
lvNumber = match.group(2);
semYear = match.group(3);
semType = match.group(4);
semester = new Semester(semType.equals("W") ? Semester.SEM_WS : Semester.SEM_SS, Integer.valueOf(semYear) % 100);
lvNumberSet.add(lvNumber);
for (Modules.Module module: modulesKVV) {
if (module.lvNumber.contains(lvNumber)) {
found = true;
break;
}
}
} catch (NoSuchFieldException | NumberFormatException e) {
log.e(e);
type = "Projekt";
}
if (!found) {
Modules.Module module = modulesKVV.addModule(semester, lvNumberSet, name, new LinkedHashSet<>(), type, description, courseId, Modules.TYPE_BB);
lecturer().getBBLecturers(courseId, success -> {
module.setLecturers(success);
cacheBBCourse().setBBCourse(courseId, module.clone());
if (--latch[0] == 0) successCallback.onResponse(modulesKVV);
}, error -> {
log.e(error);
if (--latch[0] == 0) successCallback.onResponse(modulesKVV);
});
if (--latchNoLecturers[0] == 0) fastCallback.onResponse(modulesKVV);
} else {
cacheBBCourse().addKVVCourseID(courseId);
if (--latchNoLecturers[0] == 0) fastCallback.onResponse(modulesKVV);
if (--latch[0] == 0) successCallback.onResponse(modulesKVV);
}
} catch (JSONException e) {
e.printStackTrace();
log.e(new NetworkError(101125, 403, "Cannot parse course entry!"));
if (--latchNoLecturers[0] == 0) fastCallback.onResponse(modulesKVV);
if (--latch[0] == 0) successCallback.onResponse(modulesKVV);
}
}, error -> errorCallback.onError(new NetworkError(101126, error.networkResponse.statusCode, "Cannot get module list!")));
} catch (JSONException e) {
log.e(new NetworkError(101123, 403, "Cannot parse course entry"));
log.e("ID:", i, "JSON:", sites[0]);
e.printStackTrace();
}
}
}, error -> errorCallback.onError(new NetworkError(101125, error.networkResponse.statusCode, "Cannot get module list!")));
}
private ModulesListLecturer lecturer() {
if (mLecturer == null)
mLecturer = new ModulesListLecturer(getContext(), mLogin);
return mLecturer;
}
private CacheBBCourse cacheBBCourse() {
if (mBBCache == null) {
try {
mBBCache = CacheBBCourse.load(getContext());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
if (mBBCache == null)
mBBCache = new CacheBBCourse();
return mBBCache;
}
private CacheKVVCourse cacheKVVCourse() {
if (mKVVCache == null) {
try {
mKVVCache = CacheKVVCourse.load(getContext());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
if (mKVVCache == null)
mKVVCache = new CacheKVVCourse();
return mKVVCache;
}
}

View File

@@ -0,0 +1,157 @@
package de.sebse.fuplanner.services.kvv;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import de.sebse.fuplanner.services.kvv.types.CacheLecturer;
import de.sebse.fuplanner.services.kvv.types.Lecturer;
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;
class ModulesListLecturer extends HTTPService {
private final Login mLogin;
private final CacheLecturer mStorage;
ModulesListLecturer(Context context, Login login) {
super(context);
this.mLogin = login;
CacheLecturer storage = null;
try {
storage = CacheLecturer.load(context);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if (storage == null) {
mStorage = new CacheLecturer();
} else {
mStorage = storage;
}
}
void getBBLecturers(String moduleID, NetworkCallback<LinkedHashSet<Lecturer>> callback, NetworkErrorCallback errorCallback) {
ArrayList<String> lecturerString = mStorage.getLecturersPerCourse(moduleID);
if (lecturerString != null) {
final int[] latch = {lecturerString.size()};
LinkedHashSet<Lecturer> lecturers = new LinkedHashSet<>();
for (String userId : lecturerString) {
getBBLecturer(userId, success -> {
lecturers.add(success);
if (--latch[0] == 0) callback.onResponse(lecturers);
}, error -> {
log.e(error);
if (--latch[0] == 0) callback.onResponse(lecturers);
});
}
if (lecturerString.size() == 0)
callback.onResponse(lecturers);
return;
}
if (!mLogin.isInOnlineMode() || mLogin.getLoginTokenBB() == null || !mLogin.getLoginTokenBB().isAvailable()) {
errorCallback.onError(new NetworkError(102100, 500, "Currently running in offline mode!"));
return;
}
get(String.format("https://lms.fu-berlin.de/learn/api/public/v1/courses/%s/users?fields=userId,courseRoleId", moduleID), mLogin.getLoginTokenBB().getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(102101, 403, "No lecturers retrieved!"));
return;
}
JSONArray sites;
try {
JSONObject json = new JSONObject(body);
sites = json.getJSONArray("results");
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(102102, 403, "Cannot parse lecturers!"));
return;
}
LinkedHashSet<Lecturer> lecturers = new LinkedHashSet<>();
final int[] latch = {sites.length()};
ArrayList<String> lecturerString2 = new ArrayList<>();
NetworkCallback<LinkedHashSet<Lecturer>> successCallback = l -> {
mStorage.setLecturersPerCourse(moduleID, lecturerString2);
try {
mStorage.save(getContext());
} catch (IOException e) {
e.printStackTrace();
}
callback.onResponse(lecturers);
};
for (int i = 0; i < sites.length(); i++) {
try {
JSONObject lecturerJson = sites.getJSONObject(i);
String userId = lecturerJson.getString("userId");
String role = lecturerJson.getString("courseRoleId");
if (!"Student".equals(role)) {
lecturerString2.add(userId);
getBBLecturer(userId, success -> {
lecturers.add(success);
if (--latch[0] == 0) successCallback.onResponse(lecturers);
}, error -> {
log.e(error);
if (--latch[0] == 0) successCallback.onResponse(lecturers);
});
} else {
if (--latch[0] == 0) successCallback.onResponse(lecturers);
}
} catch (JSONException e) {
log.e(new NetworkError(102103, 403, "Cannot parse lecturers!"));
log.e("ID:", i, "JSON:", sites);
e.printStackTrace();
if (--latch[0] == 0) successCallback.onResponse(lecturers);
}
}
}, error -> errorCallback.onError(new NetworkError(102104, error.networkResponse.statusCode, "Error retrieving lecturers!")));
}
private void getBBLecturer(String lecturerID, NetworkCallback<Lecturer> callback, NetworkErrorCallback errorCallback) {
Lecturer lecturerStore = mStorage.getLecturer(lecturerID);
if (lecturerStore != null) {
callback.onResponse(lecturerStore);
return;
}
if (!mLogin.isInOnlineMode() || mLogin.getLoginTokenBB() == null || !mLogin.getLoginTokenBB().isAvailable()) {
errorCallback.onError(new NetworkError(102110, 500, "Currently running in offline mode!"));
return;
}
get(String.format("https://lms.fu-berlin.de/learn/api/public/v1/users/%s?fields=userName,name", lecturerID), mLogin.getLoginTokenBB().getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(102111, 403, "No lecturer retrieved!"));
return;
}
try {
JSONObject json = new JSONObject(body);
String userName = json.getString("userName");
String givenName = json.getJSONObject("name").getString("given");
String familyName = json.getJSONObject("name").getString("family");
Lecturer lecturer = new Lecturer(givenName, familyName, userName + "@zedat.fu-berlin.de", true);
mStorage.setLecturer(lecturerID, lecturer);
try {
mStorage.save(getContext());
} catch (IOException e) {
e.printStackTrace();
}
callback.onResponse(lecturer);
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(102112, 403, "Cannot parse lecturer!"));
}
}, error -> errorCallback.onError(new NetworkError(102113, error.networkResponse.statusCode, "Error retrieving lecturer!")));
}
}

View File

@@ -0,0 +1,391 @@
package de.sebse.fuplanner.services.kvv;
import android.content.Context;
import android.os.Build;
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.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
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 upgradeKVV(final String ID, final NetworkCallback<ArrayList<Resource>> callback, final NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginTokenKVV() == null || !mLogin.getLoginTokenKVV().isAvailable()) {
errorCallback.onError(new NetworkError(101604, 500, "Currently running in offline mode!"));
return;
}
get(String.format(Constants.KVV_SERVER_URL+"direct/content/site/%s.json?_validateSession=", ID), mLogin.getLoginTokenKVV().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) {
try {
String utf8Name;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
utf8Name = StandardCharsets.UTF_8.name();
else utf8Name = "UTF-8";
if (URLDecoder.decode(res2.getUrl(), utf8Name).endsWith(res.getContainer()) && res2 instanceof Resource.Folder) {
// Append File/Folder to list
((Resource.Folder) res2).add(res);
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
}
}
callback.onResponse(root);
}, error -> errorCallback.onError(new NetworkError(101603, error.networkResponse.statusCode, "Cannot get resources!")));
}
@Override
protected void upgradeBB(String ID, NetworkCallback<ArrayList<Resource>> callback, NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginTokenBB() == null || !mLogin.getLoginTokenBB().isAvailable()) {
errorCallback.onError(new NetworkError(101614, 500, "Currently running in offline mode!"));
return;
}
getRescourceFolder(String.format("https://lms.fu-berlin.de/learn/api/public/v1/courses/%s/contents/", ID), ID, false, callback, errorCallback);
//callback.onResponse(new ArrayList<>());
}
private void getRescourceFolder(String url, String ID, boolean subRequest, NetworkCallback<ArrayList<Resource>> callback, NetworkErrorCallback errorCallback) {
if (mLogin.getLoginTokenBB() == null) {
errorCallback.onError(new NetworkError(101610, 500, "Cannot get resources!"));
return;
}
get(url, mLogin.getLoginTokenBB().getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(101611, 403, "No resources retrieved!"));
return;
}
ArrayList<Resource> resources = new ArrayList<>();
JSONArray sites;
try {
JSONObject json = new JSONObject(body);
sites = json.getJSONArray("results");
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101612, 400, "Cannot parse resources!"));
return;
}
final int[] latch = {sites.length()};
for (int i = 0; i < sites.length(); i++) {
try {
JSONObject resource = sites.getJSONObject(i);
String resid = resource.getString("id");
String title = resource.getString("title");
String created = resource.getString("created");
String content = resource.getJSONObject("contentHandler").getString("id");
long createdDate = UtilsDate.stringToMillis(created, "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
if (content.equals("resource/x-bb-folder")) {
String url2 = String.format("https://lms.fu-berlin.de/learn/api/public/v1/courses/%s/contents/%s", ID, resid);
Resource.Folder folder = new Resource.Folder("", title, createdDate, url2, true, "");
resources.add(folder);
getRescourceFolder(String.format("https://lms.fu-berlin.de/learn/api/public/v1/courses/%s/contents/%s/children", ID, resid), ID, true, response1 -> {
for (Resource resource1 : response1) {
folder.add(resource1);
}
if (--latch[0] == 0) callback.onResponse(resources);
}, error -> {
if (error.getHttpStatus() == 403) {
if (--latch[0] == 0) callback.onResponse(resources);
} else {
errorCallback.onError(error);
}
});
} else {
String bodyText = resource.optString("body", "");
bodyText = String.valueOf(PartModules.fromHtml(bodyText));
String url2 = String.format("https://lms.fu-berlin.de/learn/api/public/v1/courses/%s/contents/%s", ID, resid);
Resource.Document document = new Resource.Document("", title, createdDate, url2, true, "", bodyText);
resources.add(document);
get(String.format("https://lms.fu-berlin.de/learn/api/public/v1/courses/%s/contents/%s/attachments", ID, resid), mLogin.getLoginTokenBB().getCookies(), response1 -> {
String body1 = response1.getParsed();
if (body1 == null) {
errorCallback.onError(new NetworkError(101617, 403, "No resource attachments retrieved!"));
return;
}
JSONArray attachments;
try {
JSONObject json = new JSONObject(body1);
attachments = json.getJSONArray("results");
} catch (JSONException e) {
e.printStackTrace();
errorCallback.onError(new NetworkError(101618, 400, "Cannot parse resource attachments!"));
return;
}
for (int j = 0; j < attachments.length(); j++) {
try {
JSONObject attachment = attachments.getJSONObject(j);
String attachid = attachment.getString("id");
String attachname = attachment.getString("fileName");
document.addUrl(String.format("https://lms.fu-berlin.de/learn/api/public/v1/courses/%s/contents/%s/attachments/%s/download", ID, resid, attachid), attachname);
} catch (JSONException e) {
log.e(new NetworkError(101619, 400, "Cannot parse resource attachments!"));
e.printStackTrace();
log.e("ID:", j, "JSON:", attachments);
}
}
if (--latch[0] == 0) callback.onResponse(resources);
}, error -> errorCallback.onError(new NetworkError(101616, error.networkResponse.statusCode, "Cannot get resource attachments!")));
}
} catch (JSONException e) {
log.e(new NetworkError(101615, 400, "Cannot parse resources!"));
e.printStackTrace();
log.e("ID:", i, "JSON:", sites);
}
}
}, error -> {
if (error.networkResponse.statusCode != 403) {
errorCallback.onError(new NetworkError(101613, error.networkResponse.statusCode, "Cannot get resources!"));
} else {
callback.onResponse(new ArrayList<>());
}
});
}
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, url.contains("lms.fu-berlin.de") ? Login.LOGOUT_BB : Login.LOGOUT_KVV);
return;
}
errorCallback.onError(error);
});
}
private void fileUpgrade(String filename, String url, String modulename, final NetworkCallback<String> callback, final NetworkErrorCallback errorCallback) {
if (url.contains("lms.fu-berlin.de")) {
fileUpgradeBB(filename, url, modulename, callback, errorCallback);
} else {
fileUpgradeKVV(filename, url, modulename, callback, errorCallback);
}
}
private void fileUpgradeKVV(String filename, String url, String modulename, final NetworkCallback<String> callback, final NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginTokenKVV() == null || !mLogin.getLoginTokenKVV().isAvailable()) {
errorCallback.onError(new NetworkError(101604, 500, "Currently running in offline mode!"));
return;
}
get(url, mLogin.getLoginTokenKVV().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, 400, "External storage not writable!"));
}
}, error -> errorCallback.onError(new NetworkError(101702, error.networkResponse.statusCode, "Cannot get file!")));
}
private void fileUpgradeBB(String filename, String url, String modulename, final NetworkCallback<String> callback, final NetworkErrorCallback errorCallback) {
if (!mLogin.isInOnlineMode() || mLogin.getLoginTokenBB() == null || !mLogin.getLoginTokenBB().isAvailable()) {
errorCallback.onError(new NetworkError(101615, 500, "Currently running in offline mode!"));
return;
}
get(url, mLogin.getLoginTokenBB().getCookies(), response -> {
if (response.getHeaders().containsKey("Location")) {
// Return redirected URL
String redirectUrl = response.getHeaders().get("Location");
if (redirectUrl == null){
redirectUrl = "";
}
get(redirectUrl, mLogin.getLoginTokenBB().getCookies(), response1 -> {
if (response1.getHeaders().containsKey("Location")) {
// Return redirected URL
String redirectUrl2 = response1.getHeaders().get("Location");
if (redirectUrl2 == null) {
redirectUrl2 = "";
} else {
redirectUrl2 = "https://lms.fu-berlin.de" + redirectUrl2;
}
get(redirectUrl2, mLogin.getLoginTokenBB().getCookies(), response2 -> {
if (response2.getBytes() == null) {
errorCallback.onError(new NetworkError(101714, 400, "Cannot get file!"));
} else if (isExternalStorageWritable()) {
String path = saveFileInDownloads(filename, response2.getBytes(), modulename);
callback.onResponse(path);
} else {
errorCallback.onError(new NetworkError(101713, 400, "External storage not writable!"));
}
}, error -> errorCallback.onError(new NetworkError(101717, error.networkResponse.statusCode, "Cannot get file!")));
} else {
errorCallback.onError(new NetworkError(101716, 400, "External storage not writable!"));
}
}, error -> errorCallback.onError(new NetworkError(101712, error.networkResponse.statusCode, "Cannot get file!")));
} else {
errorCallback.onError(new NetworkError(101711, 400, "External storage not writable!"));
}
}, error -> errorCallback.onError(new NetworkError(101710, 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.mkdirs()) {
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,87 @@
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 (mLogin.isLoginPending()) {
mLogin.restoreOnlineLogin(resCode -> {
mQueue.next();
});
} else {
mQueue.next();
}
});
mQueue.add(() -> {
if (getPart(module) != null && !forceRefresh) {
callback.onResponse(module);
mQueue.next();
return;
}
upgrade(module.getModuleType(), 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();
}, module.getModuleType() == Modules.TYPE_BB ? Login.LOGOUT_BB : Login.LOGOUT_KVV);
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 void upgrade(final int moduleType, final String ID, final NetworkCallback<T> callback, final NetworkErrorCallback errorCallback) {
switch (moduleType) {
case Modules.TYPE_KVV:
upgradeKVV(ID, callback, errorCallback);
break;
case Modules.TYPE_BB:
upgradeBB(ID, callback, errorCallback);
break;
}
}
protected abstract void upgradeKVV(final String ID, final NetworkCallback<T> callback, final NetworkErrorCallback errorCallback);
protected abstract void upgradeBB(final String ID, final NetworkCallback<T> callback, final NetworkErrorCallback errorCallback);
}

View File

@@ -0,0 +1,162 @@
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.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.sebse.fuplanner.services.kvv.types.LoginTokenBB;
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 BBLogin extends HTTPService {
private final FULogin mFULogin;
private long lastHash;
private long lastSync;
private static final long MAX_CACHE_TIME = 1000 * 60; // 1 minute
public BBLogin(Context context, FULogin fuLogin) {
super(context);
this.mFULogin = fuLogin;
}
public void testLoginToken(@NotNull LoginTokenBB token, @NotNull NetworkCallback<LoginTokenBB> callback, @NotNull NetworkErrorCallback errorCallback) {
if (token.hashCode() == lastHash && lastSync + MAX_CACHE_TIME > System.currentTimeMillis() && token.getStudentId() != null) {
callback.onResponse(token);
return;
}
get(String.format("https://lms.fu-berlin.de/learn/api/public/v1/users/?userName=%s", token.getUsername()), token.getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(100272, 403, "Testing login failed!"));
return;
}
try {
JSONObject json = new JSONObject(body);
json = json.getJSONArray("results").getJSONObject(0);
String id = json.getString("id");
String studentId = json.getString("studentId");
token.setAdditionals(id, studentId);
lastSync = System.currentTimeMillis();
lastHash = token.hashCode();
callback.onResponse(token);
} catch (JSONException e) {
errorCallback.onError(new NetworkError(100271, 403, "Cannot parse profile!"));
}
}, error -> errorCallback.onError(new NetworkError(100270, error.networkResponse.statusCode, "Testing login failed!")));
}
public void doLogin(String username, String password, NetworkCallback<LoginTokenBB> callback, NetworkErrorCallback error) {
step1(success1 -> {
String samlLocation = success1.get("Location");
mFULogin.fulogin(samlLocation, username, password, samlResponse -> {
step5(samlResponse, success5 -> {
String shibsessionKey = success5.get("shibsessionKey");
String shibsessionName = success5.get("shibsessionName");
step6(shibsessionKey, shibsessionName, success6 -> {
String s_session_id = success6.get("s_session_id");
String session_id = success6.get("session_id");
LoginTokenBB token = new LoginTokenBB(username, s_session_id, session_id);
callback.onResponse(token);
}, error);
}, error);
}, error);
}, error);
}
/*
1= GET https://lms.fu-berlin.de/lms-apps/login/sso/index.php
-> Location-Header: https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO?SAMLResponse=[SAMLResponse]&RelayState=[RelayState]
*/
private void step1(final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
get("https://lms.fu-berlin.de/lms-apps/login/sso/index.php", null, response -> {
String location = response.getHeaders().get("Location");
if (location==null) {
errorCallback.onError(new NetworkError(100211, -1, "Error on getting SAML request!"));
return;
}
HashMap<String, String> object = new HashMap<>();
object.put("Location", location);
callback.onResponse(object);
}, error -> errorCallback.onError(new NetworkError(100210, error.networkResponse.statusCode, "Error on getting SAML request!")));
}
/*
5= POST https://lms.fu-berlin.de/Shibboleth.sso/SAML2/POST
+ Body: SAMLResponse=[SAML-RESPONSE]
+ Header: Content-Type: application/x-www-form-urlencoded
-> Set-Cookie: _shibsession_[SESS-NR]: [SESS-VALUE]
*/
private void step5(String SAMLResponse, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
HashMap<String, String> body = new HashMap<>();
body.put("SAMLResponse", SAMLResponse);
post("https://lms.fu-berlin.de/Shibboleth.sso/SAML2/POST", null, body, response -> {
String cookies = response.getHeaders().get("Set-Cookie");
if (cookies ==null) {
errorCallback.onError(new NetworkError(100251, -1, "Error on starting KVV session!"));
return;
}
HashMap<String, String> object = new HashMap<>();
Pattern pattern = Pattern.compile("(_shibsession_[0-9a-f]+)=([^;]+);");
Matcher matcher = pattern.matcher(cookies);
if (!matcher.find()) {
errorCallback.onError(new NetworkError(100252, -1, "Error on starting KVV session!"));
}
object.put("shibsessionKey", matcher.group(1));
object.put("shibsessionName", matcher.group(2));
callback.onResponse(object);
}, error -> errorCallback.onError(new NetworkError(100250, error.networkResponse.statusCode, "Error on starting KVV session!")));
}
/*
6= https://lms.fu-berlin.de/webapps/bb-auth-provider-shibboleth-bb_bb60/execute/shibbolethLogin?returnUrl=https://lms.fu-berlin.de/webapps/portal/execute/defaultTab&authProviderId=_3_1
+ Cookie: _shibsession_[SESS-NR]: [SESS-VALUE]
-> Set-Cookie: JSESSIONID: [JSESSION-KVV]
*/
private void step6(String shibsessionKey, String shibsessionName, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
HashMap<String, String> cookies = new HashMap<>();
cookies.put(shibsessionKey, shibsessionName);
get("https://lms.fu-berlin.de/webapps/bb-auth-provider-shibboleth-bb_bb60/execute/shibbolethLogin?returnUrl=https://lms.fu-berlin.de/webapps/portal/execute/defaultTab&authProviderId=_3_1", cookies, response -> {
String cookies1 = response.getHeaders().get("Set-Cookie");
if (cookies1 ==null) {
errorCallback.onError(new NetworkError(100261, -1, "Cannot finish login process!"));
return;
}
HashMap<String, String> object;
try {
object = getCookie(cookies1, new String[]{"session_id", "s_session_id"});
} catch (NoSuchFieldException e) {
errorCallback.onError(new NetworkError(100262, -1, "Cannot finish login process!"));
return;
}
callback.onResponse(object);
}, error -> errorCallback.onError(new NetworkError(100260, error.networkResponse.statusCode, "Cannot finish login process!")));
}
}

View File

@@ -0,0 +1,143 @@
package de.sebse.fuplanner.services.kvv.sync;
import android.content.Context;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import androidx.annotation.Nullable;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.tools.Preferences;
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 FULogin extends HTTPService {
public FULogin(Context context) {
super(context);
}
public void fulogin(String requestURI, String username, String password, final NetworkCallback<String> callback, final NetworkErrorCallback errorCallback) {
String old_shib_idp_session = Preferences.getString(getContext(), R.string.pref_shib_idp_session);
step2(requestURI, old_shib_idp_session, success2 -> {
String samlResp = success2.get("SAMLResponse");
if (samlResp != null) {
callback.onResponse(samlResp);
return;
}
String fuJSESSIONID = success2.get("JSESSIONID");
step3(fuJSESSIONID, success3 -> {
step4(username, password, fuJSESSIONID, success4 -> {
String shib_idp_session = success4.get("shib_idp_session");
Preferences.setString(getContext(), R.string.pref_shib_idp_session, shib_idp_session);
String samlResponse = success4.get("SAMLResponse");
if (samlResponse != null)
callback.onResponse(samlResponse);
else
errorCallback.onError(new NetworkError(100300, -1, "Cannot get SAML Response!"));
}, errorCallback);
}, errorCallback);
}, errorCallback);
}
/*
2= GET [Location-Header 1]
-> Set-Cookie: JSESSIONID=[JSESSION-FU]
-> Location: /idp-fub/profile/SAML2/Redirect/SSO?execution=e1s1
*/
private void step2(String url, @Nullable String shib_idp_session, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
HashMap<String, String> cookiesReq = null;
if (shib_idp_session != null) {
cookiesReq = new HashMap<>();
cookiesReq.put("shib_idp_session", shib_idp_session);
}
get(url, cookiesReq, response -> {
String body = response.getParsed();
if (body != null) {
Pattern pattern = Pattern.compile("name=\"SAMLResponse\" value=\"([0-9a-zA-Z+]+=*)");
Matcher matcher = pattern.matcher(body);
if (!matcher.find()) {
errorCallback.onError(new NetworkError(100344, -1, "Error on getting SAML response!"));
return;
}
HashMap<String, String> object = new HashMap<>();
object.put("SAMLResponse", matcher.group(1));
callback.onResponse(object);
return;
}
String cookies = response.getHeaders().get("Set-Cookie");
if (cookies == null) {
errorCallback.onError(new NetworkError(100321, -1, "Error on starting FU session!"));
return;
}
HashMap<String, String> object;
try {
object = getCookie(cookies, new String[]{"JSESSIONID"});
} catch (NoSuchFieldException e) {
errorCallback.onError(new NetworkError(100322, -1, "Error on starting FU session!"));
return;
}
callback.onResponse(object);
}, error -> errorCallback.onError(new NetworkError(100320, error.networkResponse.statusCode, "Error on starting FU session!")));
}
/*
3= GET [Location-Header 2]
+ Cookie: JSESSIONID=[JSESSION-FU]
*/
private void step3(String JSESSIONID_FU, final NetworkCallback<Boolean> callback, final NetworkErrorCallback errorCallback) {
HashMap<String, String> cookies = new HashMap<>();
cookies.put("JSESSIONID", JSESSIONID_FU);
head("https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO?execution=e1s1", cookies, response -> {
callback.onResponse(true);
}, error -> errorCallback.onError(new NetworkError(100330, error.networkResponse.statusCode, "Error starting login page!")));
}
/*
4= POST [Location-Header 2]
+ Body: j_username=[USERNAME]&j_password=[PASSWORD]&_eventId_proceed=
+ Header: Content-Type: application/x-www-form-urlencoded
+ Header: Referer: [Location-Header 2]
+ Cookie: JSESSIONID=[JSESSION-FU]
-> Set-Cookie: shib_idp_session=[SHIB-IDP-SESSION]
-> Body SAMLResponse-Input-value
*/
private void step4(String username, String password, String JSESSIONID_FU, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
HashMap<String, String> cookies = new HashMap<>();
cookies.put("JSESSIONID", JSESSIONID_FU);
HashMap<String, String> body = new HashMap<>();
body.put("j_username", username);
body.put("j_password", password);
body.put("_eventId_proceed", "");
post("https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO?execution=e1s1", cookies, body, response -> {
String content = response.getParsed();
if (content == null) {
errorCallback.onError(new NetworkError(100343, -1, "Error on getting SAML response!"));
return;
}
String cookies1 = response.getHeaders().get("Set-Cookie");
if (cookies1 ==null) {
errorCallback.onError(new NetworkError(100341, -1, "Error on logging in to FU Identity Server!"));
return;
}
HashMap<String, String> object;
try {
object = getCookie(cookies1, new String[]{"shib_idp_session"});
} catch (NoSuchFieldException e) {
errorCallback.onError(new NetworkError(100342, -1, "Error on logging in to FU Identity Server!"));
return;
}
Pattern pattern = Pattern.compile("name=\"SAMLResponse\" value=\"([0-9a-zA-Z+]+=*)");
Matcher matcher = pattern.matcher(content);
if (!matcher.find()) {
errorCallback.onError(new NetworkError(100344, -1, "Error on getting SAML response!"));
return;
}
object.put("SAMLResponse", matcher.group(1));
callback.onResponse(object);
}, error -> errorCallback.onError(new NetworkError(100345, error.networkResponse.statusCode, "Error on logging in to FU Identity Server!")));
}
}

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 {
public 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,176 @@
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.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.sebse.fuplanner.services.kvv.Constants;
import de.sebse.fuplanner.services.kvv.types.LoginTokenKVV;
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 KVVLogin extends HTTPService {
private final FULogin mFULogin;
private long lastHash;
private long lastSync;
private static final long MAX_CACHE_TIME = 1000 * 60; // 1 minute
public static final String KVV_SERVER_URL = Constants.KVV_SERVER_URL;
public KVVLogin(Context context, FULogin fuLogin) {
super(context);
this.mFULogin = fuLogin;
}
public void testLoginToken(@NotNull LoginTokenKVV token, @NotNull NetworkCallback<LoginTokenKVV> callback, @NotNull NetworkErrorCallback errorCallback) {
if (token.hashCode() == lastHash && lastSync + MAX_CACHE_TIME > System.currentTimeMillis() && token.getFullName() != null) {
callback.onResponse(token);
return;
}
get(String.format(KVV_SERVER_URL+"direct/profile/%s.json", token.getUsername()), token.getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(100172, 403, "Testing login failed!"));
return;
}
try {
JSONObject json = new JSONObject(body);
String displayName = json.getString("displayName");
String email = json.getString("email");
token.setAdditionals(displayName, email);
lastSync = System.currentTimeMillis();
lastHash = token.hashCode();
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!")));
}
public void doLogin(String username, String password, NetworkCallback<LoginTokenKVV> callback, NetworkErrorCallback error) {
step0(username, success -> {
step1(success1 -> {
String samlLocation = success1.get("Location");
mFULogin.fulogin(samlLocation, username, password, samlResponse -> {
step5(samlResponse, success5 -> {
String shibsessionKey = success5.get("shibsessionKey");
String shibsessionName = success5.get("shibsessionName");
step6(shibsessionKey, shibsessionName, success6 -> {
String kvvJSESSIONID = success6.get("JSESSIONID");
LoginTokenKVV token = new LoginTokenKVV(username, kvvJSESSIONID);
callback.onResponse(token);
}, error);
}, error);
}, error);
}, error);
}, error);
}
private void step0(String username, final NetworkCallback<Boolean> callback, final NetworkErrorCallback errorCallback) {
get(String.format(KVV_SERVER_URL+"direct/profile/%s", username), null, result -> {
callback.onResponse(true);
}, error -> {
if (error.networkResponse.statusCode == 500) {
errorCallback.onError(new NetworkError(100101, error.networkResponse.statusCode, "KVV not available!"));
} else {
callback.onResponse(true);
}
});
}
/*
1= GET https://kvv.imp.fu-berlin.de/Shibboleth.sso/Login?entityID=https://identity.fu-berlin.de/idp-fub
-> Location-Header: https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO?SAMLResponse=[SAMLResponse]&RelayState=[RelayState]
*/
private void step1(final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
get(KVV_SERVER_URL+"Shibboleth.sso/Login?entityID=https://identity.fu-berlin.de/idp-fub", null, response -> {
String location = response.getHeaders().get("Location");
if (location==null) {
errorCallback.onError(new NetworkError(100111, -1, "Error on getting SAML request!"));
return;
}
HashMap<String, String> object = new HashMap<>();
object.put("Location", location);
callback.onResponse(object);
}, error -> errorCallback.onError(new NetworkError(100110, error.networkResponse.statusCode, "Error on getting SAML request!")));
}
/*
5= POST https://kvv.imp.fu-berlin.de/Shibboleth.sso/SAML2/POST
+ Body: SAMLResponse=[SAML-RESPONSE]
+ Header: Content-Type: application/x-www-form-urlencoded
-> Set-Cookie: _shibsession_[SESS-NR]: [SESS-VALUE]
*/
private void step5(String SAMLResponse, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
HashMap<String, String> body = new HashMap<>();
body.put("SAMLResponse", SAMLResponse);
post(KVV_SERVER_URL+"Shibboleth.sso/SAML2/POST", null, body, response -> {
String cookies = response.getHeaders().get("Set-Cookie");
if (cookies ==null) {
errorCallback.onError(new NetworkError(100151, -1, "Error on starting KVV session!"));
return;
}
HashMap<String, String> object = new HashMap<>();
Pattern pattern = Pattern.compile("(_shibsession_[0-9a-f]+)=([^;]+);");
Matcher matcher = pattern.matcher(cookies);
if (!matcher.find()) {
errorCallback.onError(new NetworkError(100152, -1, "Error on starting KVV session!"));
}
object.put("shibsessionKey", matcher.group(1));
object.put("shibsessionName", matcher.group(2));
callback.onResponse(object);
}, error -> errorCallback.onError(new NetworkError(100150, error.networkResponse.statusCode, "Error on starting KVV session!")));
}
/*
6= https://kvv.imp.fu-berlin.de/sakai-login-tool/container
+ Cookie: _shibsession_[SESS-NR]: [SESS-VALUE]
-> Set-Cookie: JSESSIONID: [JSESSION-KVV]
*/
private void step6(String shibsessionKey, String shibsessionName, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
HashMap<String, String> cookies = new HashMap<>();
cookies.put(shibsessionKey, shibsessionName);
get(KVV_SERVER_URL+"sakai-login-tool/container", cookies, response -> {
String cookies1 = response.getHeaders().get("Set-Cookie");
if (cookies1 ==null) {
errorCallback.onError(new NetworkError(100161, -1, "Cannot finish login process!"));
return;
}
HashMap<String, String> object;
try {
object = getCookie(cookies1, new String[]{"JSESSIONID"});
} catch (NoSuchFieldException e) {
errorCallback.onError(new NetworkError(100162, -1, "Cannot finish login process!"));
return;
}
callback.onResponse(object);
}, error -> errorCallback.onError(new NetworkError(100160, error.networkResponse.statusCode, "Cannot finish login process!")));
}
}

View File

@@ -0,0 +1,204 @@
package de.sebse.fuplanner.services.kvv.sync;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ComponentName;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SyncResult;
import android.os.Bundle;
import android.os.IBinder;
import java.util.ArrayList;
import java.util.Iterator;
import androidx.annotation.StringRes;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.fragments.moddetails.ModulePart;
import de.sebse.fuplanner.services.kvv.KVV;
import de.sebse.fuplanner.services.kvv.KVVListener;
import de.sebse.fuplanner.services.kvv.types.Announcement;
import de.sebse.fuplanner.services.kvv.types.Assignment;
import de.sebse.fuplanner.services.kvv.types.AssignmentList;
import de.sebse.fuplanner.services.kvv.types.EventList;
import de.sebse.fuplanner.services.kvv.types.Grade;
import de.sebse.fuplanner.services.kvv.types.Modules;
import de.sebse.fuplanner.services.kvv.types.Resource;
import de.sebse.fuplanner.tools.CustomAccountManager;
import de.sebse.fuplanner.tools.CustomNotificationManager;
import de.sebse.fuplanner.tools.NewAsyncQueue;
import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.logging.Logger;
import static de.sebse.fuplanner.MainActivity.FRAGMENT_MODULES_DETAILS;
public class KVVSyncAdapter extends AbstractThreadedSyncAdapter {
private KVV mKVV;
private Logger log = new Logger(this);
private NewAsyncQueue mQueue = new NewAsyncQueue();
private boolean mBound = false;
private boolean mWaitForBound = false;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
KVV.LocalBinder binder = (KVV.LocalBinder) service;
mKVV = binder.getService();
mBound = true;
if (mWaitForBound) {
mWaitForBound = false;
mQueue.next();
}
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
mKVV = null;
}
};
/**
* Set up the sync adapter
*/
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) {
// Bind to LocalService
}
/*
* 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) {
if (!mBound) {
Intent intent = new Intent(getContext(), KVV.class);
mWaitForBound = true;
mQueue.add(() -> {});
}
mQueue.add(() -> {
mKVV.account().restoreOnlineLogin(bool -> {
mQueue.next();
});
});
mQueue.add(() -> {
if (!mKVV.account().isLoggedIn()) {
log.w("Not logged in!");
return;
}
mKVV.modules().list().reloadIfOutdated();
mKVV.modules().list().recv(success -> {
final int[] latch = {0};
Iterator<Modules.Module> iterator = success.latestSemesterIterator();
while (iterator.hasNext()) {
latch[0] += 1;
iterator.next();
}
iterator = success.latestSemesterIterator();
while (iterator.hasNext()) {
Modules.Module module = iterator.next();
final ArrayList<Announcement> announcements = module.announcements;
final AssignmentList assignments = module.assignments;
final EventList events = module.events;
final ArrayList<Grade> gradebook = module.gradebook;
final ArrayList<Resource> resources = module.resources;
mKVV.modules().details().recv(module, success1 -> {
if (success1.second) {
sendNotifications(announcements, module.announcements, module.title, Announcement::getTitle, Announcement::getId,
module.getID(), ModulePart.ANNOUNCEMENT,
R.string.announcement_updated, R.string.announcement_added, R.string.announcement_removed);
sendNotifications(assignments, module.assignments, module.title, Assignment::getTitle, Assignment::getId,
module.getID(), ModulePart.ASSIGNMENT,
R.string.assignment_updated, R.string.assignment_added, R.string.assignment_removed);
sendNotifications(events, module.events, module.title, evt -> evt.getTitle()+" - "+UtilsDate.getModifiedDate(evt.getStartDate()), event -> String.valueOf(event.getStartDate())+event.getType()+event.getTitle(),
module.getID(), ModulePart.EVENT,
R.string.event_updated, R.string.event_added, R.string.event_removed);
sendNotifications(gradebook, module.gradebook, module.title, Grade::getItemName, Grade::getItemName,
module.getID(), ModulePart.GRADEBOOK,
R.string.gradebook_updated, R.string.gradebook_added, R.string.gradebook_removed);
sendNotifications(resources, module.resources, module.title, Resource::getTitle, Resource::getUrl,
module.getID(), ModulePart.RESOURCES,
R.string.resource_updated, R.string.resource_added, R.string.resource_removed);
if (--latch[0] == 0) mQueue.next();
}
}, error -> {
log.e(error);
if (--latch[0] == 0) mQueue.next();
}, true);
}
}, msg -> {
log.e(msg);
mQueue.next();
}, true);
});
mQueue.add(() -> {
mBound = false;
mKVV = null;
getContext().unbindService(mConnection);
});
}
private <T> void sendNotifications(Iterable<T> oldList, Iterable<T> newList, String title, StringInterface<T> titleInterface, StringInterface<T> idInterface, String moduleId, int modulePart, @StringRes int updateRes, @StringRes int addRes, @StringRes int removeRes) {
if (oldList == null || newList == null) {
return;
}
ArrayList<T> obsoletes = new ArrayList<>();
for (T old: oldList) {
obsoletes.add(old);
}
String targetData = moduleId+"."+ModulePart.getPageByPart(modulePart);
for (T newEntry: newList) {
boolean found = false;
for (T oldEntry: oldList) {
if (idInterface.get(newEntry).equals(idInterface.get(oldEntry))) {
found = true;
if (newEntry.hashCode() != oldEntry.hashCode()) {
CustomNotificationManager.sendNotification(getContext(), getContext().getString(updateRes, title), titleInterface.get(newEntry), FRAGMENT_MODULES_DETAILS, targetData);
}
obsoletes.remove(oldEntry);
break;
}
}
if (!found) {
CustomNotificationManager.sendNotification(getContext(), getContext().getString(addRes, title), titleInterface.get(newEntry), FRAGMENT_MODULES_DETAILS, targetData);
}
}
for (T oldEntry: obsoletes) {
CustomNotificationManager.sendNotification(getContext(), getContext().getString(removeRes, title), titleInterface.get(oldEntry), FRAGMENT_MODULES_DETAILS, targetData);
}
}
@FunctionalInterface
interface StringInterface<T> {
String get(T element);
}
}

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,8 +1,12 @@
package de.sebse.fuplanner.services.KVV.types; package de.sebse.fuplanner.services.kvv.types;
import com.google.android.gms.common.internal.Objects;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import androidx.annotation.NonNull;
public class Announcement implements Serializable { public class Announcement implements Serializable {
private final String id; private final String id;
private final String title; private final String title;
@@ -12,7 +16,6 @@ public class Announcement implements Serializable {
private final ArrayList<String> urls; private final ArrayList<String> urls;
public Announcement(String id, String title, String body, String createdBy, long createdOn, ArrayList<String> urls) { public Announcement(String id, String title, String body, String createdBy, long createdOn, ArrayList<String> urls) {
this.id = id; this.id = id;
this.title = title; this.title = title;
this.body = body; this.body = body;
@@ -45,6 +48,7 @@ public class Announcement implements Serializable {
return createdOn; return createdOn;
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
return "ID: "+getId()+ return "ID: "+getId()+
@@ -54,4 +58,9 @@ public class Announcement implements Serializable {
"\nCreated on: "+getCreatedOn()+ "\nCreated on: "+getCreatedOn()+
"\nURLs: "+getUrls().toString(); "\nURLs: "+getUrls().toString();
} }
@Override
public int hashCode() {
return Objects.hashCode(getId(), getBody(), getCreatedBy(), getCreatedOn(), getTitle(), getUrls());
}
} }

View File

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

View File

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

View File

@@ -0,0 +1,92 @@
package de.sebse.fuplanner.services.kvv.types;
import android.content.Context;
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;
import java.util.HashSet;
public class CacheBBCourse implements Serializable {
private transient static final long RESAVE_TIMER = 1000L * 60 * 60 * 24 * 30;
private static final String FILE_NAME = "BBCourseStorageSaving";
private static final String FILE_NAME_TIMESTAMP = "BBCourseStorageSavingTimestamp";
private transient long mLastTimestamp = 0;
private HashSet<String> mKVVCourseList = new HashSet<>();
private long mKVVCourseListRefresh = 0;
private HashMap<String, Modules.Module> mBBCourseList = new HashMap<>();
private HashMap<String, Long> mBBCourseListRefresh = new HashMap<>();
public static CacheBBCourse load(Context context) throws IOException, ClassNotFoundException {
FileInputStream fis = context.openFileInput(FILE_NAME);
ObjectInputStream is = new ObjectInputStream(fis);
Object readObject = is.readObject();
if (!(readObject instanceof CacheBBCourse))
return null;
CacheBBCourse storage = (CacheBBCourse) readObject;
is.close();
fis.close();
fis = context.openFileInput(FILE_NAME_TIMESTAMP);
is = new ObjectInputStream(fis);
storage.mLastTimestamp = is.readLong();
is.close();
fis.close();
return storage;
}
public boolean isNewerVersionInStorage(Context context) throws IOException {
FileInputStream fis = context.openFileInput(FILE_NAME_TIMESTAMP);
ObjectInputStream is = new ObjectInputStream(fis);
boolean result = this.mLastTimestamp < is.readLong();
is.close();
fis.close();
return result;
}
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();
fos = context.openFileOutput(FILE_NAME_TIMESTAMP, Context.MODE_PRIVATE);
os = new ObjectOutputStream(fos);
this.mLastTimestamp = System.currentTimeMillis();
os.writeLong(this.mLastTimestamp);
os.close();
fos.close();
}
public void addKVVCourseID(String courseID) {
mKVVCourseList.add(courseID);
mKVVCourseListRefresh = System.currentTimeMillis();
}
public boolean hasKVVCourseID(String courseID) {
if (mKVVCourseListRefresh + RESAVE_TIMER < System.currentTimeMillis())
return false;
return mKVVCourseList.contains(courseID);
}
public void setBBCourse(String courseID, Modules.Module lecturer) {
mBBCourseList.put(courseID, lecturer);
mBBCourseListRefresh.put(courseID, System.currentTimeMillis());
}
public Modules.Module getBBCourse(String courseID) {
if (!mBBCourseListRefresh.containsKey(courseID))
return null;
//noinspection ConstantConditions
if (mBBCourseListRefresh.get(courseID) + RESAVE_TIMER < System.currentTimeMillis())
return null;
return mBBCourseList.get(courseID);
}
}

View File

@@ -0,0 +1,78 @@
package de.sebse.fuplanner.services.kvv.types;
import android.content.Context;
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;
public class CacheBBEventNumber implements Serializable {
private transient static final long RESAVE_TIMER = 1000L * 60 * 60 * 24 * 30;
private static final String FILE_NAME = "BBEventNumberStorageSaving";
private static final String FILE_NAME_TIMESTAMP = "BBEventNumberStorageSavingTimestamp";
private transient long mLastTimestamp = 0;
private HashMap<String, String> mBBEventNumbers = new HashMap<>();
private HashMap<String, Long> mBBEventNumbersRefresh = new HashMap<>();
public static CacheBBEventNumber load(Context context) throws IOException, ClassNotFoundException {
FileInputStream fis = context.openFileInput(FILE_NAME);
ObjectInputStream is = new ObjectInputStream(fis);
Object readObject = is.readObject();
if (!(readObject instanceof CacheBBEventNumber))
return null;
CacheBBEventNumber storage = (CacheBBEventNumber) readObject;
is.close();
fis.close();
fis = context.openFileInput(FILE_NAME_TIMESTAMP);
is = new ObjectInputStream(fis);
storage.mLastTimestamp = is.readLong();
is.close();
fis.close();
return storage;
}
public boolean isNewerVersionInStorage(Context context) throws IOException {
FileInputStream fis = context.openFileInput(FILE_NAME_TIMESTAMP);
ObjectInputStream is = new ObjectInputStream(fis);
boolean result = this.mLastTimestamp < is.readLong();
is.close();
fis.close();
return result;
}
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();
fos = context.openFileOutput(FILE_NAME_TIMESTAMP, Context.MODE_PRIVATE);
os = new ObjectOutputStream(fos);
this.mLastTimestamp = System.currentTimeMillis();
os.writeLong(this.mLastTimestamp);
os.close();
fos.close();
}
public void setVVNumber(String lvNumber, String vvNumber) {
mBBEventNumbers.put(lvNumber, vvNumber);
mBBEventNumbersRefresh.put(lvNumber, System.currentTimeMillis());
}
public String getVVNumber(String lvNumber) {
if (!mBBEventNumbersRefresh.containsKey(lvNumber))
return null;
//noinspection ConstantConditions
if (mBBEventNumbersRefresh.get(lvNumber) + RESAVE_TIMER < System.currentTimeMillis())
return null;
return mBBEventNumbers.get(lvNumber);
}
}

View File

@@ -0,0 +1,79 @@
package de.sebse.fuplanner.services.kvv.types;
import android.content.Context;
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;
import java.util.HashSet;
public class CacheKVVCourse implements Serializable {
private transient static final long RESAVE_TIMER = 1000L * 60 * 60 * 24 * 30;
private static final String FILE_NAME = "KVVCourseStorageSaving";
private static final String FILE_NAME_TIMESTAMP = "KVVCourseStorageSavingTimestamp";
private transient long mLastTimestamp = 0;
private HashMap<String, Modules.Module> mKVVCourseList = new HashMap<>();
private HashMap<String, Long> mKVVCourseListRefresh = new HashMap<>();
public static CacheKVVCourse load(Context context) throws IOException, ClassNotFoundException {
FileInputStream fis = context.openFileInput(FILE_NAME);
ObjectInputStream is = new ObjectInputStream(fis);
Object readObject = is.readObject();
if (!(readObject instanceof CacheKVVCourse))
return null;
CacheKVVCourse storage = (CacheKVVCourse) readObject;
is.close();
fis.close();
fis = context.openFileInput(FILE_NAME_TIMESTAMP);
is = new ObjectInputStream(fis);
storage.mLastTimestamp = is.readLong();
is.close();
fis.close();
return storage;
}
public boolean isNewerVersionInStorage(Context context) throws IOException {
FileInputStream fis = context.openFileInput(FILE_NAME_TIMESTAMP);
ObjectInputStream is = new ObjectInputStream(fis);
boolean result = this.mLastTimestamp < is.readLong();
is.close();
fis.close();
return result;
}
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();
fos = context.openFileOutput(FILE_NAME_TIMESTAMP, Context.MODE_PRIVATE);
os = new ObjectOutputStream(fos);
this.mLastTimestamp = System.currentTimeMillis();
os.writeLong(this.mLastTimestamp);
os.close();
fos.close();
}
public void setKVVCourse(String courseID, Modules.Module lecturer) {
mKVVCourseList.put(courseID, lecturer);
mKVVCourseListRefresh.put(courseID, System.currentTimeMillis());
}
public Modules.Module getKVVCourse(String courseID) {
if (!mKVVCourseListRefresh.containsKey(courseID))
return null;
//noinspection ConstantConditions
if (mKVVCourseListRefresh.get(courseID) + RESAVE_TIMER < System.currentTimeMillis())
return null;
return mKVVCourseList.get(courseID);
}
}

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