186 Commits

Author SHA1 Message Date
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
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
163 changed files with 2827 additions and 827 deletions

4
.gitignore vendored
View File

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

View File

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

18
.idea/gradle.xml generated
View File

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

29
.idea/misc.xml generated
View File

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

9
.idea/modules.xml generated
View File

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

View File

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

7
.idea/vcs.xml generated
View File

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

View File

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

View File

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

View File

@@ -4,6 +4,9 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -2,24 +2,27 @@ package de.sebse.fuplanner;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.design.widget.NavigationView;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.navigation.NavigationView;
import java.util.HashMap;
import java.util.Iterator;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import de.sebse.fuplanner.fragments.CanteensFragment;
import de.sebse.fuplanner.fragments.LoginFragment;
import de.sebse.fuplanner.fragments.ModulesFragment;
@@ -34,11 +37,12 @@ import de.sebse.fuplanner.services.GoogleAuth.GoogleAuth;
import de.sebse.fuplanner.services.KVV.KVV;
import de.sebse.fuplanner.services.KVV.types.LoginToken;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.MainAcitivityListener;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.RequestPermissionsResultListener;
import de.sebse.fuplanner.tools.logging.Logger;
public class MainActivity extends AppCompatActivity
implements MainAcitivityListener,
implements MainActivityListener,
NavigationView.OnNavigationItemSelectedListener,
LoginFragment.OnLoginFragmentInteractionListener,
ModulesFragment.OnModulesFragmentInteractionListener,
@@ -64,9 +68,11 @@ public class MainActivity extends AppCompatActivity
private NavigationView mNavigationView;
private int fragmentPage = FRAGMENT_NONE;
private int currentPage = FRAGMENT_NONE;
private String fragmentData = "";
private CanteenBrowser mCanteenBrowser;
private boolean mOfflineMode = false;
private HashMap<String, RequestPermissionsResultListener> permissionListeners = new HashMap<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -93,24 +99,14 @@ public class MainActivity extends AppCompatActivity
mFragmentManager = getSupportFragmentManager();
LoginToken loginToken = getKVV().easyLogin();
if (newFragmentPage != FRAGMENT_LOGIN && newFragmentPage != FRAGMENT_STARTUP && newFragmentPage != FRAGMENT_NONE) {
if (loginToken != null)
toLoginState(loginToken, newFragmentPage, newFragmentData);
else
if (loginToken == null) {
checkAndDoLogin();
} else {
if (loginToken != null)
toLoginState(loginToken, getDefaultFragmentAfterLogin(), "");
if (newFragmentPage != FRAGMENT_LOGIN && newFragmentPage != FRAGMENT_STARTUP && newFragmentPage != FRAGMENT_NONE)
toLoginState(loginToken, newFragmentPage, newFragmentData);
else
checkAndDoLogin();
toLoginState(loginToken, getDefaultFragmentAfterLogin(), "");
}
/*this.getCanteenBrowser().getCanteens(success -> {
Canteen canteen = success.get(0);
this.getCanteenBrowser().getCanteen(canteen, success1 -> {
this.getCanteenBrowser().getDay(canteen.get(0), log::d, log::e, true);
}, log::e, true);
}, log::e, true);*/
}
@Override
@@ -126,7 +122,7 @@ public class MainActivity extends AppCompatActivity
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
if (fragmentPage == FRAGMENT_SCHEDULE) {
if (currentPage == FRAGMENT_SCHEDULE) {
getMenuInflater().inflate(R.menu.options_schedule, menu);
return true;
}
@@ -162,7 +158,6 @@ public class MainActivity extends AppCompatActivity
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
switch (id) {
case R.id.nav_modules:
changeFragment(FRAGMENT_MODULES);
@@ -192,8 +187,8 @@ public class MainActivity extends AppCompatActivity
this.toLogoutState();
});
break;
}
}
DrawerLayout drawer = findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
@@ -214,11 +209,24 @@ public class MainActivity extends AppCompatActivity
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
Fragment fragment = mFragmentManager.findFragmentByTag(String.valueOf(fragmentPage));
savedInstanceState.putInt(ARG_FRAGMENT_PAGE, fragmentPage);
savedInstanceState.putString(ARG_FRAGMENT_STATUS, fragmentData);
if (fragment instanceof ModDetailFragment) {
savedInstanceState.putString(ARG_FRAGMENT_STATUS, ((ModDetailFragment) fragment).getData());
} else {
savedInstanceState.putString(ARG_FRAGMENT_STATUS, fragmentData);
}
super.onSaveInstanceState(savedInstanceState);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
for (RequestPermissionsResultListener listener: permissionListeners.values()) {
listener.callback(requestCode, permissions, grantResults);
}
}
/* --------------------------------------------*/
/* --------------------------------------------*/
/* --------------------------------------------*/
@@ -245,8 +253,19 @@ public class MainActivity extends AppCompatActivity
}
private int getDefaultFragmentAfterLogin() {
return getDefaultFragmentAfterLogin(new String[1]);
}
private int getDefaultFragmentAfterLogin(String[] id) {
if (fragmentPage == FRAGMENT_NONE){
id[0] = "";
return FRAGMENT_MODULES;
}
else {
id[0] = fragmentData;
return fragmentPage;
}
}
private void toLogoutState() {
changeFragment(FRAGMENT_LOGIN);
@@ -257,15 +276,16 @@ public class MainActivity extends AppCompatActivity
if (loginToken == null) {
toLogoutState();
} else {
toLoginState(loginToken.getFullname(), loginToken.getEmail(), newFragment, newData, true);
toLoginState(loginToken.getFullName(), loginToken.getEmail(), newFragment, newData, true);
}
}
private void toLoginState(String fullname, String email, int newFragment, String newData, boolean onlineMode) {
private void toLoginState(String fullName, String email, int newFragment, String newData, boolean onlineMode) {
log.d(currentPage, newFragment);
changeFragment(newFragment, newData);
View header = mNavigationView.getHeaderView(0);
((TextView) header.findViewById(R.id.login_name)).setText(fullname);
((TextView) header.findViewById(R.id.login_name)).setText(fullName);
((TextView) header.findViewById(R.id.login_mail)).setText(email);
setOfflineBanner(onlineMode);
}
@@ -277,7 +297,10 @@ public class MainActivity extends AppCompatActivity
toLogoutState();
return;
}
this.getKVV().login(credentials.getUsername(), credentials.getPassword(), success -> toLoginState(success, getDefaultFragmentAfterLogin(), ""),
String[] id = {""};
int fragment = getDefaultFragmentAfterLogin(id);
this.getKVV().login(credentials.getUsername(), credentials.getPassword(), success -> toLoginState(success, fragment , id[0]),
error -> {
log.e(error);
toLogoutState();
@@ -290,27 +313,35 @@ public class MainActivity extends AppCompatActivity
}
private void changeFragment(int newFragment, String newData) {
onTitleTextChange(R.string.courses);
if (mFragmentManager.isStateSaved())
return;
if (newFragment == FRAGMENT_CANTEENS_DETAILS && newData.equals(""))
newFragment = FRAGMENT_CANTEENS;
if (newFragment == FRAGMENT_MODULES_DETAILS && newData.equals(""))
newFragment = FRAGMENT_MODULES;
//log.d("changeFragment: ", newFragment, newData);
onTitleTextChange(R.string.app_name);
Fragment fragment;
switch (newFragment) {
case FRAGMENT_MODULES:
fragment = ModulesFragment.newInstance();
break;
case FRAGMENT_MODULES_DETAILS:
fragment = ModDetailFragment.newInstance(newData);
break;
case FRAGMENT_MODULES:
fragment = ModulesFragment.newInstance();
break;
case FRAGMENT_LOGIN:
fragment = LoginFragment.newInstance();
break;
case FRAGMENT_SCHEDULE:
fragment = ScheduleFragment.newInstance();
break;
case FRAGMENT_CANTEENS:
fragment = CanteensFragment.newInstance();
break;
case FRAGMENT_CANTEENS_DETAILS:
fragment = DaySwitcherFragment.newInstance(Integer.parseInt(newData));
break;
case FRAGMENT_CANTEENS:
fragment = CanteensFragment.newInstance();
break;
case FRAGMENT_PREFERENCES:
fragment = PrefsFragment.newInstance();
break;
@@ -329,7 +360,7 @@ public class MainActivity extends AppCompatActivity
findViewById(R.id.app_bar_layout).setVisibility(View.VISIBLE);
}
// switch to logout
if ((fragmentPage != FRAGMENT_STARTUP && fragmentPage != FRAGMENT_LOGIN) && (newFragment == FRAGMENT_STARTUP || newFragment == FRAGMENT_LOGIN)) {
if ((currentPage != FRAGMENT_STARTUP && currentPage != 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);
@@ -346,7 +377,10 @@ public class MainActivity extends AppCompatActivity
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)) {
else if (
(currentPage == FRAGMENT_STARTUP || currentPage == FRAGMENT_LOGIN || currentPage == FRAGMENT_NONE) &&
(newFragment != FRAGMENT_STARTUP && newFragment != FRAGMENT_LOGIN && getKVV().isLoggedIn())
) {
View header = mNavigationView.getHeaderView(0);
//header.findViewById(R.id.imageView).setVisibility(View.VISIBLE);
header.findViewById(R.id.login_name).setVisibility(View.VISIBLE);
@@ -391,8 +425,11 @@ public class MainActivity extends AppCompatActivity
}, log::e);
}
if (newFragment != FRAGMENT_STARTUP && newFragment != FRAGMENT_NONE && newFragment != FRAGMENT_LOGIN) {
this.fragmentPage = newFragment;
this.fragmentData = newData;
}
this.currentPage = newFragment;
invalidateOptionsMenu();
}
@@ -436,31 +473,68 @@ public class MainActivity extends AppCompatActivity
public void onLoginFragmentInteraction(LoginToken loginToken, boolean onlineMode) {
toLoginState(loginToken.getFullname(), loginToken.getEmail(), getDefaultFragmentAfterLogin(), "", onlineMode);
String[] id = {""};
int fragment = getDefaultFragmentAfterLogin(id);
toLoginState(loginToken.getFullName(), loginToken.getEmail(), fragment, id[0], onlineMode);
}
@Override
public void onModulesFragmentInteraction(final String itemID) {
changeFragment(FRAGMENT_MODULES_DETAILS, itemID);
}
@Override
public void onCanteensFragmentInteraction(final int itemID) {
changeFragment(FRAGMENT_CANTEENS_DETAILS, String.valueOf(itemID));
}
@Override
public void onTitleTextChange(String newTitle) {
setTitle(newTitle);
}
@Override
public void onTitleTextChange(@StringRes int titleId) {
setTitle(titleId);
}
public void loginTokenInvalid(boolean doPrecheck) {
@Override
public void loginTokenInvalid(boolean doLoginCheck) {
if (doLoginCheck) {
getKVV().testLogin(isSuccess -> {
if (!isSuccess) {
getKVV().invalidate();
checkAndDoLogin();
}
});
} else {
getKVV().invalidate();
checkAndDoLogin();
}
}
public void refreshFailed(boolean isFailed) {
@Override
public void onRefreshCompleted(boolean isFailed) {
setRefreshFailedBanner(isFailed);
}
@Override
public void addRequestPermissionsResultListener(RequestPermissionsResultListener listener, String id) {
permissionListeners.put(id, listener);
}
@Override
public void removeRequestPermissionsResultListener(String id) {
permissionListeners.remove(id);
}
@Override
public void showToast(@StringRes int msgStringRes) {
showToast(getString(msgStringRes));
}
@Override
public void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
}

View File

@@ -1,11 +1,11 @@
package de.sebse.fuplanner.fragments;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.fragments.CanteensFragment.OnCanteensFragmentInteractionListener;
import de.sebse.fuplanner.services.Canteen.types.Canteen;

View File

@@ -2,19 +2,19 @@ package de.sebse.fuplanner.fragments;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import de.sebse.fuplanner.MainActivity;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.Canteen.CanteenBrowser;
import de.sebse.fuplanner.tools.MainAcitivityListener;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger;
/**
@@ -87,8 +87,8 @@ public class CanteensFragment extends Fragment {
throw new RuntimeException(context.toString()
+ " must implement OnCanteensFragmentInteractionListener");
}
if (context instanceof MainAcitivityListener)
((MainAcitivityListener) context).onTitleTextChange(R.string.canteens);
if (context instanceof MainActivityListener)
((MainActivityListener) context).onTitleTextChange(R.string.canteens);
else
throw new RuntimeException(context.toString() + "must implement MainActivityListener");
}

View File

@@ -3,8 +3,6 @@ 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;
@@ -13,13 +11,15 @@ import android.widget.EditText;
import java.io.IOException;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
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.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger;
/**
@@ -33,6 +33,7 @@ import de.sebse.fuplanner.tools.logging.Logger;
public class LoginFragment extends Fragment {
private OnLoginFragmentInteractionListener mListener;
private final Logger log = new Logger(this);
private MainActivityListener mActivityListener;
public LoginFragment() {
// Required empty public constructor
@@ -96,10 +97,23 @@ public class LoginFragment extends Fragment {
kvv.login(username, password, success -> {
progressDialog.dismiss();
gauth.setLoginState(username, password);
if (mListener != null)
if (mListener != null) {
input_usr.setError(null);
input_pwd.setError(null);
mListener.onLoginFragmentInteraction(success, true);
}
}, error -> {
progressDialog.dismiss();
// Invalid password
if (mActivityListener != null) {
if (error.getCode() == 100131) {
mActivityListener.showToast(R.string.invalid_credentials);
input_usr.setError(input_usr.getResources().getString(R.string.invalid_credentials));
input_pwd.setError(input_pwd.getResources().getString(R.string.invalid_credentials));
} else {
mActivityListener.showToast(v.getResources().getString(R.string.error_occurred_code, error.getCode()));
}
}
log.e("Error on KVV login!", error);
});
}
@@ -120,9 +134,10 @@ public class LoginFragment extends Fragment {
throw new RuntimeException(context.toString()
+ " must implement OnLoginFragmentInteractionListener");
}
if (context instanceof MainAcitivityListener)
((MainAcitivityListener) context).onTitleTextChange(R.string.courses);
else
if (context instanceof MainActivityListener) {
mActivityListener = (MainActivityListener) context;
mActivityListener.onTitleTextChange(R.string.log_in);
} else
throw new RuntimeException(context.toString() + "must implement MainActivityListener");
}

View File

@@ -1,11 +1,11 @@
package de.sebse.fuplanner.fragments;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.fragments.ModulesFragment.OnModulesFragmentInteractionListener;
import de.sebse.fuplanner.services.KVV.types.Modules;

View File

@@ -2,19 +2,19 @@ package de.sebse.fuplanner.fragments;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import de.sebse.fuplanner.MainActivity;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.KVV;
import de.sebse.fuplanner.tools.MainAcitivityListener;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger;
/**
@@ -87,8 +87,8 @@ public class ModulesFragment extends Fragment {
throw new RuntimeException(context.toString()
+ " must implement OnModulesFragmentInteractionListener");
}
if (context instanceof MainAcitivityListener)
((MainAcitivityListener) context).onTitleTextChange(R.string.courses);
if (context instanceof MainActivityListener)
((MainActivityListener) context).onTitleTextChange(R.string.courses);
else
throw new RuntimeException(context.toString() + "must implement MainActivityListener");
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ import java.util.ArrayList;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.types.Announcement;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.DateUtils;
import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.ui.ItemViewHolder;
import de.sebse.fuplanner.tools.ui.StringViewHolder;
@@ -19,7 +19,7 @@ class ModDetailAnnounceAdapter extends BaseExpandableListAdapter {
private Modules.Module mModule = null;
@Override
public String getChild(int groupPosition, int childPosititon) {
public String getChild(int groupPosition, int childPosition) {
StringBuilder s = new StringBuilder(this.getGroup(groupPosition).getBody());
ArrayList<String> urls = this.getGroup(groupPosition).getUrls();
for (int j =0; j<urls.size(); j++){
@@ -89,7 +89,7 @@ class ModDetailAnnounceAdapter extends BaseExpandableListAdapter {
ItemViewHolder itemHolder = new ItemViewHolder(convertView);
itemHolder.mTitle.setText(announce.getTitle());
itemHolder.mSubLeft.setText(announce.getCreatedBy());
itemHolder.mSubRight.setText(DateUtils.getModifiedDateTime(parent.getContext(), announce.getCreatedOn()));
itemHolder.mSubRight.setText(UtilsDate.getModifiedDateTime(parent.getContext(), announce.getCreatedOn()));
return convertView;
}

View File

@@ -2,14 +2,14 @@ package de.sebse.fuplanner.fragments.moddetails;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import de.sebse.fuplanner.MainActivity;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.KVV;

View File

@@ -8,7 +8,7 @@ import android.widget.BaseExpandableListAdapter;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.types.Assignment;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.DateUtils;
import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.ui.ItemViewHolder;
import de.sebse.fuplanner.tools.ui.StringViewHolder;
@@ -17,7 +17,7 @@ class ModDetailAssignmentAdapter extends BaseExpandableListAdapter {
private Modules.Module mModule = null;
@Override
public String getChild(int groupPosition, int childPosititon) {
public String getChild(int groupPosition, int childPosition) {
StringBuilder sb = new StringBuilder();
sb.append(this.getGroup(groupPosition).getInstructions());
sb.append("\n\n");
@@ -92,7 +92,7 @@ class ModDetailAssignmentAdapter extends BaseExpandableListAdapter {
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()));
itemHolder.mSubRight.setText(UtilsDate.getModifiedDateTime(parent.getContext(), assignment.getDueDate()));
return convertView;
}

View File

@@ -2,14 +2,14 @@ package de.sebse.fuplanner.fragments.moddetails;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import de.sebse.fuplanner.MainActivity;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.KVV;

View File

@@ -1,21 +1,21 @@
package de.sebse.fuplanner.fragments.moddetails;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.types.Event;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.DateUtils;
import de.sebse.fuplanner.tools.ui.CustomViewHolder;
import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.ui.ItemViewHolder;
import de.sebse.fuplanner.tools.ui.StringViewHolder;
class ModDetailEventAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_HEADER = 0;
@@ -27,6 +27,8 @@ class ModDetailEventAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
private Modules.Module mValue;
private final ArrayList<Pair<Integer, Integer>> mPositionalData;
private final Logger log = new Logger(this);
ModDetailEventAdapter() {
mValue = null;
mPositionalData = new ArrayList<>();
@@ -61,7 +63,7 @@ class ModDetailEventAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
case TYPE_HEADER:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_caption, parent, false);
return new HeaderViewHolder(view);
return new StringViewHolder(view);
case TYPE_ITEM:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_items, parent, false);
@@ -87,13 +89,13 @@ class ModDetailEventAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
Pair<Integer, Integer> data = mPositionalData.get(position);
switch (data.first) {
case TYPE_HEADER:
HeaderViewHolder h = (HeaderViewHolder) holder;
StringViewHolder h = (StringViewHolder) holder;
switch (data.second) {
case SECTION_PAST:
h.mCaption.setText(R.string.past_events);
h.mString.setText(R.string.past_events);
break;
case SECTION_UPCOMING:
h.mCaption.setText(R.string.upcoming_events);
h.mString.setText(R.string.upcoming_events);
break;
}
break;
@@ -114,14 +116,14 @@ class ModDetailEventAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
i.mTitle.setText(event.getTitle());
i.mSubLeft.setText(event.getType());
String start, end;
if (DateUtils.dateEquals(event.getStartDate(), System.currentTimeMillis()))
start = DateUtils.getModifiedTime(i.mView.getContext(), event.getStartDate());
if (UtilsDate.dateEquals(event.getStartDate(), System.currentTimeMillis()))
start = UtilsDate.getModifiedTime(i.mView.getContext(), event.getStartDate());
else
start = DateUtils.getModifiedDateTime(i.mView.getContext(), event.getStartDate());
if (DateUtils.dateEquals(event.getStartDate(), event.getEndDate()))
end = DateUtils.getModifiedTime(i.mView.getContext(), event.getEndDate());
start = UtilsDate.getModifiedDateTime(i.mView.getContext(), event.getStartDate());
if (UtilsDate.dateEquals(event.getStartDate(), event.getEndDate()))
end = UtilsDate.getModifiedTime(i.mView.getContext(), event.getEndDate());
else
end = DateUtils.getModifiedDateTime(i.mView.getContext(), event.getEndDate());
end = UtilsDate.getModifiedDateTime(i.mView.getContext(), event.getEndDate());
i.mSubRight.setText(i.mView.getResources().getString(R.string.date_scale,
start, end
));
@@ -145,27 +147,4 @@ class ModDetailEventAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
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,15 +3,15 @@ package de.sebse.fuplanner.fragments.moddetails;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import de.sebse.fuplanner.MainActivity;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.KVV;

View File

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

View File

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

View File

@@ -3,15 +3,15 @@ package de.sebse.fuplanner.fragments.moddetails;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import de.sebse.fuplanner.MainActivity;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.KVV;

View File

@@ -1,27 +1,27 @@
package de.sebse.fuplanner.fragments.moddetails;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.ms.square.android.expandabletextview.ExpandableTextView;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.types.Announcement;
import de.sebse.fuplanner.services.KVV.types.Assignment;
import de.sebse.fuplanner.services.KVV.types.Event;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.DateUtils;
import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.ui.CustomViewHolder;
import de.sebse.fuplanner.tools.ui.ItemViewHolder;
import de.sebse.fuplanner.tools.ui.StringViewHolder;
class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int MAX_ITEMS_PER_PREVIEW = 2;
@@ -77,7 +77,7 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
case TYPE_HEADER:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_all_caption, parent, false);
return new HeaderViewHolder(view);
return new StringViewHolder(view);
case TYPE_DESCRIPTION:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_moddetails_description, parent, false);
@@ -111,19 +111,19 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
Pair<Integer, Object> data = mPositionalData.get(position);
switch (data.first) {
case TYPE_HEADER:
HeaderViewHolder h = (HeaderViewHolder) holder;
StringViewHolder h = (StringViewHolder) holder;
switch ((Integer) data.second) {
case ModulePart.DESCRIPTION:
h.mCaption.setText(R.string.description);
h.mString.setText(R.string.description);
break;
case ModulePart.ANNOUNCEMENT:
h.mCaption.setText(h.mView.getResources().getString(R.string.announcements_count, getAnnounceCount()));
h.mString.setText(h.mView.getResources().getString(R.string.announcements_count, getAnnounceCount()));
break;
case ModulePart.ASSIGNMENT:
h.mCaption.setText(h.mView.getResources().getString(R.string.assignments_count, getAssignmentCount()));
h.mString.setText(h.mView.getResources().getString(R.string.assignments_count, getAssignmentCount()));
break;
case ModulePart.EVENT:
h.mCaption.setText(h.mView.getResources().getString(R.string.upcoming_events_count, getEventsCount()));
h.mString.setText(h.mView.getResources().getString(R.string.upcoming_events_count, getEventsCount()));
break;
}
break;
@@ -141,7 +141,7 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
Announcement announce = mValue.announcements.get(index);
i.mTitle.setText(announce.getTitle());
i.mSubLeft.setText(announce.getCreatedBy());
i.mSubRight.setText(DateUtils.getModifiedDateTime(i.mView.getContext(), announce.getCreatedOn()));
i.mSubRight.setText(UtilsDate.getModifiedDateTime(i.mView.getContext(), announce.getCreatedOn()));
i.mView.setOnClickListener(view -> {
if (mListener != null) mListener.gotoFragmentPart(section, index);
});
@@ -154,7 +154,7 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
i.mSubLeft.setText(i.mView.getResources().getText(R.string.open));
else
i.mSubLeft.setText(i.mView.getResources().getText(R.string.closed));
i.mSubRight.setText(DateUtils.getModifiedDateTime(i.mView.getContext(), assignment.getDueDate()));
i.mSubRight.setText(UtilsDate.getModifiedDateTime(i.mView.getContext(), assignment.getDueDate()));
i.mView.setOnClickListener(view -> {
if (mListener != null) mListener.gotoFragmentPart(section, index);
});
@@ -165,14 +165,14 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
i.mTitle.setText(event.getTitle());
i.mSubLeft.setText(event.getType());
String start, end;
if (DateUtils.dateEquals(event.getStartDate(), System.currentTimeMillis()))
start = DateUtils.getModifiedTime(i.mView.getContext(), event.getStartDate());
if (UtilsDate.dateEquals(event.getStartDate(), System.currentTimeMillis()))
start = UtilsDate.getModifiedTime(i.mView.getContext(), event.getStartDate());
else
start = DateUtils.getModifiedDateTime(i.mView.getContext(), event.getStartDate());
if (DateUtils.dateEquals(event.getStartDate(), event.getEndDate()))
end = DateUtils.getModifiedTime(i.mView.getContext(), event.getEndDate());
start = UtilsDate.getModifiedDateTime(i.mView.getContext(), event.getStartDate());
if (UtilsDate.dateEquals(event.getStartDate(), event.getEndDate()))
end = UtilsDate.getModifiedTime(i.mView.getContext(), event.getEndDate());
else
end = DateUtils.getModifiedDateTime(i.mView.getContext(), event.getEndDate());
end = UtilsDate.getModifiedDateTime(i.mView.getContext(), event.getEndDate());
i.mSubRight.setText(i.mView.getResources().getString(R.string.date_scale,
start, end
));
@@ -221,19 +221,7 @@ class ModDetailOverviewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
class HeaderViewHolder extends CustomViewHolder {
final TextView mCaption;
HeaderViewHolder(View view) {
super(view);
mCaption = view.findViewById(R.id.caption);
}
@Override
public String toString() {
return super.toString() + " '" + mCaption.getText() + "'";
}
}
class DescriptionViewHolder extends CustomViewHolder {
final ExpandableTextView mText;

View File

@@ -3,16 +3,16 @@ package de.sebse.fuplanner.fragments.moddetails;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import 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.KVV;

View File

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

View File

@@ -0,0 +1,344 @@
package de.sebse.fuplanner.fragments.moddetails;
import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
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.KVV;
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.Regex;
import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.ui.treeview.DirectoryNodeBinder;
import de.sebse.fuplanner.tools.ui.treeview.FileNodeBinder;
import de.sebse.fuplanner.tools.ui.treeview.TreeNode;
import de.sebse.fuplanner.tools.ui.treeview.TreeViewAdapter;
import static androidx.core.content.ContextCompat.checkSelfPermission;
/**
* A simple {@link Fragment} subclass.
* Use the {@link ModDetailResourceFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class ModDetailResourceFragment extends Fragment {
private static final String ARG_POSITION = "itemPosition";
private String mItemPos;
private final Logger log = new Logger(this);
private ModDetailResourceAdapter adapter;
private SwipeRefreshLayout swipeLayout;
private MainActivityListener context;
private RequestedDownload requestedDownload;
public ModDetailResourceFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param itemPosition Item position in module list.
* @return A new instance of fragment ModDetailAnnounceFragment.
*/
public static ModDetailResourceFragment newInstance(String itemPosition) {
ModDetailResourceFragment fragment = new ModDetailResourceFragment();
Bundle args = new Bundle();
args.putString(ARG_POSITION, itemPosition);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mItemPos = getArguments().getString(ARG_POSITION);
}
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_recycler_view, container, false);
// Set the adapter
Context context = view.getContext();
RecyclerView recyclerView = view.findViewById(R.id.list);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
adapter = new ModDetailResourceAdapter(Arrays.asList(new FileNodeBinder(), new DirectoryNodeBinder()));
adapter.setOnTreeNodeListener(new TreeViewAdapter.OnTreeNodeListener() {
@Override
public boolean onClick(TreeNode node, RecyclerView.ViewHolder holder) {
if (!node.isLeaf()) {
// Update and toggle the node.
onToggle(!node.isExpand(), holder);
} else if(node.getContent() instanceof Resource.File) { // if leaf is file
KVV kvv = ModDetailResourceFragment.this.context.getKVV();
kvv.getModule(mItemPos, (Modules.Module module) -> {
if (getContext() == null)
return;
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext());
Resource.File file = (Resource.File) node.getContent();
String folderName = "FU-"+module.title.replaceAll("[:*<>|/\"\\\\]", "-");
File f = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS)+"/"+folderName+"/"+file.getTitle());
alertDialogBuilder
.setTitle(file.getTitle())
.setMessage(
getResources().getString(R.string.creator_name, file.getAuthor()) + "\n" +
getResources().getString(R.string.last_modified_on, UtilsDate.getModifiedDateTime(context, file.getModifiedDate()))
)
.setCancelable(true)
.setNeutralButton(R.string.close, (dialog, id) -> dialog.cancel())
.setPositiveButton(R.string.download, (dialog, id) -> download(file, folderName, true));
// if already downloaded, show open button
if (f.exists()) {
alertDialogBuilder
.setNegativeButton(R.string.openFile, (dialog, id) -> download(file, folderName, false));
}
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}, 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;
}
private void download(Resource.File file, String folderName, boolean downloadNew){
if (getActivity() == null) {
showDownloadError();
return;
}
if (checkSelfPermission(getActivity(), android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
// Access granted
downloadOrOpen(file, folderName, downloadNew);
} else {
this.requestedDownload = new RequestedDownload(file, folderName, downloadNew);
ActivityCompat.requestPermissions(ModDetailResourceFragment.super.getActivity(),
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
1);
}
}
private void downloadOrOpen(Resource.File file, String folderName, boolean downloadNew) {
KVV kvv = this.context.getKVV();
if(isExternalStorageWritable()){
kvv.getResourceFile(success1 -> {
// Downloading file failed
if (success1.equals("")){
showDownloadError();
}else {
if (Regex.has("^http", success1)){
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(success1));
startActivity(intent);
}
else {
fileOpen(new File(success1));
}
}
}, log::e, file.getTitle(), file.getUrl(), folderName, downloadNew);
}
}
private void showDownloadError() {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getContext());
alertDialogBuilder
.setTitle(R.string.ErrorFileDownload)
.setMessage(
R.string.ErrorFileDownloadText
)
.setCancelable(true)
.setNeutralButton(R.string.close, (dialog, id) -> dialog.cancel());
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof MainActivityListener) {
this.context = ((MainActivityListener) context);
this.context.addRequestPermissionsResultListener((requestCode, permissions, grantResults) -> {
if (requestedDownload == null) {
log.d("No request");
return;
}
if (getActivity() == null) {
showDownloadError();
return;
}
ArrayList<Integer> intList = new ArrayList<>();
for (int i : grantResults)
{
intList.add(i);
}
log.d(requestCode, Arrays.asList(permissions), intList);
int pos = Arrays.asList(permissions).indexOf("android.permission.WRITE_EXTERNAL_STORAGE");
if (pos != -1) {
if (grantResults[pos] != -1) {
downloadOrOpen(requestedDownload.file, requestedDownload.folderName, requestedDownload.downloadNew);
} else {
log.d(requestedDownload, pos, grantResults[pos]);
showDownloadError();
}
requestedDownload = null;
}
}, "ModDetailResourceFragment");
} else
throw new RuntimeException(context.toString() + " must implement MainActivityListener");
}
@Override
public void onDetach() {
super.onDetach();
this.context.removeRequestPermissionsResultListener("ModDetailResourceFragment");
}
/* Checks if external storage is available for read and write */
private boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
log.e("File system: Writing not possible");
return false;
}
private void fileOpen(File url){
Uri uri = Uri.fromFile(url);
Intent intent = new Intent();//Intent.ACTION_VIEW
// Check what kind of file you are trying to open, by comparing the url with extensions.
// When the if condition is matched, plugin sets the correct intent (mime) type,
// so Android knew what application to use to open the file
if (url.toString().contains(".doc") || url.toString().contains(".docx")) {
// Word document
intent.setDataAndType(uri, "application/msword");
} else if(url.toString().contains(".pdf")) {
// PDF file
intent.setDataAndType(uri, "application/pdf");
} else if(url.toString().contains(".ppt") || url.toString().contains(".pptx")) {
// Powerpoint file
intent.setDataAndType(uri, "application/vnd.ms-powerpoint");
} else if(url.toString().contains(".xls") || url.toString().contains(".xlsx")) {
// Excel file
intent.setDataAndType(uri, "application/vnd.ms-excel");
} else if(url.toString().contains(".zip") || url.toString().contains(".rar")) {
// ZIP file
intent.setDataAndType(uri, "application/zip");
} else if(url.toString().contains(".rtf")) {
// RTF file
intent.setDataAndType(uri, "application/rtf");
} else if(url.toString().contains(".wav") || url.toString().contains(".mp3")) {
// WAV audio file
intent.setDataAndType(uri, "audio/x-wav");
} else if(url.toString().contains(".gif")) {
// GIF file
intent.setDataAndType(uri, "image/gif");
} else if(url.toString().contains(".jpg") || url.toString().contains(".jpeg") || url.toString().contains(".png")) {
// JPG file
intent.setDataAndType(uri, "image/jpeg");
} else if(url.toString().contains(".txt")) {
// Text file
intent.setDataAndType(uri, "text/plain");
} else if(url.toString().contains(".3gp") || url.toString().contains(".mpg") || url.toString().contains(".mpeg") || url.toString().contains(".mpe") || url.toString().contains(".mp4") || url.toString().contains(".avi")) {
// Video files
intent.setDataAndType(uri, "video/*");
} else {
//if you want you can also define the intent type for any other file
//additionally use else clause below, to manage other unknown extensions
//in this case, Android will show all applications installed on the device
//so you can choose which application to use
intent.setDataAndType(uri, "*/*");
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
private void refresh(boolean forceRefresh) {
if (getActivity() != null) {
KVV kvv = ((MainActivity) getActivity()).getKVV();
kvv.getModule(mItemPos, (Modules.Module module) -> {
adapter.setModule(module);
kvv.getModuleResources(module, success1 -> {
adapter.setModule();
swipeLayout.setRefreshing(false);
}, error -> {
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
}, error -> {
swipeLayout.setRefreshing(false);
log.e(error);
}, forceRefresh);
}
}
private class RequestedDownload {
Resource.File file;
String folderName;
boolean downloadNew;
RequestedDownload(Resource.File file, String folderName, boolean downloadNew) {
this.file = file;
this.folderName = folderName;
this.downloadNew = downloadNew;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,8 +2,6 @@ package de.sebse.fuplanner.services.GoogleAuth;
import android.content.Intent;
import android.content.IntentSender;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.widget.Toast;
@@ -17,6 +15,9 @@ import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.api.ResolvableApiException;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import static android.app.Activity.RESULT_OK;
/**

View File

@@ -1,16 +1,17 @@
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 androidx.annotation.NonNull;
import de.sebse.fuplanner.services.KVV.types.LoginToken;
import de.sebse.fuplanner.services.KVV.types.Modules;
import de.sebse.fuplanner.tools.MainAcitivityListener;
import de.sebse.fuplanner.tools.MainActivityListener;
import de.sebse.fuplanner.tools.logging.Logger;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
@@ -24,14 +25,20 @@ public class KVV {
private boolean isLoginPending = true;
private final ArrayList<LastTokenCallback> updatingList;
private final HashMap<String, Object> addons = new HashMap<>();
private final MainAcitivityListener mListener;
private final MainActivityListener mListener;
private Logger log = new Logger(this);
public KVV(Context context) {
mListener = (MainAcitivityListener) context;
mListener = (MainActivityListener) context;
this.context = context;
this.updatingList = new ArrayList<>();
}
public boolean isLoggedIn() {
log.d(this.lastToken, new KVVLogin(this.context).easyLogin());
return this.lastToken != null;
}
public LoginToken easyLogin() {
KVVLogin login = new KVVLogin(this.context);
lastToken = login.easyLogin();
@@ -137,6 +144,18 @@ public class KVV {
getModulePart(modules -> modules.getResources(module, saveOnCallback(modules, callback, forceRefresh), errorOnCallback(error), forceRefresh));
}
public void getResourceFile(final NetworkCallback<String> callback, final NetworkErrorCallback error, String filename, String url, String moduleName, boolean downloadNew){
getModulePart(modules -> modules.getResourceFile(callback, errorOnCallback(error),filename, url, moduleName, downloadNew));
}
public void testLogin(BooleanFunction callback) {
getLastToken(lastToken -> {
TestLogin.testLogin(context, lastToken, success -> callback.apply(true), error -> callback.apply(false));
});
}
private void getModulePart(ModListFunction func) {
@@ -158,7 +177,7 @@ public class KVV {
e.printStackTrace();
}
if (forceRefresh)
mListener.refreshFailed(false);
mListener.onRefreshCompleted(false);
callback.onResponse(success);
});
}
@@ -166,9 +185,9 @@ public class KVV {
private NetworkErrorCallback errorOnCallback(NetworkErrorCallback errorCallback){
return (error -> {
if (error.getHttpStatus() == 401 || error.getHttpStatus() == 403)
mListener.loginTokenInvalid(false);
mListener.loginTokenInvalid(true);
else
mListener.refreshFailed(true);
mListener.onRefreshCompleted(true);
errorCallback.onError(error);
});
}
@@ -178,6 +197,11 @@ public class KVV {
void apply(KVVModuleList mod);
}
@FunctionalInterface
public interface BooleanFunction {
void apply(boolean isSuccess);
}
private void getLastToken(LastTokenCallback lastTokenCallback) {
if (this.isLoginPending) {
this.updatingList.add(lastTokenCallback);

View File

@@ -2,9 +2,6 @@ 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;
@@ -16,17 +13,17 @@ 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.TestLogin.testLogin;
/**
* 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;
super(context);
try {
this.loginToken = LoginToken.load(context);
} catch (IOException e) {
@@ -36,14 +33,14 @@ class KVVLogin extends HTTPService {
}
}
public LoginToken easyLogin() {
LoginToken easyLogin() {
return this.loginToken;
}
public void login(String username, String password, NetworkCallback<LoginToken> callback, NetworkErrorCallback errorCallback) {
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 -> {
testLogin(getContext(), this.loginToken, success -> callback.onResponse(this.loginToken), error -> {
this.loginToken = null;
login(username, password, callback, errorCallback);
});
@@ -54,19 +51,19 @@ class KVVLogin extends HTTPService {
} else {
doLogin(username, password, token -> {
this.loginToken = token;
testLogin(this.loginToken, success -> callback.onResponse(this.loginToken), errorCallback);
testLogin(getContext(), this.loginToken, success -> callback.onResponse(this.loginToken), errorCallback);
}, errorCallback);
}
}
public void deleteOffline() {
void deleteOffline() {
if (this.loginToken != null)
this.loginToken.delete(mContext);
this.loginToken.delete(getContext());
}
public void saveOffline() throws IOException {
void saveOffline() throws IOException {
if (this.loginToken != null)
this.loginToken.save(mContext);
this.loginToken.save(getContext());
}
@@ -92,21 +89,6 @@ class KVVLogin extends HTTPService {
}, error);
}
private void testLogin(LoginToken loginToken, NetworkCallback<LoginToken> callback, NetworkErrorCallback errorCallback) {
get(String.format("https://kvv.imp.fu-berlin.de/direct/profile/%s.json", loginToken.getUsername()), loginToken.getCookies(), response -> {
String body = response.getParsed();
try {
JSONObject json = new JSONObject(body);
String displayName = json.getString("displayName");
String email = json.getString("email");
loginToken.setAdditionals(displayName, email);
callback.onResponse(loginToken);
} catch (JSONException e) {
errorCallback.onError(new NetworkError(100201, 403, "Cannot parse profile!"));
}
}, error -> errorCallback.onError(new NetworkError(100200, error.networkResponse.statusCode, "Testing login failed!")));
}
/*
GET https://kvv.imp.fu-berlin.de/portal/login
-> JSESSIONID 5c10406f-588c-4c16-96e9-c80d115417de.tomcat1
@@ -233,10 +215,16 @@ class KVVLogin extends HTTPService {
cookies.put("ROUTEID", ROUTEID);
cookies.put("_idp_session", _idp_session);
get("https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO", cookies, response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(100143, -1, "Error on getting SAML response!"));
return;
}
HashMap<String, String> object = new HashMap<>();
Pattern pattern = Pattern.compile("ss&#x3a;mem&#x3a;([0-9a-f]+)");
Matcher matcher = pattern.matcher(response.getParsed());
Matcher matcher = pattern.matcher(body);
if (!matcher.find()) {
errorCallback.onError(new NetworkError(100142, -1, "Error on getting SAML response!"));
return;
@@ -244,7 +232,7 @@ class KVVLogin extends HTTPService {
object.put("RelayState", "ss:mem:"+matcher.group(1));
pattern = Pattern.compile("name=\"SAMLResponse\" value=\"([0-9a-zA-Z+]+=*)");
matcher = pattern.matcher(response.getParsed());
matcher = pattern.matcher(body);
if (!matcher.find()) {
errorCallback.onError(new NetworkError(100141, -1, "Error on getting SAML response!"));
return;

View File

@@ -1,14 +1,16 @@
package de.sebse.fuplanner.services.KVV;
import android.content.Context;
import android.os.Environment;
import android.text.Html;
import android.util.Pair;
import net.htmlparser.jericho.Source;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
@@ -18,20 +20,23 @@ 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.AssignmentList;
import de.sebse.fuplanner.services.KVV.types.Event;
import de.sebse.fuplanner.services.KVV.types.EventList;
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;
import de.sebse.fuplanner.tools.network.Result;
import static de.sebse.fuplanner.services.KVV.TestLogin.testLogin;
/**
* Created by sebastian on 29.10.17.
@@ -59,7 +64,7 @@ class KVVModuleList extends HTTPService {
public void getModuleList(final NetworkCallback<Modules> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
void getModuleList(final NetworkCallback<Modules> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
queueModuleDetails.add("list", () -> {
if (this.moduleList != null && !forceRefresh) {
callback.onResponse(this.moduleList);
@@ -108,7 +113,7 @@ class KVVModuleList extends HTTPService {
}
String type = site.getJSONObject("props").getString("kvv_coursetype");
String description = site.getString("description");
description = new Source(description).getRenderer().toString();
description = String.valueOf(Html.fromHtml(description));
String id = site.getString("id");
modules.addModule(semester, lvNumbers, title, lecturers, type, description, id);
}
@@ -123,24 +128,26 @@ class KVVModuleList extends HTTPService {
}
// Empty module *may be* because token is invalid -> check
if (modules.size() == 0)
testLogin(token, token -> callback.onResponse(modules), errorCallback);
testLogin(getContext(), 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) {
void deleteModulesOffline(Context context) {
if (this.moduleList != null)
this.moduleList.delete(context);
}
public void saveModulesOffline(Context context) throws IOException {
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);
void getModule(String id, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
this.getModuleList(success -> {
callback.onResponse(success.get(id));
}, errorCallback, forceRefresh);
}
@@ -148,7 +155,7 @@ class KVVModuleList extends HTTPService {
public void getModuleDetails(Modules.Module module, final NetworkCallback<Pair<Modules.Module, Boolean>> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
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);
@@ -188,7 +195,7 @@ class KVVModuleList extends HTTPService {
public void getAnnouncements(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
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);
@@ -224,13 +231,13 @@ class KVVModuleList extends HTTPService {
String id = site.getString("announcementId");
String title = site.getString("title");
String text = site.getString("body");
text = new Source(text).getRenderer().toString();
text = String.valueOf(Html.fromHtml(text));
String createdBy = site.getString("createdByDisplayName");
long createdOn = site.getLong("createdOn");
//PDFs links rausziehen
// Extract attachment links
JSONArray attachments = site.getJSONArray("attachments");
ArrayList<String> urls = new ArrayList<>();
for (int j =0; j<attachments.length(); j++){
@@ -247,7 +254,7 @@ class KVVModuleList extends HTTPService {
}
// Empty announcements *may be* because token is invalid -> check
if (announcements.size() == 0)
testLogin(token, token -> callback.onResponse(announcements), errorCallback);
testLogin(getContext(), token, token -> callback.onResponse(announcements), errorCallback);
else
callback.onResponse(announcements);
}, error -> errorCallback.onError(new NetworkError(101203, error.networkResponse.statusCode, "Cannot get announcements!")));
@@ -257,7 +264,7 @@ class KVVModuleList extends HTTPService {
public void getAssignments(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
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);
@@ -293,7 +300,7 @@ class KVVModuleList extends HTTPService {
String id = site.getString("id");
String title = site.getString("title");
String instructions = site.getString("instructions");
instructions = new Source(instructions).getRenderer().toString();
instructions = String.valueOf(Html.fromHtml(instructions));
long dueTime = site.getJSONObject("dueTime").getLong("time");
String gradebookItemName = site.optString("gradebookItemName", null);
String gradeScale = site.getString("gradeScale");
@@ -311,7 +318,7 @@ class KVVModuleList extends HTTPService {
}
// Empty assignments *may be* because token is invalid -> check
if (assignments.size() == 0)
testLogin(token, token -> callback.onResponse(assignments), errorCallback);
testLogin(getContext(), token, token -> callback.onResponse(assignments), errorCallback);
else
callback.onResponse(assignments);
}, error -> errorCallback.onError(new NetworkError(101303, error.networkResponse.statusCode, "Cannot get assignments!")));
@@ -324,7 +331,7 @@ class KVVModuleList extends HTTPService {
public void getEvents(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
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);
@@ -374,7 +381,7 @@ class KVVModuleList extends HTTPService {
events.sort();
// Empty events *may be* because token is invalid -> check
if (events.size() == 0)
testLogin(token, token -> callback.onResponse(events), errorCallback);
testLogin(getContext(), token, token -> callback.onResponse(events), errorCallback);
else
callback.onResponse(events);
}, error -> errorCallback.onError(new NetworkError(101403, error.networkResponse.statusCode, "Cannot get calendar entries!")));
@@ -384,7 +391,7 @@ class KVVModuleList extends HTTPService {
public void getGradebook(Modules.Module module, final NetworkCallback<Modules.Module> callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) {
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);
@@ -471,7 +478,16 @@ class KVVModuleList extends HTTPService {
String title = site.getString("title");
long modifiedDate = site.getLong("modifiedDate");
String url = site.getString("url");
resources.add(new Resource(author, title, modifiedDate, 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) {
@@ -479,38 +495,133 @@ class KVVModuleList extends HTTPService {
errorCallback.onError(new NetworkError(101602, 403, "Cannot parse resources!"));
return;
}
ArrayList<Resource> root = new ArrayList<>();
// Generate folder structure
for (Resource res: resources) {
if (!res.getContainer().equals("/content/group/")) {
if (res.getContainer().equals("/content/group/"+ID+"/")){
// if file in root folder
root.add(res);
} else {
// in sub folder
for (Resource res2: resources) {
if (res2.getUrl().endsWith(res.getContainer()) && res2 instanceof Resource.Folder) {
// Append File/Folder to list
((Resource.Folder) res2).add(res);
}
}
}
}
}
// Empty resources *may be* because token is invalid -> check
if (resources.size() == 0)
testLogin(token, token -> callback.onResponse(resources), errorCallback);
if (root.size() == 0)
testLogin(getContext(), token, token -> callback.onResponse(root), errorCallback);
else
callback.onResponse(resources);
callback.onResponse(root);
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!"));
void getResourceFile(final NetworkCallback<String> callback, final NetworkErrorCallback errorCallback, String Filename, String url, String moduleName, boolean downloadNew) {
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() && !downloadNew) {
callback.onResponse(f.getPath());
return;
}
}, error -> errorCallback.onError(new NetworkError(100200, error.networkResponse.statusCode, "Testing login failed!")));
}
getResourceFileUpgrade(Filename, url , moduleName, callback, errorCallback);
}
private void getResourceFileUpgrade(String filename, String url , String moduleName, final NetworkCallback<String> callback, final NetworkErrorCallback errorCallback) {
if (token == null) {
errorCallback.onError(new NetworkError(101701, 500, "Currently running in offline mode!"));
return;
}
get(url, token.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){
testLogin(getContext(), token, token -> {
if (isExternalStorageWritable()) {
// try to download file again
get(url, token.getCookies(), response2 -> {
String path = saveFileInDownloads(filename, response2, moduleName);
callback.onResponse(path);
}, error -> errorCallback.onError(new NetworkError(101705, error.networkResponse.statusCode, "Cannot get file!")));
} else {
errorCallback.onError(new NetworkError(101703, 403, "External storage not writable!"));
}
}, errorCallback);
} else if (isExternalStorageWritable()) {
String path = saveFileInDownloads(filename, response, moduleName);
callback.onResponse(path);
} else {
errorCallback.onError(new NetworkError(101704, 403, "External storage not writable!"));
}
}, error -> errorCallback.onError(new NetworkError(101702, error.networkResponse.statusCode, "Cannot get file!")));
}
/* Checks if external storage is available for read and write */
private boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
log.w("File system: Writing not possible!");
return false;
}
/* Checks if external storage is available to at least read */
private boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
log.w("File system: Reading not possible!");
return false;
}
private String saveFileInDownloads(String filename, Result fileResult, String moduleName) {
// Saves file in folder: DOWNLOADS/moduleName
File folder = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS), moduleName);
if (!folder.mkdir()) {
log.w( "Directory not created");
}
String path = "";
try {
// TODO check if enough storage space is available
FileOutputStream out = new FileOutputStream(folder.getPath()+"/"+filename);
out.write(fileResult.getBytes());
out.close();
path = folder.getPath()+"/"+filename;
} catch (Exception e) {
log.w("File not saved!");
e.printStackTrace();
}
return path;
}

View File

@@ -0,0 +1,38 @@
package de.sebse.fuplanner.services.KVV;
import android.content.Context;
import org.json.JSONException;
import org.json.JSONObject;
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;
final class TestLogin extends HTTPService {
private TestLogin(Context context) {
super(context);
}
static void testLogin(Context context, LoginToken loginToken, NetworkCallback<LoginToken> callback, NetworkErrorCallback errorCallback) {
new TestLogin(context).get(String.format("https://kvv.imp.fu-berlin.de/direct/profile/%s.json", loginToken.getUsername()), loginToken.getCookies(), response -> {
String body = response.getParsed();
if (body == null) {
errorCallback.onError(new NetworkError(100202, 403, "Testing login failed!"));
return;
}
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

@@ -25,7 +25,7 @@ public class Announcement implements Serializable {
return urls;
}
public String getId() {
private String getId() {
return id;
}

View File

@@ -7,8 +7,6 @@ public class Assignment implements Serializable {
private final String id;
private final String title;
private final long dueTime;
private final String gradebookItemName;
private final String gradeScale;
private final ArrayList<String> urls;
private final String instructions;
@@ -16,14 +14,12 @@ public class Assignment implements Serializable {
this.id = id;
this.title = title;
this.dueTime = dueTime;
this.gradebookItemName = gradebookItemName;
this.gradeScale = gradeScale;
this.urls = urls;
//this.grade = grade;
this.instructions = instructions;
}
public String getId() {
private String getId() {
return id;
}
@@ -52,6 +48,6 @@ public class Assignment implements Serializable {
return "ID: "+getId()+
"\nTitle: "+getTitle()+
"\nDue date: "+getDueDate()+
"\nInstuctions: "+getInstructions().substring(0, Math.min(getInstructions().length(), 100));
"\nInstructions: "+getInstructions().substring(0, Math.min(getInstructions().length(), 100));
}
}

View File

@@ -5,7 +5,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Lecturer implements Serializable {
private final String firstname;
private final String firstName;
private final String surname;
private final String mail;
@@ -15,26 +15,26 @@ public class Lecturer implements Serializable {
if (!matcher.find()) {
throw new NoSuchFieldException();
}
this.firstname = matcher.group(1);
this.firstName = matcher.group(1);
this.surname = matcher.group(2);
this.mail = matcher.group(3);
}
public String getFirstname() {
return firstname;
private String getFirstName() {
return firstName;
}
public String getSurname() {
private String getSurname() {
return surname;
}
public String getMail() {
private String getMail() {
return mail;
}
@Override
public String toString() {
return "First name: "+getFirstname()+
return "First name: "+ getFirstName()+
"\nSurname: "+getSurname()+
"\nMail: "+getMail();
}

View File

@@ -1,7 +1,6 @@
package de.sebse.fuplanner.services.KVV.types;
import android.content.Context;
import android.support.annotation.Nullable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -11,6 +10,8 @@ import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashMap;
import androidx.annotation.Nullable;
/**
* Created by sebastian on 29.10.17.
*/
@@ -23,7 +24,7 @@ public class LoginToken implements Serializable {
private final String shibsessionKey;
private final String shibsessionName;
private final String JSESSIONID;
private String fullname;
private String fullName;
private String email;
private long saveDate = 0;
@@ -58,8 +59,8 @@ public class LoginToken implements Serializable {
context.deleteFile(FILE_NAME);
}
public void setAdditionals(String fullname, String email) {
this.fullname = fullname;
public void setAdditionals(String fullName, String email) {
this.fullName = fullName;
this.email = email;
}
@@ -79,8 +80,8 @@ public class LoginToken implements Serializable {
return JSESSIONID;
}
public String getFullname() {
return fullname;
public String getFullName() {
return fullName;
}
public String getEmail() {

View File

@@ -1,8 +1,6 @@
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;
@@ -14,6 +12,9 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Created by sebastian on 29.10.17.
*/
@@ -103,9 +104,9 @@ public class Modules implements Iterable<Modules.Module>, Serializable {
public class Module implements Serializable {
public final String semester;
public final HashSet<String> lvNumber;
final HashSet<String> lvNumber;
public final String title;
public final HashSet<Lecturer> lecturer;
final HashSet<Lecturer> lecturer;
public final String type;
public final String description;
private final String ID;

View File

@@ -1,22 +1,31 @@
package de.sebse.fuplanner.services.KVV.types;
import java.io.Serializable;
import java.util.ArrayList;
import androidx.annotation.LayoutRes;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.tools.ui.treeview.LayoutItemType;
import de.sebse.fuplanner.tools.ui.treeview.TreeNode;
public abstract class Resource implements Serializable {
public class Resource implements Serializable {
private final String author;
private final long modifiedDate;
private final String title;
private final String url;
final String author;
final long modifiedDate;
final String title;
final String url;
private final boolean visible;
private final String container;
public Resource(String author, String title, long modifiedDate, String url) {
Resource(String author, String title, long modifiedDate, String url, boolean visible, String container) {
this.author = author;
this.title = title;
this.modifiedDate = modifiedDate;
this.url = url;
this.visible = visible;
this.container = container;
}
public String getAuthor() {
@@ -35,6 +44,24 @@ public class Resource implements Serializable {
return url;
}
public String getContainer() {
return container;
}
public boolean isVisible() {
return visible;
}
public abstract TreeNode getTreeNode();
public static class File extends Resource implements LayoutItemType {
private final String type;
public File(String author, String title, long modifiedDate, String url, boolean visible, String container, String type) {
super(author, title, modifiedDate, url, visible, container);
this.type = type;
}
@Override
public String toString() {
return "Resource{" +
@@ -42,9 +69,66 @@ public class Resource implements Serializable {
", modifiedDate=" + modifiedDate +
", title='" + title + '\'' +
", url='" + url + '\'' +
", type='" + type + '\'' +
'}';
}
@Override
public TreeNode getTreeNode() {
return new TreeNode<>(this);
}
@Override
public @LayoutRes int getLayoutId() {
return R.layout.item_file;
}
}
public static class Folder extends Resource implements LayoutItemType {
private final ArrayList<Resource> children;
public Folder(String author, String title, long modifiedDate, String url, boolean visible, String container) {
super(author, title, modifiedDate, url, visible, container);
children = new ArrayList<>();
}
public void add(Resource res){
children.add(res);
}
public Resource get(int id){
return children.get(id);
}
public int size(){
return children.size();
}
@Override
public String toString() {
return "Resource{" +
"author='" + author + '\'' +
", modifiedDate=" + modifiedDate +
", title='" + title + '\'' +
", url='" + url + '\'' +
", children='" + children + '\'' +
'}';
}
@Override
public TreeNode getTreeNode() {
TreeNode dir = new TreeNode<>(this);
for (Resource res: children) {
dir.addChild(res.getTreeNode());
}
return dir;
}
@Override
public @LayoutRes int getLayoutId() {
return R.layout.item_dir;
}
}
}

View File

@@ -43,15 +43,15 @@ public class SortedListModule extends SortedList<Modules.Module, String, String>
if (b == null)
return LARGER;
String s1type = Regex.regex("(S|WS)", a);
int s1year = Integer.parseInt(Regex.regex("(S|WS) ([0-9]{2})", a, 2));
String s2type = Regex.regex("(S|WS)", b);
int s2year = Integer.parseInt(Regex.regex("(S|WS) ([0-9]{2})", b, 2));
String s1type = Regex.regex("^(S|WS) ", a);
int s1year = Integer.parseInt(Regex.regex("^(S|WS) ([0-9]{2})", a, 2));
String s2type = Regex.regex("^(S|WS) ", b);
int s2year = Integer.parseInt(Regex.regex("^(S|WS) ([0-9]{2})", b, 2));
if (s1year == s2year) {
if (s1type.equals(s2type))
return EQUAL;
return s1type.equals("SS") ? SMALLER : LARGER;
return s1type.equals("S") ? SMALLER : LARGER;
}
return s1year < s2year ? SMALLER : LARGER;
}

View File

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

View File

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

View File

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

View File

@@ -31,8 +31,8 @@ public abstract class DateSortedList<T> extends ArrayList<T> {
@Override
public T get(int index) {
if (split < 0)
sort();
//if (split < 0)
// sort();
if (reversed())
index = size() - index - 1;
return super.get(index);

View File

@@ -1,23 +0,0 @@
package de.sebse.fuplanner.tools;
import android.support.annotation.StringRes;
import de.sebse.fuplanner.services.Canteen.CanteenBrowser;
import de.sebse.fuplanner.services.GoogleAuth.GoogleAuth;
import de.sebse.fuplanner.services.KVV.KVV;
public interface MainAcitivityListener {
void onTitleTextChange(String newTitle);
void onTitleTextChange(@StringRes int titleId);
KVV getKVV();
GoogleAuth getGoogleAuth();
void loginTokenInvalid(boolean doPrecheck);
void refreshFailed(boolean isFailed);
CanteenBrowser getCanteenBrowser();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,16 +4,14 @@ import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.annotation.Nullable;
import androidx.annotation.Nullable;
import android.text.format.DateFormat;
import com.google.android.gms.common.logging.Logger;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class DateUtils {
public class UtilsDate {
@Deprecated
public static String getModifiedDateTime(long modified) {
return getModifiedDateTime(null, modified);

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
package de.sebse.fuplanner.tools.network;
import android.content.Context;
import android.support.annotation.Nullable;
import com.android.volley.AuthFailureError;
import com.android.volley.NetworkResponse;
@@ -17,6 +16,7 @@ import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import androidx.annotation.Nullable;
import de.sebse.fuplanner.tools.logging.Logger;
/**
@@ -25,14 +25,12 @@ import de.sebse.fuplanner.tools.logging.Logger;
public class HTTPService {
private final RequestQueue requestQueue;
private final Context mContext;
protected Logger log = new Logger(this);
protected HTTPService(Context context) {
this(context, false);
}
protected HTTPService(Context context, boolean followRedirects) {
requestQueue = Volley.newRequestQueue(context, new BetterHurlStack(followRedirects));
this.mContext = context;
requestQueue = Volley.newRequestQueue(context, new BetterHurlStack(false));
}
protected void get(String url, @Nullable final HashMap<String, String> cookies, Response.Listener<Result> response, Response.ErrorListener error) {
@@ -40,14 +38,14 @@ public class HTTPService {
@Override
public void deliverError(VolleyError error) {
if (error == null) {
super.deliverError(new VolleyError(new NetworkResponse(500, null, null, true, 0)));
super.deliverError(new VolleyError(new NetworkResponse(500, null, true, 0, null)));
} else if (error.networkResponse == null) {
int statusCode;
if (error instanceof TimeoutError)
statusCode = 408;
else
statusCode = 500;
super.deliverError(new VolleyError(new NetworkResponse(statusCode, null, null, true, error.getNetworkTimeMs())));
super.deliverError(new VolleyError(new NetworkResponse(statusCode, null, true, error.getNetworkTimeMs(), null)));
} else {
final int status = error.networkResponse.statusCode;
if (status == 302) {
@@ -96,7 +94,6 @@ public class HTTPService {
sb.append('&');
}
try {
//Log.e("Superissimo", e.getKey()+"|||"+e.getValue());
sb.append(URLEncoder.encode(e.getKey(), "UTF-8")).append('=').append(URLEncoder.encode(e.getValue(), "UTF-8"));
} catch (UnsupportedEncodingException ignored) {
}
@@ -112,14 +109,14 @@ public class HTTPService {
@Override
public void deliverError(VolleyError error) {
if (error == null) {
super.deliverError(new VolleyError(new NetworkResponse(500, null, null, true, 0)));
super.deliverError(new VolleyError(new NetworkResponse(500, null, true, 0, null)));
} else if (error.networkResponse == null) {
int statusCode;
if (error instanceof TimeoutError)
statusCode = 408;
else
statusCode = 500;
super.deliverError(new VolleyError(new NetworkResponse(statusCode, null, null, true, error.getNetworkTimeMs())));
super.deliverError(new VolleyError(new NetworkResponse(statusCode, null, true, error.getNetworkTimeMs(), null)));
} else {
final int status = error.networkResponse.statusCode;
if (status == 302) {
@@ -149,4 +146,8 @@ public class HTTPService {
};
requestQueue.add(request);
}
protected Context getContext() {
return mContext;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,27 @@
package de.sebse.fuplanner.tools.ui;
import android.view.View;
import de.sebse.fuplanner.tools.ui.cardview.ExpandableCardView;
public class ExpandableCardViewHolder extends CustomViewHolder {
ExpandableCardViewHolder(View view) {
super(view);
}
public void reset() {
getView().reset();
}
private ExpandableCardView getView() {
return (ExpandableCardView) mView;
}
View getOuterView() {
return getView().getOuterView();
}
View getInnerView() {
return getView().getInnerView();
}
}

View File

@@ -9,7 +9,7 @@ public class ItemViewHolder extends CustomViewHolder {
public final TextView mTitle;
public final TextView mSubLeft;
public final TextView mSubRight;
public final TextView mTopRight;
private final TextView mTopRight;
public ItemViewHolder(View view) {
super(view);

View File

@@ -0,0 +1,39 @@
package de.sebse.fuplanner.tools.ui;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import de.sebse.fuplanner.R;
public class MealViewHolder extends ExpandableCardViewHolder {
public final TextView mTitle;
public final TextView mSubTitle;
public final TextView mCategory;
public final TextView mNotes;
public final ImageView mIconVegan;
public final ImageView mIconVegetarian;
public final ImageView mIconBio;
public final ImageView mIconMsc;
public MealViewHolder(View view) {
super(view);
View outerView = getOuterView();
View innerView = getInnerView();
mTitle = outerView.findViewById(R.id.title);
mSubTitle = outerView.findViewById(R.id.sub_title);
mNotes = innerView.findViewById(R.id.notes);
mCategory = innerView.findViewById(R.id.category);
mIconVegan = innerView.findViewById(R.id.icon_vegan);
mIconVegetarian = innerView.findViewById(R.id.icon_vegetarian);
mIconBio = innerView.findViewById(R.id.icon_organic);
mIconMsc = innerView.findViewById(R.id.icon_msc);
}
@Override
public String toString() {
return super.toString() + " '" + mTitle.getText() + "' '" + mSubTitle.getText() + "'";
}
}

View File

@@ -0,0 +1,368 @@
package de.sebse.fuplanner.tools.ui.cardview;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.view.animation.Transformation;
import android.widget.ImageButton;
import androidx.annotation.LayoutRes;
import androidx.annotation.Nullable;
import androidx.cardview.widget.CardView;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.tools.UtilsUi;
import de.sebse.fuplanner.tools.logging.Logger;
/**
*
* Copyright (c) 2018 Alessandro Sperotti
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
* Created by alessandros on 23/02/2018.
* @author Alessandro Sperotti
*/
public class ExpandableCardView extends CardView {
private int innerViewRes;
private int outerViewRes;
private View innerView;
private View outerView;
private ImageButton imageButton;
private static final int DEFAULT_ANIM_DURATION = 350;
private long animDuration = DEFAULT_ANIM_DURATION;
private final static int COLLAPSING = 0;
private final static int EXPANDING = 1;
private boolean isExpanded = false;
private boolean isExpanding = false;
private boolean isCollapsing = false;
private boolean startExpanded = false;
private int collapsedHeight = 0;
private int expandedHeight = 0;
private OnExpandedListener listener;
private OnClickListener defaultClickListener = v -> {
if(isExpanded()) collapse();
else expand();
};
private Logger log = new Logger(this);
public ExpandableCardView(Context context) {
super(context);
initView(context);
}
public ExpandableCardView(Context context, AttributeSet attrs) {
super(context, attrs);
initAttributes(context, attrs);
initView(context);
}
public ExpandableCardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttributes(context, attrs);
initView(context);
}
private void initView(Context context){
//Inflating View
imageButton = new ImageButton(context);
imageButton.setImageDrawable(getResources().getDrawable(R.drawable.arrow_down));
imageButton.setPadding(
(int) UtilsUi.convertDpToPixels(getContext(), 10),
(int) UtilsUi.convertDpToPixels(getContext(), 10),
(int) UtilsUi.convertDpToPixels(getContext(), 10),
(int) UtilsUi.convertDpToPixels(getContext(), 10)
);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
imageButton.setBackgroundResource(outValue.resourceId);
}
isExpanded = startExpanded;
}
private void initAttributes(Context context, AttributeSet attrs){
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ExpandableCardView);
innerViewRes = typedArray.getResourceId(R.styleable.ExpandableCardView_inner_view, View.NO_ID);
outerViewRes = typedArray.getResourceId(R.styleable.ExpandableCardView_outer_view, View.NO_ID);
animDuration = typedArray.getInteger(R.styleable.ExpandableCardView_animationDuration, DEFAULT_ANIM_DURATION);
startExpanded = typedArray.getBoolean(R.styleable.ExpandableCardView_startExpanded, false);
typedArray.recycle();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
innerView = inflateChild(innerViewRes);
outerView = inflateChild(outerViewRes);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setElevation(UtilsUi.convertDpToPixels(getContext(), 4));
}
setOnClickListener(defaultClickListener);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
removeAllViews();
int x = getPaddingLeft();
int y = getPaddingTop();
addView(outerView);
outerView.layout(x, y, x+outerView.getMeasuredWidth(), y+outerView.getMeasuredHeight());
addView(imageButton);
imageButton.layout(
getMeasuredWidth() - getPaddingRight() - imageButton.getMeasuredWidth(),
y,
getMeasuredWidth() - getPaddingRight(),
y+imageButton.getMeasuredHeight()
);
addView(innerView);
innerView.layout(x, y+outerView.getMeasuredHeight(), x+innerView.getMeasuredWidth(), y+outerView.getMeasuredHeight()+innerView.getMeasuredHeight());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (MeasureSpec.getSize(widthMeasureSpec) == 0 && MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED || MeasureSpec.getSize(heightMeasureSpec) == 0 && MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.UNSPECIFIED) {
log.w("This should not happen! Invalid dimension size");
setMeasuredDimension(reconcileSize(10, widthMeasureSpec), reconcileSize(10, heightMeasureSpec));
return;
}
int desiredWidth;
int desiredHeight;
int widthMeasure = atMostSpec(MeasureSpec.getSize(widthMeasureSpec), widthMeasureSpec);
int heightMeasure = atMostSpec(MeasureSpec.getSize(heightMeasureSpec), heightMeasureSpec);
imageButton.measure(widthMeasure, heightMeasure);
widthMeasure = atMostExactlySpec(Math.max(0, MeasureSpec.getSize(widthMeasureSpec)-imageButton.getMeasuredWidth()), widthMeasureSpec);
heightMeasure = atMostSpec(MeasureSpec.getSize(heightMeasureSpec), heightMeasureSpec);
outerView.measure(widthMeasure, heightMeasure);
desiredWidth = imageButton.getMeasuredWidth() + outerView.getMeasuredWidth();
desiredHeight = Math.max(imageButton.getMeasuredHeight(), outerView.getMeasuredHeight());
widthMeasure = atMostSpec(MeasureSpec.getSize(widthMeasureSpec), widthMeasureSpec);
heightMeasure = atMostSpec(Math.max(0, MeasureSpec.getSize(heightMeasureSpec)-desiredHeight), heightMeasureSpec);
innerView.measure(widthMeasure, heightMeasure);
desiredWidth = Math.max(desiredWidth, innerView.getMeasuredWidth());
desiredHeight += innerView.getMeasuredHeight();
desiredWidth += getPaddingLeft() + getPaddingRight();
desiredHeight += getPaddingTop() + getPaddingBottom();
if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {
expandedHeight = desiredHeight;
collapsedHeight = desiredHeight - innerView.getMeasuredHeight();
}
setMeasuredDimension(reconcileSize(desiredWidth, widthMeasureSpec), reconcileSize(isExpanded ? expandedHeight : collapsedHeight, heightMeasureSpec));
}
private void expand() {
final int initialHeight = this.getHeight();
int targetHeight = expandedHeight;
if(targetHeight - initialHeight != 0) {
animateViews(initialHeight, targetHeight - initialHeight, EXPANDING);
}
}
private void collapse() {
final int initialHeight = this.getHeight();
int targetHeight = collapsedHeight;
if(initialHeight - targetHeight != 0) {
animateViews(initialHeight, initialHeight - targetHeight, COLLAPSING);
}
}
private boolean isExpanded() {
return isExpanded;
}
private void animateViews(final int initialHeight, final int distance, final int animationType){
Animation expandAnimation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (interpolatedTime == 1) {
//Setting isExpanding/isCollapsing to false
isExpanding = false;
isCollapsing = false;
if (listener != null) {
if (animationType == EXPANDING)
listener.onExpandChanged(ExpandableCardView.this, true);
else
listener.onExpandChanged(ExpandableCardView.this, false);
}
}
ExpandableCardView.this.getLayoutParams().height = animationType == EXPANDING
? (int) (initialHeight + (distance * interpolatedTime))
: (int) (initialHeight - (distance * interpolatedTime));
ExpandableCardView.this.requestLayout();
}
@Override
public boolean willChangeBounds() {
return true;
}
};
RotateAnimation arrowAnimation = animationType == EXPANDING ?
new RotateAnimation(0,180,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f) :
new RotateAnimation(180,0,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
arrowAnimation.setFillAfter(true);
arrowAnimation.setDuration(animDuration);
expandAnimation.setDuration(animDuration);
isExpanding = animationType == EXPANDING;
isCollapsing = animationType == COLLAPSING;
startAnimation(expandAnimation);
imageButton.startAnimation(arrowAnimation);
isExpanded = animationType == EXPANDING;
}
private boolean isExpanding(){
return isExpanding;
}
private boolean isCollapsing(){
return isCollapsing;
}
private boolean isMoving(){
return isExpanding() || isCollapsing();
}
public void setOnExpandedListener(OnExpandedListener listener) {
this.listener = listener;
}
public void removeOnExpandedListener(){
this.listener = null;
}
private View inflateChild(@LayoutRes int resId) {
return inflate(getContext(), resId, null);
}
public View getOuterView() {
return outerView;
}
public View getInnerView() {
return innerView;
}
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
if(imageButton != null) imageButton.setOnClickListener(l);
super.setOnClickListener(l);
}
private long getAnimDuration() {
return animDuration;
}
private void setAnimDuration(long animDuration) {
this.animDuration = animDuration;
}
private int reconcileSize(int contentSize, int measureSpec) {
final int mode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
switch(mode) {
case MeasureSpec.EXACTLY:
return specSize;
case MeasureSpec.AT_MOST:
if (contentSize < specSize) {
return contentSize;
} else {
log.w("Your content may be cropped!");
return specSize;
}
case MeasureSpec.UNSPECIFIED:
default:
return contentSize;
}
}
private int atMostSpec(int atMostSpace, int measureSpec) {
if (MeasureSpec.getMode(measureSpec) == MeasureSpec.UNSPECIFIED)
return measureSpec;
else
return MeasureSpec.makeMeasureSpec(atMostSpace, MeasureSpec.AT_MOST);
}
private int atMostExactlySpec(int atMostExactlySpace, int measureSpec) {
if (MeasureSpec.getMode(measureSpec) == MeasureSpec.UNSPECIFIED)
return measureSpec;
else
return MeasureSpec.makeMeasureSpec(atMostExactlySpace, MeasureSpec.getMode(measureSpec));
}
public void reset() {
long anim = getAnimDuration();
this.setAnimDuration(0);
if (startExpanded) {
this.expand();
} else {
this.collapse();
}
this.setAnimDuration(anim);
}
/**
* Interfaces
*/
interface OnExpandedListener {
void onExpandChanged(View v, boolean isExpanded);
}
}

View File

@@ -0,0 +1,56 @@
package de.sebse.fuplanner.tools.ui.treeview;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.types.Resource;
/**
* Created by tlh on 2016/10/1 :)
*/
public class DirectoryNodeBinder extends TreeViewBinder<DirectoryNodeBinder.ViewHolder> {
@Override
public ViewHolder provideViewHolder(View itemView) {
return new ViewHolder(itemView);
}
@Override
public void bindView(ViewHolder holder, int position, TreeNode node) {
holder.ivArrow.setRotation(0);
holder.ivArrow.setImageResource(R.drawable.ic_keyboard_arrow_right_black_18dp);
int rotateDegree = node.isExpand() ? 90 : 0;
holder.ivArrow.setRotation(rotateDegree);
Resource.Folder dirNode = (Resource.Folder) node.getContent();
holder.tvName.setText(dirNode.getTitle());
if (node.isLeaf())
holder.ivArrow.setVisibility(View.INVISIBLE);
else holder.ivArrow.setVisibility(View.VISIBLE);
}
@Override
public int getLayoutId() {
return R.layout.item_dir;
}
public static class ViewHolder extends TreeViewBinder.ViewHolder {
private ImageView ivArrow;
private TextView tvName;
ViewHolder(View rootView) {
super(rootView);
this.ivArrow = rootView.findViewById(R.id.iv_arrow);
this.tvName = rootView.findViewById(R.id.tv_name);
}
public ImageView getIvArrow() {
return ivArrow;
}
public TextView getTvName() {
return tvName;
}
}
}

View File

@@ -0,0 +1,39 @@
package de.sebse.fuplanner.tools.ui.treeview;
import android.view.View;
import android.widget.TextView;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.KVV.types.Resource;
/**
* Created by tlh on 2016/10/1 :)
*/
public class FileNodeBinder extends TreeViewBinder<FileNodeBinder.ViewHolder> {
@Override
public ViewHolder provideViewHolder(View itemView) {
return new ViewHolder(itemView);
}
@Override
public void bindView(ViewHolder holder, int position, TreeNode node) {
Resource.File fileNode = (Resource.File) node.getContent();
holder.tvName.setText(fileNode.getTitle());
}
@Override
public int getLayoutId() {
return R.layout.item_file;
}
public class ViewHolder extends TreeViewBinder.ViewHolder {
TextView tvName;
ViewHolder(View rootView) {
super(rootView);
this.tvName = rootView.findViewById(R.id.tv_name);
}
}
}

View File

@@ -0,0 +1,9 @@
package de.sebse.fuplanner.tools.ui.treeview;
/**
* Created by tlh on 2016/10/1 :)
*/
public interface LayoutItemType {
int getLayoutId();
}

View File

@@ -0,0 +1,147 @@
package de.sebse.fuplanner.tools.ui.treeview;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
/**
* Created by tlh on 2016/10/1 :)
*/
public class TreeNode<T extends LayoutItemType> implements Cloneable {
private T content;
private TreeNode parent;
private List<TreeNode> childList;
private boolean isExpand;
private boolean isLocked;
//the tree high
private int height = UNDEFINE;
private static final int UNDEFINE = -1;
public TreeNode(@NonNull T content) {
this.content = content;
this.childList = new ArrayList<>();
}
public int getHeight() {
if (isRoot())
height = 0;
else if (height == UNDEFINE)
height = parent.getHeight() + 1;
return height;
}
public boolean isRoot() {
return parent == null;
}
public boolean isLeaf() {
return childList == null || childList.isEmpty();
}
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
public List<TreeNode> getChildList() {
return childList;
}
public void setChildList(List<TreeNode> childList) {
this.childList.clear();
for (TreeNode treeNode : childList) {
addChild(treeNode);
}
}
public void addChild(TreeNode node) {
if (childList == null)
childList = new ArrayList<>();
childList.add(node);
node.parent = this;
}
public void toggle() {
isExpand = !isExpand;
}
public void collapse() {
if (isExpand) {
isExpand = false;
}
}
private void collapseAll() {
if (childList == null || childList.isEmpty()) {
return;
}
for (TreeNode child : this.childList) {
child.collapseAll();
}
}
private void expand() {
if (!isExpand) {
isExpand = true;
}
}
private void expandAll() {
expand();
if (childList == null || childList.isEmpty()) {
return;
}
for (TreeNode child : this.childList) {
child.expandAll();
}
}
public boolean isExpand() {
return isExpand;
}
public void setParent(TreeNode parent) {
this.parent = parent;
}
public TreeNode getParent() {
return parent;
}
public TreeNode<T> lock() {
isLocked = true;
return this;
}
public TreeNode<T> unlock() {
isLocked = false;
return this;
}
public boolean isLocked() {
return isLocked;
}
@Override
public String toString() {
return "TreeNode{" +
"content=" + this.content +
", parent=" + (parent == null ? "null" : parent.getContent().toString()) +
", childList=" + (childList == null ? "null" : childList.toString()) +
", isExpand=" + isExpand +
'}';
}
@Override
protected TreeNode<T> clone() {
TreeNode<T> clone = new TreeNode<>(this.content);
clone.isExpand = this.isExpand;
return clone;
}
}

View File

@@ -0,0 +1,317 @@
package de.sebse.fuplanner.tools.ui.treeview;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
/**
* Created by tlh on 2016/10/1 :)
*/
public class TreeViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String KEY_IS_EXPAND = "IS_EXPAND";
private final List<? extends TreeViewBinder> viewBinders;
private List<TreeNode> displayNodes;
private int padding = 30;
private OnTreeNodeListener onTreeNodeListener;
private boolean toCollapseChild;
protected TreeViewAdapter(List<? extends TreeViewBinder> viewBinders) {
this(null, viewBinders);
}
private TreeViewAdapter(List<TreeNode> nodes, List<? extends TreeViewBinder> viewBinders) {
displayNodes = new ArrayList<>();
if (nodes != null)
findDisplayNodes(nodes);
this.viewBinders = viewBinders;
}
/**
* 从nodes的结点中寻找展开了的非叶结点添加到displayNodes中。
*
* @param nodes 基准点
*/
private void findDisplayNodes(List<TreeNode> nodes) {
for (TreeNode node : nodes) {
displayNodes.add(node);
if (!node.isLeaf() && node.isExpand())
findDisplayNodes(node.getChildList());
}
}
@Override
public int getItemViewType(int position) {
return displayNodes.get(position).getContent().getLayoutId();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(viewType, parent, false);
if (viewBinders.size() == 1)
return viewBinders.get(0).provideViewHolder(v);
for (TreeViewBinder viewBinder : viewBinders) {
if (viewBinder.getLayoutId() == viewType)
return viewBinder.provideViewHolder(v);
}
return viewBinders.get(0).provideViewHolder(v);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List<Object> payloads) {
if (payloads != null && !payloads.isEmpty()) {
Bundle b = (Bundle) payloads.get(0);
for (String key : b.keySet()) {
switch (key) {
case KEY_IS_EXPAND:
if (onTreeNodeListener != null)
onTreeNodeListener.onToggle(b.getBoolean(key), holder);
break;
}
}
}
super.onBindViewHolder(holder, position, payloads);
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
holder.itemView.setPadding(displayNodes.get(position).getHeight() * padding, 3, 3, 3);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TreeNode selectedNode = displayNodes.get(holder.getLayoutPosition());
// Prevent multi-click during the short interval.
try {
long lastClickTime = (long) holder.itemView.getTag();
if (System.currentTimeMillis() - lastClickTime < 500)
return;
} catch (Exception e) {
holder.itemView.setTag(System.currentTimeMillis());
}
holder.itemView.setTag(System.currentTimeMillis());
if (onTreeNodeListener != null && onTreeNodeListener.onClick(selectedNode, holder))
return;
if (selectedNode.isLeaf())
return;
// This TreeNode was locked to click.
if (selectedNode.isLocked()) return;
boolean isExpand = selectedNode.isExpand();
int positionStart = displayNodes.indexOf(selectedNode) + 1;
if (!isExpand) {
notifyItemRangeInserted(positionStart, addChildNodes(selectedNode, positionStart));
} else {
notifyItemRangeRemoved(positionStart, removeChildNodes(selectedNode, true));
}
}
});
for (TreeViewBinder viewBinder : viewBinders) {
if (viewBinder.getLayoutId() == displayNodes.get(position).getContent().getLayoutId())
viewBinder.bindView(holder, position, displayNodes.get(position));
}
}
private int addChildNodes(TreeNode pNode, int startIndex) {
List<TreeNode> childList = pNode.getChildList();
int addChildCount = 0;
for (TreeNode treeNode : childList) {
displayNodes.add(startIndex + addChildCount++, treeNode);
if (treeNode.isExpand()) {
addChildCount += addChildNodes(treeNode, startIndex + addChildCount);
}
}
if (!pNode.isExpand())
pNode.toggle();
return addChildCount;
}
private int removeChildNodes(TreeNode pNode) {
return removeChildNodes(pNode, true);
}
private int removeChildNodes(TreeNode pNode, boolean shouldToggle) {
if (pNode.isLeaf())
return 0;
List<TreeNode> childList = pNode.getChildList();
int removeChildCount = childList.size();
displayNodes.removeAll(childList);
for (TreeNode child : childList) {
if (child.isExpand()) {
if (toCollapseChild)
child.toggle();
removeChildCount += removeChildNodes(child, false);
}
}
if (shouldToggle)
pNode.toggle();
return removeChildCount;
}
@Override
public int getItemCount() {
return displayNodes == null ? 0 : displayNodes.size();
}
public void setPadding(int padding) {
this.padding = padding;
}
public void ifCollapseChildWhileCollapseParent(boolean toCollapseChild) {
this.toCollapseChild = toCollapseChild;
}
public void setOnTreeNodeListener(OnTreeNodeListener onTreeNodeListener) {
this.onTreeNodeListener = onTreeNodeListener;
}
public interface OnTreeNodeListener {
/**
* called when TreeNodes were clicked.
* @return weather consume the click event.
*/
boolean onClick(TreeNode node, RecyclerView.ViewHolder holder);
/**
* called when TreeNodes were toggle.
* @param isExpand the status of TreeNodes after being toggled.
*/
void onToggle(boolean isExpand, RecyclerView.ViewHolder holder);
}
protected void refresh(List<TreeNode> treeNodes) {
displayNodes.clear();
findDisplayNodes(treeNodes);
notifyDataSetChanged();
}
public Iterator<TreeNode> getDisplayNodesIterator() {
return displayNodes.iterator();
}
private void notifyDiff(final List<TreeNode> temp) {
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
@Override
public int getOldListSize() {
return temp.size();
}
@Override
public int getNewListSize() {
return displayNodes.size();
}
// judge if the same items
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return TreeViewAdapter.this.areItemsTheSame(temp.get(oldItemPosition), displayNodes.get(newItemPosition));
}
// if they are the same items, whether the contents has bean changed.
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return TreeViewAdapter.this.areContentsTheSame(temp.get(oldItemPosition), displayNodes.get(newItemPosition));
}
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
return TreeViewAdapter.this.getChangePayload(temp.get(oldItemPosition), displayNodes.get(newItemPosition));
}
});
diffResult.dispatchUpdatesTo(this);
}
private Object getChangePayload(TreeNode oldNode, TreeNode newNode) {
Bundle diffBundle = new Bundle();
if (newNode.isExpand() != oldNode.isExpand()) {
diffBundle.putBoolean(KEY_IS_EXPAND, newNode.isExpand());
}
if (diffBundle.size() == 0)
return null;
return diffBundle;
}
// For DiffUtil, if they are the same items, whether the contents has bean changed.
private boolean areContentsTheSame(TreeNode oldNode, TreeNode newNode) {
return oldNode.getContent() != null && oldNode.getContent().equals(newNode.getContent())
&& oldNode.isExpand() == newNode.isExpand();
}
// judge if the same item for DiffUtil
private boolean areItemsTheSame(TreeNode oldNode, TreeNode newNode) {
return oldNode.getContent() != null && oldNode.getContent().equals(newNode.getContent());
}
/**
* collapse all root nodes.
*/
public void collapseAll() {
// Back up the nodes are displaying.
List<TreeNode> temp = backupDisplayNodes();
//find all root nodes.
List<TreeNode> roots = new ArrayList<>();
for (TreeNode displayNode : displayNodes) {
if (displayNode.isRoot())
roots.add(displayNode);
}
//Close all root nodes.
for (TreeNode root : roots) {
if (root.isExpand())
removeChildNodes(root);
}
notifyDiff(temp);
}
@NonNull
private List<TreeNode> backupDisplayNodes() {
List<TreeNode> temp = new ArrayList<>();
for (TreeNode displayNode : displayNodes) {
temp.add(displayNode.clone());
}
return temp;
}
public void collapseNode(TreeNode pNode) {
List<TreeNode> temp = backupDisplayNodes();
removeChildNodes(pNode);
notifyDiff(temp);
}
public void collapseBrotherNode(TreeNode pNode) {
List<TreeNode> temp = backupDisplayNodes();
if (pNode.isRoot()) {
List<TreeNode> roots = new ArrayList<>();
for (TreeNode displayNode : displayNodes) {
if (displayNode.isRoot())
roots.add(displayNode);
}
//Close all root nodes.
for (TreeNode root : roots) {
if (root.isExpand() && !root.equals(pNode))
removeChildNodes(root);
}
} else {
TreeNode parent = pNode.getParent();
if (parent == null)
return;
List<TreeNode> childList = parent.getChildList();
for (TreeNode node : childList) {
if (node.equals(pNode) || !node.isExpand())
continue;
removeChildNodes(node);
}
}
notifyDiff(temp);
}
}

View File

@@ -0,0 +1,25 @@
package de.sebse.fuplanner.tools.ui.treeview;
import android.view.View;
import androidx.annotation.IdRes;
import androidx.recyclerview.widget.RecyclerView;
public abstract class TreeViewBinder<VH extends RecyclerView.ViewHolder> implements LayoutItemType {
public abstract VH provideViewHolder(View itemView);
public abstract void bindView(VH holder, int position, TreeNode node);
public static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View rootView) {
super(rootView);
}
protected <T extends View> T findViewById(@IdRes int id) {
//noinspection unchecked
return (T) itemView.findViewById(id);
}
}
}

View File

@@ -1,6 +1,6 @@
package de.sebse.fuplanner.tools.ui.weekview;
import android.support.annotation.ColorInt;
import androidx.annotation.ColorInt;

View File

@@ -9,16 +9,10 @@ import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.animation.FastOutLinearInInterpolator;
import android.text.Layout;
import android.text.SpannableStringBuilder;
import android.text.StaticLayout;
@@ -45,9 +39,15 @@ import java.util.Collections;
import java.util.List;
import java.util.Locale;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.view.GestureDetectorCompat;
import androidx.core.view.ViewCompat;
import androidx.interpolator.view.animation.FastOutLinearInInterpolator;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.tools.ColorRes;
import de.sebse.fuplanner.tools.DateUtils;
import de.sebse.fuplanner.tools.UtilsDate;
import de.sebse.fuplanner.tools.UtilsUi;
import de.sebse.fuplanner.tools.logging.Logger;
import static de.sebse.fuplanner.tools.ui.weekview.WeekViewUtil.daysBetween;
@@ -62,7 +62,6 @@ import static de.sebse.fuplanner.tools.ui.weekview.WeekViewUtil.today;
*/
@SuppressWarnings("unused")
public class WeekView extends View {
private enum Direction {
NONE, LEFT, RIGHT, VERTICAL
}
@@ -361,8 +360,8 @@ public class WeekView extends View {
selectedTime.set(Calendar.HOUR_OF_DAY, mMinTime);
selectedTime.set(Calendar.MINUTE, 0);
}
int unroundedMinutes = selectedTime.get(Calendar.MINUTE);
int mod = unroundedMinutes % mNewEventTimeResolutionInMinutes;
int nonRoundedMinutes = selectedTime.get(Calendar.MINUTE);
int mod = nonRoundedMinutes % mNewEventTimeResolutionInMinutes;
selectedTime.add(Calendar.MINUTE, mod < Math.ceil(mNewEventTimeResolutionInMinutes / 2) ? -mod : (mNewEventTimeResolutionInMinutes - mod));
Calendar endTime = (Calendar) selectedTime.clone();
@@ -741,7 +740,8 @@ public class WeekView extends View {
canvas.drawRect(0, mHeaderHeight + mHeaderRowPadding * 2, mHeaderColumnWidth, getHeight(), mHeaderColumnBackgroundPaint);
// Clip to paint in left column only.
canvas.clipRect(0, mHeaderHeight + mHeaderRowPadding * 2, mHeaderColumnWidth, getHeight(), Region.Op.REPLACE);
canvas.save();
canvas.clipRect(0, mHeaderHeight + mHeaderRowPadding * 2, mHeaderColumnWidth, getHeight());
for (int i = 0; i < getNumberOfPeriods(); i++) {
// If we are showing half hours (eg. 5:30am), space the times out by half the hour height
@@ -768,6 +768,7 @@ public class WeekView extends View {
if (top < getHeight())
canvas.drawText(time, mTimeTextWidth + mHeaderColumnPadding, top + mTimeTextHeight, mTimeTextPaint);
}
canvas.restore();
}
private void drawHeaderRowAndEvents(Canvas canvas) {
@@ -852,7 +853,8 @@ public class WeekView extends View {
}
// Clip to paint events only.
canvas.clipRect(mHeaderColumnWidth, mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight / 2, getWidth(), getHeight(), Region.Op.REPLACE);
canvas.save();
canvas.clipRect(mHeaderColumnWidth, mHeaderHeight + mHeaderRowPadding * 2 + mHeaderMarginBottom + mTimeTextHeight / 2, getWidth(), getHeight());
// Iterate through each day.
Calendar oldFirstVisibleDay = mFirstVisibleDay;
@@ -954,13 +956,17 @@ public class WeekView extends View {
// In the next iteration, start from the next day.
startPixel += mWidthPerDay + mColumnGap;
}
canvas.restore();
// Hide everything in the first cell (top left corner).
canvas.clipRect(0, 0, mTimeTextWidth + mHeaderColumnPadding * 2, mHeaderHeight + mHeaderRowPadding * 2, Region.Op.REPLACE);
canvas.save();
canvas.clipRect(0, 0, mTimeTextWidth + mHeaderColumnPadding * 2, mHeaderHeight + mHeaderRowPadding * 2);
canvas.drawRect(0, 0, mTimeTextWidth + mHeaderColumnPadding * 2, mHeaderHeight + mHeaderRowPadding * 2, mHeaderBackgroundPaint);
canvas.restore();
// Clip to paint header row only.
canvas.clipRect(mHeaderColumnWidth, 0, getWidth(), mHeaderHeight + mHeaderRowPadding * 2, Region.Op.REPLACE);
canvas.save();
canvas.clipRect(mHeaderColumnWidth, 0, getWidth(), mHeaderHeight + mHeaderRowPadding * 2);
// Draw the header background.
canvas.drawRect(0, 0, getWidth(), mHeaderHeight + mHeaderRowPadding * 2, mHeaderBackgroundPaint);
@@ -981,10 +987,11 @@ public class WeekView extends View {
String dayLabel = getDateTimeInterpreter().interpretDate(day);
if (dayLabel == null)
throw new IllegalStateException("A DateTimeInterpreter must not return null date");
canvas.drawText(dayLabel, startPixel + mWidthPerDay / 2, mHeaderTextHeight + mHeaderRowPadding, isToday ? mTodayHeaderTextPaint : mHeaderTextPaint);
canvas.drawText(dayLabel, startPixel + mWidthPerDay / 2, mHeaderTextHeight + mHeaderRowPadding+ UtilsUi.convertPixelsToDp(40, mContext), isToday ? mTodayHeaderTextPaint : mHeaderTextPaint);
drawAllDayEvents(day, startPixel, canvas);
startPixel += mWidthPerDay + mColumnGap;
}
canvas.restore();
}
@@ -1253,8 +1260,8 @@ public class WeekView extends View {
*/
private class EventRect {
public WeekViewEvent event;
public WeekViewEvent originalEvent;
public RectF rectF;
WeekViewEvent originalEvent;
RectF rectF;
public float left;
public float width;
public float top;
@@ -1645,7 +1652,7 @@ public class WeekView extends View {
@Override
public String interpretDate(Calendar date) {
try {
return DateUtils.getModifiedDate(getContext(), date.getTimeInMillis(), "EEE dd.M").toUpperCase();
return UtilsDate.getModifiedDate(getContext(), date.getTimeInMillis(), "EEE dd.M").toUpperCase();
} catch (Exception e) {
e.printStackTrace();
return "";
@@ -2414,7 +2421,7 @@ public class WeekView extends View {
/**
* Set the scroll duration
*
* @param scrollDuration the new scrollDuraction
* @param scrollDuration the new scrollDuration
*/
public void setScrollDuration(int scrollDuration) {
mScrollDuration = scrollDuration;
@@ -2518,7 +2525,7 @@ public class WeekView extends View {
// Check after call of mGestureDetector, so mCurrentFlingDirection and mCurrentScrollDirection are set.
if (event.getAction() == MotionEvent.ACTION_UP && !mIsZooming && mCurrentFlingDirection == Direction.NONE) {
if (mCurrentScrollDirection == Direction.RIGHT || mCurrentScrollDirection == Direction.LEFT) {
goToNearestOrigin();
performClick();
}
mCurrentScrollDirection = Direction.NONE;
}
@@ -2526,6 +2533,13 @@ public class WeekView extends View {
return val;
}
@Override
public boolean performClick() {
super.performClick();
goToNearestOrigin();
return true;
}
private void goToNearestOrigin() {
double leftDays = mCurrentOrigin.x / (mWidthPerDay + mColumnGap);

View File

@@ -76,7 +76,7 @@ public class WeekViewEvent {
* @param allDay Is the event an all day event.
* @param shader the Shader of the event rectangle
*/
public WeekViewEvent(String id, String name, String location, Calendar startTime, Calendar endTime, boolean allDay, Shader shader) {
private WeekViewEvent(String id, String name, String location, Calendar startTime, Calendar endTime, boolean allDay, Shader shader) {
this.mId = id;
this.mName = name;
this.mLocation = location;
@@ -96,7 +96,7 @@ public class WeekViewEvent {
* @param endTime The time when the event ends.
* @param allDay Is the event an all day event
*/
public WeekViewEvent(String id, String name, String location, Calendar startTime, Calendar endTime, boolean allDay) {
private WeekViewEvent(String id, String name, String location, Calendar startTime, Calendar endTime, boolean allDay) {
this(id, name, location, startTime, endTime, allDay, null);
}

View File

@@ -5,7 +5,7 @@ import java.util.Calendar;
/**
* Created by jesse on 6/02/2016.
*/
public class WeekViewUtil {
class WeekViewUtil {
/////////////////////////////////////////////////////////////////

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 B

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