Compare commits

...

202 Commits

Author SHA1 Message Date
9b7aca745a [3.9.9-dev] 2019-11-21 19:18:04 +01:00
82852389fa [UI] Update indentation, again. Fix manual event color selecting. 2019-11-21 18:43:01 +01:00
ce06084e6f [Timetable] Fix lessons removing, again. 2019-11-21 18:24:02 +01:00
3ca051983f [APIv2/Timetable] Add searching for the next lesson by team id 2019-11-20 22:43:48 +01:00
cd379e4175 [APIv2/Librus] Add getting grade comments 2019-11-20 21:16:18 +01:00
62fdfa2b6f [UI] Update manual event dialog. Fix timetable errors. 2019-11-20 21:13:43 +01:00
9866017f7e [APIv2/Vulcan] Fix timetable teams issue. Fix missing login data error. 2019-11-20 19:51:50 +01:00
67f98b08c6 [Update] Update update code to allow update from direct download. 2019-11-20 17:11:08 +01:00
fdb5f7ec02 [APIv2/Mobidziennik] Implement getting message details. 2019-11-18 22:57:23 +01:00
04c3c7ca6e [APIv2] Handle Librus Portal maintenance. 2019-11-18 18:55:52 +01:00
f424315d97 [3.9.8-dev] 2019-11-17 23:17:37 +01:00
c907a8df37 [APIv2] Librus: better error handling. Timetable: fix widget crashing with NPE. 2019-11-17 23:16:13 +01:00
37ea65e3fc [Timetable] Add SwipeToRefresh. Select start&end hours based on lesson ranges. 2019-11-16 21:16:18 +01:00
a3e5f824c8 [UI] Fix messages & homework refresh layout sensitivity. 2019-11-16 18:46:01 +01:00
e0c850a455 [Gradle] Update Tachyon to support creating DayView programmatically. 2019-11-16 17:07:07 +01:00
1c6815f708 [3.9.7-dev] 2019-11-15 22:00:18 +01:00
9a20511935 [UI] Set pull-to-refresh colors. 2019-11-15 21:16:58 +01:00
965f5e73d9 [Bugs] Update gradle. Fix crashes in the timetable widget. 2019-11-15 19:55:46 +01:00
13b58a1d56 [3.9.6-dev] Widgets. Timetables. RIP APIv1. 2019-11-15 00:02:00 +01:00
0a3261b8b3 [APIv2/Mobidziennik] Fix incorrect profile ID in teams & timetable. 2019-11-14 23:46:53 +01:00
dbdfc7fdd8 [Widget] Add new Timetable widget with APIv2. 2019-11-14 23:33:13 +01:00
56062f5bfa [Timetable] Fix day fragment crashing on restoring saved instance 2019-11-14 22:13:32 +01:00
0cbba2eb45 [Models] Make Time comparable. Implement faster Date.stepForward 2019-11-14 19:31:50 +01:00
aa84356dd6 [APIv2/Vulcan] Add getting timetable with lesson changes 2019-11-14 00:41:34 +01:00
efaad0a4dd [APIv1] Remove remaining APIv1 components. [*] 2019-11-13 22:37:30 +01:00
71015c0137 [APIv1] Remove most APIv1 components. 2019-11-13 22:26:12 +01:00
85b5667a7e [Sync] New manual sync dialog. Remove most APIv1 dependencies. 2019-11-13 21:57:47 +01:00
dfdc6817a1 [APIv2/Vulcan] Fix getting sent messages with unknown recipient 2019-11-13 21:01:09 +01:00
058345b9c9 [Error] Add user friendly error strings. Add error snackbar to activity & login. 2019-11-13 19:44:08 +01:00
831b7876b4 [APIv2] Fix duplicated error code 2019-11-13 18:23:21 +01:00
729cf6f08e [Settings] New Profile removal dialog. 2019-11-13 17:19:25 +01:00
860a16b32d [3.9.5-dev] Messages & manual events 2019-11-13 17:19:25 +01:00
9f871c077b [APIv2/Librus] Fix getting messages with unknown recipient 2019-11-13 00:00:28 +01:00
a8f89abf7d [APIv2/Vulcan] Add changing message status 2019-11-12 23:59:48 +01:00
16102de619 [Event] Add new manual event dialog 2019-11-12 23:35:47 +01:00
472e768369 [Gradle] Update MaterialDrawer and NavLib 2019-11-12 23:35:13 +01:00
131a769c26 [Gradle] Update dependencies 2019-11-12 22:05:40 +01:00
4a0a6c54e4 [Timetable] Make timetable sync disable the button on click. 2019-11-12 14:36:20 +01:00
c83abe57d5 [Messages] Implement APIv2 in MessageFragment. 2019-11-12 14:11:35 +01:00
c6e2519dcc [APIv2/Librus] Add getting message info 2019-11-12 00:30:26 +01:00
74db524db6 [Timetable] Add lesson details dialog. 2019-11-11 23:59:45 +01:00
810976d976 [3.9.4-dev] The Timetable Release 2019-11-11 19:22:15 +01:00
eb0540b5cb [Timetable] Fix adding NO_LESSONS in Mobidziennik. Change ID generation. 2019-11-11 19:14:02 +01:00
124437fd73 [Login] Allow fake login with DevMode. 2019-11-11 18:41:39 +01:00
69b512e3d1 [Timetable] Extract string resources. Increase offscreen page limit. 2019-11-11 18:32:49 +01:00
29d74e14bd [Timetable] Fix scrolling to first lesson. Update lesson ID generation. 2019-11-11 18:13:37 +01:00
f42ec8435a [Timetable] Show user friendly day name in view pager. 2019-11-11 15:46:04 +01:00
1052b824db [Timetable] Make it sync only timetable when getting a single week. 2019-11-11 14:52:09 +01:00
0742a6a74c [Timetable] Fix removing all lessons when not needed. 2019-11-11 14:16:31 +01:00
d4e9e1730f [APIv2] Implement getting timetable for one week. Support arguments in EdziennikTask. Create new DataRemoveModel. 2019-11-11 14:11:05 +01:00
4eeaa54a47 [Timetable] Implement Librus timetable with lesson changes and shifts. Update UI. 2019-11-10 22:57:19 +01:00
5fa7409317 [APIv2/Librus] Add getting normal lessons 2019-11-10 21:49:40 +01:00
0bcd190714 [APIv2] Add Librus Fake login. 2019-11-10 20:27:26 +01:00
563f08b0ab [UI] Update Timetable lesson layout. Add lesson number text. 2019-11-10 18:53:45 +01:00
1b75424604 [APIv2/UI] Add new Timetable module. Implement in Mobidziennik. 2019-11-10 17:53:10 +01:00
01ac26e67b [Sync] Fix background sync on Android O+. 2019-11-07 17:50:12 +01:00
434ddd1342 [3.9.2-dev] Add persistent debug logging. 2019-11-06 22:49:26 +01:00
3925496595 [3.9.1-dev] Fix Librus Messages crash. 2019-11-06 21:49:40 +01:00
5711c02170 [Home] Fix the private variable error. 2019-11-05 22:22:28 +01:00
ca1c691bf0 [3.9.0-dev] Update build.gradle and changelog 2019-11-05 22:12:15 +01:00
39c8a743bb [Login/Librus] Add Librus captcha activity/dialog. 2019-11-05 21:42:16 +01:00
14cd548dff [Sync] Add more AppManager intents to launch. 2019-11-05 18:49:40 +01:00
b72324805f [APIv2] Move onSuccess from callback to an argument 2019-11-05 18:20:41 +01:00
a049effa61 [APIv2/Librus] Add getting normal grade categories 2019-11-05 18:16:42 +01:00
23d55ec571 [APIv2/Vulcan] Add getting sent messages 2019-11-05 17:30:38 +01:00
385fe21d16 [APIv2/Librus] Implement error handling. Catch exceptions in ApiService. 2019-11-05 11:27:29 +01:00
399ae7e3dc [APIv2/Idziennik] Update Web login expiry time. 2019-11-03 21:30:51 +01:00
22726f8566 [ApiService] Refactor the service, requesting, cancelling. Make compatible with other API tasks. 2019-11-03 21:26:48 +01:00
d789d08f31 [Sync] Fix auto sync when screen is off/device is in Doze mode. 2019-11-03 16:10:30 +01:00
07863fed6f [Sync] Implement APIv2 auto sync using WorkManager. 2019-11-03 15:01:12 +01:00
dcd355851d [Login] Remove Login migration fragment (fix). 2019-11-02 19:25:44 +01:00
aa161b5b0e [Login] Remove Login migration fragment. 2019-11-02 19:20:39 +01:00
99ab9d586f [APIv2] Revert changes in Data. 2019-11-02 13:31:24 +01:00
33c009befe [APIv2/Vulcan] Add getting received messages 2019-11-02 00:06:23 +01:00
64019dccf7 [APIv2/Vulcan] Update uonet-request-signer. Update removing profile. Fix minor issues. 2019-11-01 22:30:39 +01:00
e3bb607303 [APIv2/Vulcan] Use Wulkanowy uonet-request-signer. 2019-11-01 21:31:26 +01:00
0b211c4f12 [APIv2/Vulcan] Fix first login. Add Attendance, Proposed grades. Complete Dictionaries. 2019-11-01 21:21:25 +01:00
38d0a173af [Gradle] Fix AgendaCalendarView dependencies. 2019-11-01 21:19:30 +01:00
bf73c75872 [APIv2/Librus] Change teacher free day types sync frequency. 2019-10-31 15:36:12 +01:00
dd99771c0b [APIv2/Idziennik] Add Messages - received and sent. 2019-10-30 21:13:49 +01:00
01657ca002 [APIv2/Mobidziennik] Reformat Grades a bit. 2019-10-29 23:20:03 +01:00
f2b3603531 [APIv2/Idziennik] Fix Attendance and Exams. Add Lucky number. 2019-10-29 23:17:13 +01:00
d6d73b19ec [APIv2/Mobidziennik] Fix a terrible mistake. 2019-10-29 23:01:11 +01:00
2ad8a308b3 [APIv2/Idziennik] Better request parameter adding. 2019-10-28 23:21:57 +01:00
81c6275255 [APIv2/Idziennik] Add Proposed grades, Exams, Notices, Announcements and broken Attendance. 2019-10-28 23:18:59 +01:00
cfc5db2fe8 [APIv2/Idziennik] Add Grades. 2019-10-28 22:12:04 +01:00
5e90e9aa71 [APIv2/Mobidziennik] Make use of String.fixName() 2019-10-28 19:54:53 +01:00
debb0b1507 [APIv2/Idziennik] Fix login, add timetable. List features. 2019-10-28 19:54:15 +01:00
f452a1b81c [Utils] Update CRC16 generation method. 2019-10-27 21:25:01 +01:00
42f70da162 [UI/Settings] Add option to change app language. 2019-10-27 21:00:23 +01:00
4e8b80822b [APIv2/Idziennik] Add Idziennik implementation (Web, Api login + first login) 2019-10-27 19:45:14 +01:00
7ce7859a5f [UI/Login] Display class and school year in summary, show e-register logo icon. 2019-10-27 19:43:43 +01:00
13b970f4e8 [APIv2] Optimize first login. Add extracting account name, class name, school year. Add Vulcan error handling. Fix Vulcan first login. 2019-10-27 19:40:50 +01:00
054426c9cc [APIv2/DB] Add profile account name, class, school year fields. 2019-10-27 18:03:58 +01:00
10c439afad [APIv2/Template] Add first login template. 2019-10-27 15:38:43 +01:00
46dd543b48 [APIv2] Librus/Synergia request: error handling. Mobidziennik: fix login error code. 2019-10-27 15:30:24 +01:00
28e0f3487c [APIv2/Debug] Add debug button for marking all announcements as read 2019-10-26 15:13:03 +02:00
ca10ee2fe5 [Debug] Make debug mode for debug build 2019-10-26 15:04:11 +02:00
3e99c111bd [APIv2/Librus] Add marking all announcements as read using Synergia 2019-10-26 14:36:21 +02:00
843a8e4298 [APIv2/Librus] Make getting received messages sync always 2019-10-26 00:22:14 +02:00
454e8caa0d [APIv2/Librus] Add getting received and sent messages 2019-10-26 00:14:14 +02:00
e38dc011bd [APIv2/Librus] Fix new homework notifications (Synergia) 2019-10-26 00:11:23 +02:00
9aef3d56df [APIv2/Librus] Fix Librus messages requests 2019-10-25 22:12:45 +02:00
8c099bc137 [APIv2/Librus] Add handling denied access in messages 2019-10-25 00:24:55 +02:00
de82bc7e4d [APIv2/Librus] Add Librus Messages endpoint 2019-10-25 00:15:26 +02:00
5166228915 [APIv2/Librus] Parse document in LibrusSynergia instead of endpoints 2019-10-25 00:14:25 +02:00
35ed31f6b9 [APIv2/Librus] Add Attendance types, Classrooms, Event types, Notices, Notice types, PT meetings. Simplify JSON array iteration. 2019-10-24 22:15:35 +02:00
05ce790587 [APIv2] Add Librus Units. Add Classroom, NoticeType, AttendanceType entities. 2019-10-23 23:02:02 +02:00
ff7f015146 [APIv2/Librus] Add getting student info (student number) using Synergia 2019-10-23 22:59:45 +02:00
e2150e3018 [APIv2/Librus] Add Synergia endpoint template 2019-10-23 22:59:45 +02:00
bbf8f05d3c [APIv2/Vulcan] Add getting notices 2019-10-23 22:59:45 +02:00
36c810fdbe [APIv2/Librus] Add Virtual classes, Users, Subjects. 2019-10-23 21:11:39 +02:00
7822810b91 [APIv2] Implement APIv2 in first login activity. 2019-10-23 19:13:54 +02:00
9fefae3da3 [APIv2] Improve Librus sync timers. Make saveData update app.profile. Decide when to sync lucky number. 2019-10-23 19:12:28 +02:00
74ce9cd38d [APIv2/Librus] Add getting homework using Synergia 2019-10-23 00:05:35 +02:00
bfcbeb7140 [APIv2/Librus] Add Synergia request 2019-10-23 00:03:40 +02:00
5d3bebfdce [APIv2] Implement swipe to refresh with ApiService. 2019-10-22 22:37:02 +02:00
b8f58328cb [APIv2/Vulcan] Add faster request signing. 2019-10-22 22:34:13 +02:00
3540b09623 [APIv2/Vulcan] Temporary fix for signing requests 2019-10-22 10:30:19 +02:00
25744037f5 [DB] Fix migration compatibility for different app versions 2019-10-21 17:25:46 +02:00
0395598efb Merge branch 'develop' into api-v2
# Conflicts:
#	app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.java
2019-10-21 16:52:52 +02:00
f44b64fcc5 [APIv2/Vulcan] Add getting homework 2019-10-21 00:28:27 +02:00
2a7535920e [APIv2/Vulcan] Add getting team if it doesn't exist 2019-10-21 00:27:24 +02:00
9e6741d542 [APIv2/Vulcan] Add getting events 2019-10-20 22:45:44 +02:00
0e5a32b253 [APIv2/Vulcan] Fix setting semester dates 2019-10-20 22:35:13 +02:00
929287a553 [APIv2/Vulcan] Add getting basic dictionaries (teachers and subjects) 2019-10-20 14:55:35 +02:00
a0fe24ada0 [APIv2/Vulcan] Add getting grades 2019-10-20 14:08:06 +02:00
dd34e7d008 [APIv2/Vulcan] Add Vulcan first login 2019-10-20 00:00:50 +02:00
92fb83ccf9 [APIv2/Vulcan] Add Vulcan Api 2019-10-19 20:38:30 +02:00
b32ebe4479 [APIv2/Librus] Move some code 2019-10-19 13:51:01 +02:00
e138ca6eab [APIv2/Librus] Add getting and showing teacher absence reason 2019-10-19 13:33:50 +02:00
cf8afc03bc [APIv2/Librus] Add getting teacher absences 2019-10-19 13:21:15 +02:00
8dc358b075 [APIv2] Notifications - DB entity, move to APIv2. Add Server event sync and web push. 2019-10-18 22:12:40 +02:00
24ab2e7795 [LuckyNumber] Add lucky number metadata. Migrate Date field to int. 2019-10-18 08:46:55 +02:00
c03eca3804 [APIv2/Librus] Use apply in getting lucky numbers 2019-10-17 14:35:10 +02:00
f79263e628 [APIv2] Implement Grades remove model. Implement Librus first login. 2019-10-15 06:49:19 +02:00
e91b4fcf8b [APIv2/Mobidziennik] Implement First login. 2019-10-14 15:44:02 +02:00
0b7f9a08ef [Profile] Remove all Profile.loggedIn usages. 2019-10-14 15:31:34 +02:00
440b76d302 [APIv2/Login] EventBus: sticky events in Service. Add first login request in fragment. 2019-10-14 15:30:34 +02:00
c433a615db [Database] Fix homework/event types migration. 2019-10-14 15:20:21 +02:00
7b5269a1fe [APIv2/Librus] Add getting classes 2019-10-14 14:04:02 +02:00
33cfaef454 [APIv2/Librus] Add getting the lucky number 2019-10-14 13:33:07 +02:00
fe62c93602 [Gradle] Update gradle to 3.5.1 2019-10-14 12:46:36 +02:00
bdc0ceb11d [APIv2] Simplify endpoint choosing. Optimize imports. 2019-10-14 10:59:11 +02:00
b35df5ef11 [APIv2] Add API first login method. Better cancellation handling. 2019-10-14 10:25:44 +02:00
b59887d4e0 [APIv2/Mobidziennik] Change all messages list sync frequency. 2019-10-14 10:02:24 +02:00
da9ccf6d29 [APIv2] Update Feature lists and progress strings. 2019-10-13 20:06:45 +02:00
6b80d7cbd0 [APIv2] Update Feature shouldSync method. 2019-10-13 19:40:56 +02:00
0e17a70193 Merge remote-tracking branch 'origin/api-v2' into api-v2 2019-10-13 17:01:27 +02:00
6ff439b20d [Database/Librus] Fix duplicate homework 2019-10-13 17:00:54 +02:00
70d35e12e5 [APIv2/Librus] Fix attendance metadata 2019-10-13 16:34:44 +02:00
7561087c78 Merge remote-tracking branch 'origin/api-v2' into api-v2 2019-10-13 16:27:58 +02:00
42b56fa4a2 [APIv2/Librus] Add getting announcements 2019-10-13 16:17:39 +02:00
fb945470c0 [APIv2/Librus] Add getting attendances and their types 2019-10-13 15:45:21 +02:00
4f9b9c5f7b [Copyright] Add profile_settings.xml to .gitignore 2019-10-12 23:29:21 +02:00
3b273440cc [APIv2/Librus] Add getting homework 2019-10-12 23:25:53 +02:00
b0fb87acdb [APIv2] Add Feature priority setter and shouldSync method. 2019-10-12 17:01:21 +02:00
0875d13737 [APIv2/Librus] Update endpoint list structure 2019-10-11 23:09:23 +02:00
39050cdee5 Merge remote-tracking branch 'origin/api-v2' into api-v2 2019-10-11 23:00:50 +02:00
7594fdd578 [APIv2/Mobidziennik] Add Messages All+Inbox 2019-10-11 23:00:19 +02:00
93d5596942 [APIv2/Librus] Add events syncing and fix NPE in grades 2019-10-11 22:07:50 +02:00
93fcc0deb7 Merge remote-tracking branch 'origin/api-v2' into api-v2 2019-10-11 21:36:59 +02:00
f6b50fbb58 [API/Librus] Fix grade syncing 2019-10-11 20:27:24 +02:00
cf0aa2788d [API/Debug] Make debugging active profile in the home fragment 2019-10-11 20:03:03 +02:00
67fbb96cd9 [APIv2/Mobidziennik] Refactor data structure 2019-10-11 16:42:24 +02:00
ed8ca00a85 [APIv2] Add Grades. Update Firebase push receivers. 2019-10-11 16:24:45 +02:00
7b3e2a9ea0 [APIv2/Mobidziennik] Add Lucky number, Class calendar, better exception handling 2019-10-10 18:56:05 +02:00
2730c73413 [APIv2/Mobidziennik] Add Homework, Events, Timetable. Fix Users. 2019-10-09 17:23:44 +02:00
bbaa405c59 [APIv2/Mobidziennik] Add Notices, Grades, fix Attendance 2019-10-08 19:16:19 +02:00
6127e574db [APIv2] Implement few Mobidziennik endpoints. Add grade category type. 2019-10-07 23:03:39 +02:00
1b53c35ec5 [APIv2] Fix Librus grades exception. Improve error handling and sync cancellation. 2019-10-07 20:50:44 +02:00
359fd4efed [API/Librus] Add grades 2019-10-07 20:04:23 +02:00
f7412fea7f [APIv2] Update Mobidziennik API. Update Data structure. 2019-10-06 22:58:13 +02:00
f0bf6b8b81 [APIv2] Add sparse array extensions - simplify getting data from database. 2019-10-06 20:58:05 +02:00
7a06593821 [APIv2] Add Vulcan login. Refactor API structure. Add request debug logging. 2019-10-06 18:13:31 +02:00
ddf4fb0b46 [APIv2] Add Mobidziennik login & API prototype. Refactor some classes 2019-10-05 23:42:37 +02:00
535d608829 [APIv2] Add e-register Template 2019-10-05 14:26:01 +02:00
18e469af71 [Database] Fix lesson ranges database entity in index 2019-10-05 14:24:23 +02:00
ce921b9b85 [APIv2] Update endpoint timer features 2019-10-05 13:12:35 +02:00
bb0a366ef1 [APIv2/Librus] Refactor structure 2019-10-05 12:28:45 +02:00
fabacfdcca [APIv2/Librus] Update endpoints: API/Schools, API/Events 2019-10-05 12:26:52 +02:00
648699547e [APIv2] Fix receivers and other weird things 2019-10-04 20:24:47 +02:00
c8c933fb20 [APIv2] Implement endpoint timers. Fix compilation issues after merge. 2019-10-04 17:14:56 +02:00
870a429f3d Add back Librus test 2019-10-03 22:16:16 +02:00
23a9f24d52 Fix compilation issues after merge 2019-10-03 21:10:21 +02:00
48a2ae3599 Merge branch 'develop' into api-v2 2019-10-03 21:07:17 +02:00
4c6b467847 [APIv2] Finalize the basic API service. Add notification. Add exported broadcast receiver. 2019-10-01 21:27:09 +02:00
92880d40cf [APIv2/Librus] Update endpoint choosing algorithm 2019-10-01 11:32:53 +02:00
d2f06a256f [APIv2] Update API, add endpoint choosing algorithm 2019-10-01 07:03:12 +02:00
7da3101678 Delete unused MaterialDrawer module 2019-09-29 20:45:53 +02:00
2ae6d2a4a0 [APIv2] Update LibrusTest 2019-09-29 17:22:44 +02:00
0bf2026a64 [APIv2] Add API Service. Update other APIv2 components. Update Profile DAO 2019-09-29 17:19:38 +02:00
5edd4d5922 Add Chucker 2019-09-27 18:41:58 +02:00
2870931481 [APIv2/Librus] Update Librus Messages login method. 2019-09-25 21:28:04 +02:00
4cbb573d17 [APIv2/Librus] Update Librus login methods. 2019-09-24 20:12:25 +02:00
e95d9ee514 [APIv2/Librus] Update Librus Synergia login method. Add Librus Messages login method. 2019-09-23 09:02:59 +02:00
a785db4d47 [APIv2/Librus] Add Librus Synergia login method. Update structure and error handling. 2019-09-22 22:02:36 +02:00
76d39ac623 [APIv2/Librus] Create Login methods, update API structure 2019-09-21 23:01:31 +02:00
1bdee7857c Update Profile, remove older app version check 2019-09-21 23:00:15 +02:00
3827aeb9b4 APIv2: Update endpoints 2019-09-20 19:12:47 +02:00
4b5c14cbd5 APIv2: Prepare schemes 2019-09-20 19:12:05 +02:00
003ffa2251 APIv2: Create DataEndpoint 2019-09-19 11:23:48 +02:00
351 changed files with 21736 additions and 15538 deletions

1
.gitignore vendored
View File

@ -47,6 +47,7 @@ captures/
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
.idea/copyright/profiles_settings.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.

View File

@ -1,3 +0,0 @@
<component name="CopyrightManager">
<settings default="kubasz" />
</component>

6
.idea/misc.xml generated
View File

@ -5,6 +5,12 @@
<configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" />
</configurations>
</component>
<component name="EntryPointsManager">
<list size="2">
<item index="0" class="java.lang.String" itemvalue="androidx.databinding.BindingAdapter" />
<item index="1" class="java.lang.String" itemvalue="org.greenrobot.eventbus.Subscribe" />
</list>
</component>
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="org.jetbrains.annotations.Nullable" />
<option name="myDefaultNotNull" value="androidx.annotation.RecentlyNonNull" />

View File

@ -49,6 +49,6 @@ dependencies {
// other libraries
//implementation 'se.emilsjolander:stickylistheaders:2.7.0'
implementation 'com.github.edisonw:StickyListHeaders:master-SNAPSHOT'
implementation 'com.github.edisonw:StickyListHeaders:master-SNAPSHOT@aar'
implementation 'io.reactivex:rxjava:1.1.1'
}

View File

@ -131,7 +131,7 @@ dependencies {
implementation("com.github.ozodrukh:CircularReveal:2.0.1@aar") {transitive = true}
implementation "com.heinrichreimersoftware:material-intro:1.5.8" // do not update
implementation "com.jaredrummler:colorpicker:1.0.2"
implementation "com.squareup.okhttp3:okhttp:3.12.0"
implementation "com.squareup.okhttp3:okhttp:3.12.2"
implementation "com.theartofdev.edmodo:android-image-cropper:2.8.0" // do not update
implementation "com.wdullaer:materialdatetimepicker:4.1.2"
implementation "com.yuyh.json:jsonviewer:1.0.6"
@ -142,7 +142,7 @@ dependencies {
implementation "org.jsoup:jsoup:1.10.1"
implementation "pl.droidsonroids.gif:android-gif-drawable:1.2.15"
//implementation "se.emilsjolander:stickylistheaders:2.7.0"
implementation 'com.github.edisonw:StickyListHeaders:master-SNAPSHOT'
implementation 'com.github.edisonw:StickyListHeaders:master-SNAPSHOT@aar'
implementation "uk.co.samuelwall:material-tap-target-prompt:2.14.0"
implementation project(":agendacalendarview")
@ -152,6 +152,24 @@ dependencies {
implementation project(":nachos")
//implementation project(":Navigation")
implementation project(":szkolny-font")
debugImplementation "com.github.ChuckerTeam.Chucker:library:3.0.1"
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:3.0.1"
//implementation 'com.github.wulkanowy:uonet-request-signer:master-SNAPSHOT'
//implementation 'com.github.kuba2k2.uonet-request-signer:android:master-63f094b14a-1'
//implementation "org.redundent:kotlin-xml-builder:1.5.3"
implementation "io.github.wulkanowy:signer-android:0.1.1"
implementation "androidx.work:work-runtime-ktx:${versions.work}"
implementation 'com.hypertrack:hyperlog:0.0.10'
implementation 'com.github.kuba2k2:RecyclerTabLayout:700f980584'
implementation 'com.github.kuba2k2:Tachyon:551943a6b5'
}
repositories {
mavenCentral()

View File

@ -39,4 +39,13 @@
-keep class okhttp3.** { *; }
-keep class com.google.android.material.tabs.** {*;}
-keep class com.google.android.material.tabs.** {*;}
# ServiceLoader support
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
# Most of volatile fields are updated with AFU and should not be mangled
-keepclassmembernames class kotlinx.** {
volatile <fields>;
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF4caf50"
android:pathData="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"/>
</vector>

View File

@ -15,10 +15,14 @@
android:theme="@style/SplashTheme"
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute">
<activity
android:name=".ui.modules.login.LoginLibrusCaptchaActivity"
android:theme="@android:style/Theme.Dialog"
android:excludeFromRecents="true"/>
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@style/SplashTheme">
<intent-filter>
@ -29,7 +33,7 @@
</intent-filter>
</activity>
<activity
android:name="pl.szczodrzynski.edziennik.ui.modules.messages.MessagesComposeActivity"
android:name=".ui.modules.messages.MessagesComposeActivity"
android:configChanges="orientation|screenSize"
android:label="@string/messages_compose_title"
android:theme="@style/AppTheme.Black" />
@ -39,7 +43,7 @@
android:label="@string/app_name"
android:theme="@style/AppTheme" />
<activity
android:name="pl.szczodrzynski.edziennik.ui.modules.login.LoginActivity"
android:name=".ui.modules.login.LoginActivity"
android:configChanges="orientation|screenSize"
android:launchMode="singleTop"
android:theme="@style/AppTheme.Light" />
@ -101,22 +105,23 @@
android:excludeFromRecents="true"
android:noHistory="true"
android:theme="@style/AppTheme.NoDisplay" />
<activity android:name=".widgets.timetable.LessonDialogActivity"
android:configChanges="orientation|keyboardHidden"
android:excludeFromRecents="true"
android:noHistory="true"
android:theme="@style/AppTheme.NoDisplay" />
<activity
android:name=".ui.modules.settings.SettingsLicenseActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="@style/AppTheme" />
<activity
android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="@style/Base.Theme.AppCompat" />
<activity
android:name=".ui.modules.webpush.WebPushConfigActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="@style/AppTheme.Dark" />
<activity
android:name=".ui.modules.home.CounterActivity"
android:theme="@style/AppTheme.Black" />
@ -169,7 +174,6 @@
android:name="android.appwidget.provider"
android:resource="@xml/widget_notifications_info" />
</receiver>
<receiver
android:name=".widgets.luckynumber.WidgetLuckyNumber"
android:label="@string/widget_lucky_number_title">
@ -188,7 +192,6 @@
<action android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>
<receiver android:name=".receivers.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
@ -196,6 +199,24 @@
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
<receiver
android:name=".sync.FirebaseBroadcastReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</receiver>
<receiver
android:name=".receivers.SzkolnyReceiver"
android:exported="true">
<intent-filter>
<action android:name="pl.szczodrzynski.edziennik.SZKOLNY_MAIN" />
</intent-filter>
</receiver>
<service
android:name=".sync.MyFirebaseMessagingService"
android:exported="false">
@ -214,10 +235,7 @@
<service android:name=".Notifier$GetDataRetryService" />
<service
android:name=".sync.SyncService"
android:icon="@mipmap/ic_launcher"
android:label="@string/sync_service" />
<service android:name=".api.v2.ApiService" />
</application>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@ -231,4 +249,4 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
</manifest>
</manifest>

View File

@ -31,57 +31,11 @@
</head>
<body>
<h3>Wersja 3.1.1, 2019-10-09</h3>
<h3>Wersja 4.0, 2019-jeszcze-nie-wiem-kiedy</h3>
<ul>
<li>Librus: poprawiona synchronizacja kategorii i kolorów ocen.</li>
<li>Zmieniony kolor dolnego paska w ciemnym motywie.</li>
<li>Zaktualizowany licznik czasu lekcji.</li>
</ul>
<h3>Wersja 3.1, 2019-09-29</h3>
<ul>
<li>Poprawiony interfejs zadań domowych.</li>
<li>Librus: wyświetlanie komentarzy ocen.</li>
<li>Librus: wyświetlanie nieobecności nauczycieli w Terminarzu.</li>
<li>Librus: usprawniona synchronizacja ocen.</li>
<li>Poprawki angielskiego tłumaczenia.</li>
</ul>
<h3>Wersja 3.0.3, 2019-09-26</h3>
<ul>
<li>Librus: poprawka kilku błędów synchronizacji.</li>
<li>Vulcan: prawidłowe oznaczanie wiadomości jako przeczytana.</li>
<li>Vulcan: poprawiona synchronizacja wiadomości i frekwencji.</li>
<li>Vulcan: poprawka błędów logowania.</li>
</ul>
<h3>Wersja 3.0.2, 2019-09-24</h3>
<ul>
<li>Librus: pobieranie Bieżących ocen opisowych.</li>
<li>Poprawki UI: kolor ikon paska statusu w jasnym motywie.</li>
<li>Poprawka braku skanera QR do przekazywania powiadomień.</li>
<li>Poprawka wyboru koloru i daty własnego wydarzenia, które crashowały aplikację.</li>
</ul>
<h3>Wersja 3.0.1, 2019-09-19</h3>
<ul>
<li>Librus: Poprawa błędu synchronizacji.</li>
<li>Poprawki UI związane z paskiem nawigacji.</li>
<li>Mobidziennik: Pobieranie ocen w niektórych przedmiotach.</li>
</ul>
<h3>Wersja 3.0, 2019-09-13</h3>
<ul>
<li><b>Nowy wygląd i sposób nawigacji</b> w całej aplikacji.</li>
<li>Menu nawigacji można teraz otworzyć przyciskiem na <b>dolnym pasku</b>. Pociągnięcie w górę tego paska wyświetla <b>menu kontekstowe</b> dotyczące danego widoku.</li>
<li>Założyliśmy serwer Discord! <a href="https://discord.gg/n9e8pWr">https://discord.gg/n9e8pWr</a></li>
<br>
<li>Librus: poprawka powielonych ogłoszeń szkolnych.</li>
<li>Naprawiłem błąd nieskończonej synchronizacji w Vulcanie.</li>
<li>Naprawiłem crash launchera przy dodaniu widgetu.</li>
<li>Naprawiłem częste crashe związane z widokiem kalendarza.</li>
<li>Nowe, ładniejsze (choć trochę) motywy kolorów.</li>
<li>Dużo drobnych poprawek UI i działania aplikacji.</li>
<li>UWAGA. To jest wersja in-development. Wiele funkcji może nie działać prawidłowo (lub wcale), co oznacza tylko że nie zostały jeszcze przeniesione
z wersji 3.x. Proszę o cierpliwość oraz <b>nie udostępnianie</b> tej wersji <u>nikomu</u>.</li>
<li>Bardzo dużo zmian</li>
</ul>
<!--<i>

View File

@ -20,7 +20,13 @@ import android.util.Base64;
import android.util.Log;
import android.util.Pair;
import com.evernote.android.job.JobManager;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.work.Configuration;
import com.chuckerteam.chucker.api.ChuckerCollector;
import com.chuckerteam.chucker.api.ChuckerInterceptor;
import com.chuckerteam.chucker.api.RetentionManager;
import com.google.android.gms.security.ProviderInstaller;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
@ -30,6 +36,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import com.hypertrack.hyperlog.HyperLog;
import com.mikepenz.iconics.Iconics;
import com.mikepenz.iconics.IconicsColor;
import com.mikepenz.iconics.IconicsDrawable;
@ -52,8 +59,6 @@ import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatDelegate;
import cat.ereza.customactivityoncrash.config.CaocConfig;
import im.wangchao.mhttp.MHttp;
import im.wangchao.mhttp.internal.cookie.PersistentCookieJar;
@ -63,34 +68,38 @@ import me.leolin.shortcutbadger.ShortcutBadger;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
import okhttp3.TlsVersion;
import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity;
import pl.szczodrzynski.edziennik.data.api.Edziennik;
import pl.szczodrzynski.edziennik.data.api.Iuczniowie;
import pl.szczodrzynski.edziennik.data.api.Librus;
import pl.szczodrzynski.edziennik.data.api.Mobidziennik;
import pl.szczodrzynski.edziennik.data.api.Vulcan;
import pl.szczodrzynski.edziennik.data.db.AppDb;
import pl.szczodrzynski.edziennik.data.db.modules.debuglog.DebugLog;
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull;
import pl.szczodrzynski.edziennik.utils.models.AppConfig;
import pl.szczodrzynski.edziennik.network.NetworkUtils;
import pl.szczodrzynski.edziennik.network.TLSSocketFactory;
import pl.szczodrzynski.edziennik.receivers.JobsCreator;
import pl.szczodrzynski.edziennik.sync.SyncJob;
import pl.szczodrzynski.edziennik.sync.SyncWorker;
import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity;
import pl.szczodrzynski.edziennik.utils.DebugLogFormat;
import pl.szczodrzynski.edziennik.utils.PermissionChecker;
import pl.szczodrzynski.edziennik.utils.Themes;
import pl.szczodrzynski.edziennik.utils.Utils;
import pl.szczodrzynski.edziennik.utils.models.AppConfig;
import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_LIBRUS;
import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_MOBIDZIENNIK;
import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_VULCAN;
public class App extends androidx.multidex.MultiDexApplication {
public class App extends androidx.multidex.MultiDexApplication implements Configuration.Provider {
private static final String TAG = "App";
public static int profileId = -1;
private Context mContext;
@Override
public Configuration getWorkManagerConfiguration() {
return new Configuration.Builder()
.setMinimumLoggingLevel(Log.VERBOSE)
.build();
}
public static final int REQUEST_TIMEOUT = 10 * 1000;
// notifications
@ -127,12 +136,6 @@ public class App extends androidx.multidex.MultiDexApplication {
public PersistentCookieJar cookieJar;
public OkHttpClient http;
public OkHttpClient httpLazy;
//public Jakdojade apiJakdojade;
public Edziennik apiEdziennik;
public Mobidziennik apiMobidziennik;
public Iuczniowie apiIuczniowie;
public Librus apiLibrus;
public Vulcan apiVulcan;
public SharedPreferences appSharedPrefs; // sharedPreferences for APPCONFIG + JOBS STORE
public AppConfig appConfig; // APPCONFIG: common for all profiles
@ -190,12 +193,6 @@ public class App extends androidx.multidex.MultiDexApplication {
db = AppDb.getDatabase(this);
gson = new Gson();
networkUtils = new NetworkUtils(this);
apiEdziennik = new Edziennik(this);
//apiJakdojade = new Jakdojade(this);
apiMobidziennik = new Mobidziennik(this);
apiIuczniowie = new Iuczniowie(this);
apiLibrus = new Librus(this);
apiVulcan = new Vulcan(this);
Iconics.init(getApplicationContext());
Iconics.registerFont(SzkolnyFont.INSTANCE);
@ -207,6 +204,33 @@ public class App extends androidx.multidex.MultiDexApplication {
cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(this));
appSharedPrefs = getSharedPreferences(getString(R.string.preference_file_global), Context.MODE_PRIVATE);
loadConfig();
Themes.INSTANCE.setThemeInt(appConfig.appTheme);
try {
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
for (Signature signature: packageInfo.signatures) {
byte[] signatureBytes = signature.toByteArray();
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(signatureBytes);
this.signature = Base64.encodeToString(md.digest(), Base64.DEFAULT);
//Log.d(TAG, "Signature is "+this.signature);
}
}
catch (Exception e) {
e.printStackTrace();
}
if ("f054761fbdb6a238".equals(deviceId) || BuildConfig.DEBUG) {
devMode = true;
}
else if (appConfig.devModePassword != null) {
checkDevModePassword();
}
OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder()
.cache(null)
.followRedirects(true)
@ -254,6 +278,16 @@ public class App extends androidx.multidex.MultiDexApplication {
}
}
if (App.devMode || BuildConfig.DEBUG) {
HyperLog.initialize(this);
HyperLog.setLogLevel(Log.VERBOSE);
HyperLog.setLogFormat(new DebugLogFormat(this));
ChuckerCollector chuckerCollector = new ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR);
ChuckerInterceptor chuckerInterceptor = new ChuckerInterceptor(this, chuckerCollector);
httpBuilder.addInterceptor(chuckerInterceptor);
}
http = httpBuilder.build();
httpLazy = http.newBuilder().followRedirects(false).followSslRedirects(false).build();
@ -262,41 +296,13 @@ public class App extends androidx.multidex.MultiDexApplication {
//register = new Register(mContext);
appSharedPrefs = getSharedPreferences(getString(R.string.preference_file_global), Context.MODE_PRIVATE);
loadConfig();
Themes.INSTANCE.setThemeInt(appConfig.appTheme);
//profileLoadById(appSharedPrefs.getInt("current_profile_id", 1));
try {
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
for (Signature signature: packageInfo.signatures) {
byte[] signatureBytes = signature.toByteArray();
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(signatureBytes);
this.signature = Base64.encodeToString(md.digest(), Base64.DEFAULT);
//Log.d(TAG, "Signature is "+this.signature);
}
}
catch (Exception e) {
e.printStackTrace();
}
if ("f054761fbdb6a238".equals(deviceId)) {
devMode = true;
}
else if (appConfig.devModePassword != null) {
checkDevModePassword();
}
JobManager.create(this).addJobCreator(new JobsCreator());
if (appConfig.registerSyncEnabled) {
SyncJob.schedule(this);
SyncWorker.Companion.scheduleNext(this, false);
}
else {
SyncJob.clear();
SyncWorker.Companion.cancelNext(this);
}
db.metadataDao().countUnseen().observeForever(count -> {
@ -400,20 +406,20 @@ public class App extends androidx.multidex.MultiDexApplication {
FirebaseApp pushMobidziennikApp = FirebaseApp.initializeApp(
this,
new FirebaseOptions.Builder()
.setApplicationId("1:1029629079999:android:58bb378dab031f42")
.setGcmSenderId("1029629079999")
.setApiKey("AIzaSyCi5LmsZ5BBCQnGtrdvWnp1bWLCNP8OWQE")
.setApplicationId("1:747285019373:android:f6341bf7b158621d")
.build(),
"Mobidziennik"
"Mobidziennik2"
);
/*FirebaseApp pushLibrusApp = FirebaseApp.initializeApp(
FirebaseApp pushLibrusApp = FirebaseApp.initializeApp(
this,
new FirebaseOptions.Builder()
.setApiKey("AIzaSyDfTuEoYPKdv4aceEws1CO3n0-HvTndz-o")
.setApplicationId("1:513056078587:android:1e29083b760af544")
.build(),
"Librus"
);*/
);
FirebaseApp pushVulcanApp = FirebaseApp.initializeApp(
this,
@ -434,10 +440,10 @@ public class App extends androidx.multidex.MultiDexApplication {
Log.d(TAG, "Token for Mobidziennik is " + instanceIdResult.getToken() + ", ID is " + instanceIdResult.getId());
appConfig.fcmTokens.put(LOGIN_TYPE_MOBIDZIENNIK, new Pair<>(instanceIdResult.getToken(), new ArrayList<>()));
});
/*FirebaseInstanceId.getInstance(pushLibrusApp).getInstanceId().addOnSuccessListener(instanceIdResult -> {
FirebaseInstanceId.getInstance(pushLibrusApp).getInstanceId().addOnSuccessListener(instanceIdResult -> {
Log.d(TAG, "Token for Librus is " + instanceIdResult.getToken() + ", ID is " + instanceIdResult.getId());
appConfig.fcmTokens.put(LOGIN_TYPE_LIBRUS, new Pair<>(instanceIdResult.getToken(), new ArrayList<>()));
});*/
});
FirebaseInstanceId.getInstance(pushVulcanApp).getInstanceId().addOnSuccessListener(instanceIdResult -> {
Log.d(TAG, "Token for Vulcan is " + instanceIdResult.getToken() + ", ID is " + instanceIdResult.getId());
Pair<String, List<Integer>> pair = appConfig.fcmTokens.get(LOGIN_TYPE_VULCAN);
@ -606,7 +612,7 @@ public class App extends androidx.multidex.MultiDexApplication {
}
public ProfileFull profileGetOrNull(int id) {
return db.profileDao().getByIdNow(id);
return db.profileDao().getFullByIdNow(id);
}
public void profileLoadById(int id) {
@ -621,7 +627,7 @@ public class App extends androidx.multidex.MultiDexApplication {
return;
}*/
if (profile == null || profile.getId() != id) {
profile = db.profileDao().getByIdNow(id);
profile = db.profileDao().getFullByIdNow(id);
/*if (profile == null) {
profileLoadById(id);
return;
@ -648,7 +654,7 @@ public class App extends androidx.multidex.MultiDexApplication {
/*public void profileRemove(int id)
{
Profile profile = db.profileDao().getByIdNow(id);
Profile profile = db.profileDao().getFullByIdNow(id);
if (profile.id == profile.loginStoreId) {
// this profile is the owner of the login store
@ -700,12 +706,8 @@ public class App extends androidx.multidex.MultiDexApplication {
public void checkDevModePassword() {
try {
if (Utils.AESCrypt.decrypt("nWFVxY65Pa8/aRrT7EylNAencmOD+IxUY2Gg/beiIWY=", appConfig.devModePassword).equals("ok here you go it's enabled now")) {
devMode = true;
}
else {
devMode = false;
}
devMode = Utils.AESCrypt.decrypt("nWFVxY65Pa8/aRrT7EylNAencmOD+IxUY2Gg/beiIWY=", appConfig.devModePassword).equals("ok here you go it's enabled now")
|| BuildConfig.DEBUG;
} catch (Exception e) {
e.printStackTrace();
devMode = false;

View File

@ -0,0 +1,21 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-11.
*/
package pl.szczodrzynski.edziennik;
import android.graphics.Paint;
import android.widget.TextView;
import androidx.databinding.BindingAdapter;
public class Binding {
@BindingAdapter("strikeThrough")
public static void strikeThrough(TextView textView, Boolean strikeThrough) {
if (strikeThrough) {
textView.setPaintFlags(textView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
textView.setPaintFlags(textView.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
}
}
}

View File

@ -4,16 +4,39 @@ import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.text.*
import android.text.style.ForegroundColorSpan
import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan
import android.util.LongSparseArray
import android.util.SparseArray
import android.util.TypedValue
import android.view.View
import android.widget.TextView
import androidx.annotation.*
import androidx.core.app.ActivityCompat
import androidx.core.util.forEach
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import im.wangchao.mhttp.Response
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
import pl.szczodrzynski.navlib.R
import pl.szczodrzynski.navlib.crc16
import pl.szczodrzynski.navlib.getColorFromRes
import java.text.SimpleDateFormat
import java.util.*
import java.util.zip.CRC32
fun List<Teacher>.byId(id: Long) = firstOrNull { it.id == id }
fun List<Teacher>.byNameFirstLast(nameFirstLast: String) = firstOrNull { it.name + " " + it.surname == nameFirstLast }
@ -21,11 +44,25 @@ fun List<Teacher>.byNameLastFirst(nameLastFirst: String) = firstOrNull { it.surn
fun List<Teacher>.byNameFDotLast(nameFDotLast: String) = firstOrNull { it.name + "." + it.surname == nameFDotLast }
fun List<Teacher>.byNameFDotSpaceLast(nameFDotSpaceLast: String) = firstOrNull { it.name + ". " + it.surname == nameFDotSpaceLast }
fun JsonObject.getString(key: String): String? = get(key).let { if (it.isJsonNull) null else it.asString }
fun JsonObject.getInt(key: String): Int? = get(key).let { if (it.isJsonNull) null else it.asInt }
fun JsonObject.getLong(key: String): Long? = get(key).let { if (it.isJsonNull) null else it.asLong }
fun JsonObject.getJsonObject(key: String): JsonObject? = get(key).let { if (it.isJsonNull) null else it.asJsonObject }
fun JsonObject.getJsonArray(key: String): JsonArray? = get(key).let { if (it.isJsonNull) null else it.asJsonArray }
fun JsonObject?.get(key: String): JsonElement? = this?.get(key)
fun JsonObject?.getBoolean(key: String): Boolean? = get(key)?.let { if (it.isJsonNull) null else it.asBoolean }
fun JsonObject?.getString(key: String): String? = get(key)?.let { if (it.isJsonNull) null else it.asString }
fun JsonObject?.getInt(key: String): Int? = get(key)?.let { if (it.isJsonNull) null else it.asInt }
fun JsonObject?.getLong(key: String): Long? = get(key)?.let { if (it.isJsonNull) null else it.asLong }
fun JsonObject?.getFloat(key: String): Float? = get(key)?.let { if(it.isJsonNull) null else it.asFloat }
fun JsonObject?.getJsonObject(key: String): JsonObject? = get(key)?.let { if (it.isJsonNull) null else it.asJsonObject }
fun JsonObject?.getJsonArray(key: String): JsonArray? = get(key)?.let { if (it.isJsonNull) null else it.asJsonArray }
fun JsonObject?.getBoolean(key: String, defaultValue: Boolean): Boolean = get(key)?.let { if (it.isJsonNull) defaultValue else it.asBoolean } ?: defaultValue
fun JsonObject?.getString(key: String, defaultValue: String): String = get(key)?.let { if (it.isJsonNull) defaultValue else it.asString } ?: defaultValue
fun JsonObject?.getInt(key: String, defaultValue: Int): Int = get(key)?.let { if (it.isJsonNull) defaultValue else it.asInt } ?: defaultValue
fun JsonObject?.getLong(key: String, defaultValue: Long): Long = get(key)?.let { if (it.isJsonNull) defaultValue else it.asLong } ?: defaultValue
fun JsonObject?.getFloat(key: String, defaultValue: Float): Float = get(key)?.let { if(it.isJsonNull) defaultValue else it.asFloat } ?: defaultValue
fun JsonObject?.getJsonObject(key: String, defaultValue: JsonObject): JsonObject = get(key)?.let { if (it.isJsonNull) defaultValue else it.asJsonObject } ?: defaultValue
fun JsonObject?.getJsonArray(key: String, defaultValue: JsonArray): JsonArray = get(key)?.let { if (it.isJsonNull) defaultValue else it.asJsonArray } ?: defaultValue
fun JsonArray?.asJsonObjectList() = this?.map { it.asJsonObject }
fun CharSequence?.isNotNullNorEmpty(): Boolean {
return this != null && this.isNotEmpty()
@ -46,8 +83,65 @@ fun Bundle?.getString(key: String, defaultValue: String): String {
return this?.getString(key, defaultValue) ?: defaultValue
}
fun String.fixName(): String {
return this.fixWhiteSpaces().toProperCase()
}
fun String.toProperCase(): String = changeStringCase(this)
fun String.swapFirstLastName(): String {
return this.split(" ").let {
if (it.size > 1)
it[1]+" "+it[0]
else
it[0]
}
}
fun String.getFirstLastName(): Pair<String, String>? {
return this.split(" ").let {
if (it.size >= 2) Pair(it[0], it[1])
else null
}
}
fun String.getLastFirstName() = this.getFirstLastName()
fun changeStringCase(s: String): String {
val delimiters = " '-/"
val sb = StringBuilder()
var capNext = true
for (ch in s.toCharArray()) {
var c = ch
c = if (capNext)
Character.toUpperCase(c)
else
Character.toLowerCase(c)
sb.append(c)
capNext = delimiters.indexOf(c) >= 0
}
return sb.toString()
}
fun buildFullName(firstName: String?, lastName: String?): String {
return "$firstName $lastName".fixName()
}
fun String.getShortName(): String {
return split(" ").let {
if (it.size > 1)
"${it[0]} ${it[1][0]}."
else
it[0]
}
}
fun List<String>.join(delimiter: String): String {
return this.joinToString(delimiter)
}
fun colorFromName(context: Context, name: String?): Int {
var crc = crc16(name ?: "")
var crc = (name ?: "").crc16()
crc = (crc and 0xff) or (crc shr 8)
crc %= 16
val color = when (crc) {
@ -71,8 +165,9 @@ fun colorFromName(context: Context, name: String?): Int {
return context.getColorFromRes(color)
}
fun MutableList<out Profile>.filterOutArchived() {
fun MutableList<Profile>.filterOutArchived(): MutableList<Profile> {
this.removeAll { it.archived }
return this
}
fun Activity.isStoragePermissionGranted(): Boolean {
@ -87,3 +182,316 @@ fun Activity.isStoragePermissionGranted(): Boolean {
true
}
}
fun Response?.getUnixDate(): Long {
val rfcDate = this?.headers()?.get("date") ?: return currentTimeUnix()
val pattern = "EEE, dd MMM yyyy HH:mm:ss Z"
val format = SimpleDateFormat(pattern, Locale.ENGLISH)
return format.parse(rfcDate).time / 1000
}
const val MINUTE = 60L
const val HOUR = 60L*MINUTE
const val DAY = 24L*HOUR
const val WEEK = 7L*DAY
const val MONTH = 30L*DAY
const val YEAR = 365L*DAY
fun <T> LongSparseArray<T>.values(): List<T> {
val result = mutableListOf<T>()
forEach { _, value ->
result += value
}
return result
}
fun <T> SparseArray<T>.values(): List<T> {
val result = mutableListOf<T>()
forEach { _, value ->
result += value
}
return result
}
fun <T> List<T>.toSparseArray(destination: SparseArray<T>, key: (T) -> Int) {
forEach {
destination.put(key(it), it)
}
}
fun <T> List<T>.toSparseArray(destination: LongSparseArray<T>, key: (T) -> Long) {
forEach {
destination.put(key(it), it)
}
}
fun <T> List<T>.toSparseArray(key: (T) -> Int): SparseArray<T> {
val result = SparseArray<T>()
toSparseArray(result, key)
return result
}
fun <T> List<T>.toSparseArray(key: (T) -> Long): LongSparseArray<T> {
val result = LongSparseArray<T>()
toSparseArray(result, key)
return result
}
fun <T> SparseArray<T>.singleOrNull(predicate: (T) -> Boolean): T? {
forEach { _, value ->
if (predicate(value))
return value
}
return null
}
fun <T> LongSparseArray<T>.singleOrNull(predicate: (T) -> Boolean): T? {
forEach { _, value ->
if (predicate(value))
return value
}
return null
}
fun String.fixWhiteSpaces() = buildString(length) {
var wasWhiteSpace = true
for (c in this@fixWhiteSpaces) {
if (c.isWhitespace()) {
if (!wasWhiteSpace) {
append(c)
wasWhiteSpace = true
}
} else {
append(c)
wasWhiteSpace = false
}
}
}.trimEnd()
fun List<Team>.getById(id: Long): Team? {
return singleOrNull { it.id == id }
}
fun LongSparseArray<Team>.getById(id: Long): Team? {
forEach { _, value ->
if (value.id == id)
return value
}
return null
}
operator fun MatchResult.get(group: Int): String {
if (group >= groupValues.size)
return ""
return groupValues[group]
}
fun Activity.setLanguage(language: String) {
val locale = Locale(language.toLowerCase(Locale.ROOT))
val configuration = resources.configuration
Locale.setDefault(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLocale(locale)
}
configuration.locale = locale
resources.updateConfiguration(configuration, resources.displayMetrics)
baseContext.resources.updateConfiguration(configuration, baseContext.resources.displayMetrics)
}
/*
Code copied from android-28/java.util.Locale.initDefault()
*/
fun initDefaultLocale() {
run {
// user.locale gets priority
/*val languageTag: String? = System.getProperty("user.locale", "")
if (languageTag.isNotNullNorEmpty()) {
return@run Locale(languageTag)
}*/
// user.locale is empty
val language: String? = System.getProperty("user.language", "pl")
val region: String? = System.getProperty("user.region")
val country: String?
val variant: String?
// for compatibility, check for old user.region property
if (region != null) {
// region can be of form country, country_variant, or _variant
val i = region.indexOf('_')
if (i >= 0) {
country = region.substring(0, i)
variant = region.substring(i + 1)
} else {
country = region
variant = ""
}
} else {
country = System.getProperty("user.country", "")
variant = System.getProperty("user.variant", "")
}
return@run Locale(language)
}.let {
Locale.setDefault(it)
}
}
fun String.crc16(): Int {
var crc = 0xFFFF
for (aBuffer in this) {
crc = crc.ushr(8) or (crc shl 8) and 0xffff
crc = crc xor (aBuffer.toInt() and 0xff) // byte to int, trunc sign
crc = crc xor (crc and 0xff shr 4)
crc = crc xor (crc shl 12 and 0xffff)
crc = crc xor (crc and 0xFF shl 5 and 0xffff)
}
crc = crc and 0xffff
return crc + 32768
}
fun String.crc32(): Long {
val crc = CRC32()
crc.update(toByteArray())
return crc.value
}
fun Long.formatDate(format: String = "yyyy-MM-dd HH:mm:ss"): String = SimpleDateFormat(format).format(this)
fun CharSequence?.asColoredSpannable(colorInt: Int): Spannable {
val spannable = SpannableString(this)
spannable.setSpan(ForegroundColorSpan(colorInt), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return spannable
}
fun CharSequence?.asStrikethroughSpannable(): Spannable {
val spannable = SpannableString(this)
spannable.setSpan(StrikethroughSpan(), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return spannable
}
fun CharSequence?.asItalicSpannable(): Spannable {
val spannable = SpannableString(this)
spannable.setSpan(StyleSpan(Typeface.ITALIC), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return spannable
}
/**
* Returns a new read-only list only of those given elements, that are not empty.
* Applies for CharSequence and descendants.
*/
fun <T : CharSequence> listOfNotEmpty(vararg elements: T): List<T> = elements.filterNot { it.isEmpty() }
fun List<CharSequence>.concat(delimiter: String? = null): CharSequence {
if (this.isEmpty()) {
return ""
}
if (this.size == 1) {
return this[0]
}
var spanned = false
for (piece in this) {
if (piece is Spanned) {
spanned = true
break
}
}
var first = true
if (spanned) {
val ssb = SpannableStringBuilder()
for (piece in this) {
if (!first && delimiter != null)
ssb.append(delimiter)
first = false
ssb.append(piece)
}
return SpannedString(ssb)
} else {
val sb = StringBuilder()
for (piece in this) {
if (!first && delimiter != null)
sb.append(delimiter)
first = false
sb.append(piece)
}
return sb.toString()
}
}
fun TextView.setText(@StringRes resid: Int, vararg formatArgs: Any) {
text = context.getString(resid, *formatArgs)
}
fun JsonObject(vararg properties: Pair<String, Any>): JsonObject {
return JsonObject().apply {
for (property in properties) {
when (property.second) {
is JsonElement -> add(property.first, property.second as JsonElement)
is String -> addProperty(property.first, property.second as String)
is Char -> addProperty(property.first, property.second as Char)
is Number -> addProperty(property.first, property.second as Number)
is Boolean -> addProperty(property.first, property.second as Boolean)
}
}
}
}
fun JsonArray?.isNullOrEmpty(): Boolean = (this?.size() ?: 0) == 0
fun JsonArray.isEmpty(): Boolean = this.size() == 0
@Suppress("UNCHECKED_CAST")
inline fun <T : View> T.onClick(crossinline onClickListener: (v: T) -> Unit) {
setOnClickListener { v: View ->
onClickListener(v as T)
}
}
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
observe(lifecycleOwner, object : Observer<T> {
override fun onChanged(t: T?) {
observer.onChanged(t)
removeObserver(this)
}
})
}
/**
* Convert a value in dp to pixels.
*/
val Int.dp: Int
get() = (this * Resources.getSystem().displayMetrics.density).toInt()
/**
* Convert a value in pixels to dp.
*/
val Int.px: Int
get() = (this / Resources.getSystem().displayMetrics.density).toInt()
@ColorInt
fun @receiver:AttrRes Int.resolveAttr(context: Context?): Int {
val typedValue = TypedValue()
context?.theme?.resolveAttribute(this, typedValue, true)
return typedValue.data
}
@ColorInt
fun @receiver:ColorRes Int.resolveColor(context: Context): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
context.resources.getColor(this, context.theme)
}
else {
context.resources.getColor(this)
}
}
fun @receiver:DrawableRes Int.resolveDrawable(context: Context): Drawable {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
context.resources.getDrawable(this, context.theme)
}
else {
context.resources.getDrawable(this)
}
}
fun View.findParentById(targetId: Int): View? {
if (id == targetId) {
return this
}
val viewParent = this.parent ?: return null
if (viewParent is View) {
return viewParent.findParentById(targetId)
}
return null
}

View File

@ -6,78 +6,84 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.os.*
import android.util.Log
import android.provider.Settings
import android.view.Gravity
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata.*
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.navlib.SystemBarsUtil.Companion.COLOR_HALF_TRANSPARENT
import pl.szczodrzynski.navlib.bottomsheet.NavBottomSheet
import pl.szczodrzynski.navlib.drawer.NavDrawer
import pl.szczodrzynski.navlib.drawer.items.DrawerPrimaryItem
import pl.szczodrzynski.navlib.drawer.items.withAppTitle
import androidx.appcompat.widget.PopupMenu
import androidx.core.graphics.ColorUtils
import androidx.lifecycle.Observer
import androidx.navigation.NavOptions
import com.danimahardhika.cafebar.CafeBar
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.mikepenz.iconics.IconicsColor
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.IconicsSize
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
import com.mikepenz.materialdrawer.model.DividerDrawerItem
import com.mikepenz.materialdrawer.model.ProfileDrawerItem
import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem
import com.mikepenz.materialdrawer.model.interfaces.IProfile
import me.zhanghai.android.materialprogressbar.internal.ThemeUtils
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.droidsonroids.gif.GifDrawable
import pl.szczodrzynski.edziennik.App.APP_URL
import pl.szczodrzynski.edziennik.data.api.AppError
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface.*
import pl.szczodrzynski.edziennik.data.api.interfaces.SyncCallback
import pl.szczodrzynski.edziennik.api.v2.events.*
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata.*
import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull
import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog
import pl.szczodrzynski.edziennik.ui.modules.homework.HomeworkFragment
import pl.szczodrzynski.edziennik.ui.modules.login.LoginActivity
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesDetailsFragment
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment
import pl.szczodrzynski.edziennik.utils.models.NavTarget
import pl.szczodrzynski.edziennik.network.ServerRequest
import pl.szczodrzynski.edziennik.sync.SyncJob
import pl.szczodrzynski.edziennik.sync.AppManagerDetectedEvent
import pl.szczodrzynski.edziennik.sync.SyncWorker
import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog
import pl.szczodrzynski.edziennik.ui.dialogs.sync.SyncViewListDialog
import pl.szczodrzynski.edziennik.ui.modules.agenda.AgendaFragment
import pl.szczodrzynski.edziennik.ui.modules.announcements.AnnouncementsFragment
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceFragment
import pl.szczodrzynski.edziennik.ui.modules.base.DebugFragment
import pl.szczodrzynski.edziennik.ui.modules.behaviour.BehaviourFragment
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar
import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackFragment
import pl.szczodrzynski.edziennik.ui.modules.feedback.HelpFragment
import pl.szczodrzynski.edziennik.ui.modules.grades.editor.GradesEditorFragment
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesFragment
import pl.szczodrzynski.edziennik.ui.modules.grades.editor.GradesEditorFragment
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment
import pl.szczodrzynski.edziennik.ui.modules.behaviour.BehaviourFragment
import pl.szczodrzynski.edziennik.ui.modules.homework.HomeworkFragment
import pl.szczodrzynski.edziennik.ui.modules.login.LoginActivity
import pl.szczodrzynski.edziennik.ui.modules.messages.MessageFragment
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment
import pl.szczodrzynski.edziennik.ui.modules.notifications.NotificationsFragment
import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsNewFragment
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.Utils.dpToPx
import pl.szczodrzynski.edziennik.utils.appManagerIntentList
import pl.szczodrzynski.edziennik.utils.models.NavTarget
import pl.szczodrzynski.navlib.*
import pl.szczodrzynski.navlib.SystemBarsUtil.Companion.COLOR_HALF_TRANSPARENT
import pl.szczodrzynski.navlib.bottomsheet.NavBottomSheet
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
import pl.szczodrzynski.navlib.drawer.NavDrawer
import pl.szczodrzynski.navlib.drawer.items.DrawerPrimaryItem
import pl.szczodrzynski.navlib.drawer.items.withAppTitle
import java.io.File
import java.io.IOException
import java.util.*
import kotlin.math.roundToInt
class MainActivity : AppCompatActivity() {
companion object {
@ -199,7 +205,7 @@ class MainActivity : AppCompatActivity() {
list += NavTarget(TARGET_GRADES_EDITOR, R.string.menu_grades_editor, GradesEditorFragment::class)
list += NavTarget(TARGET_HELP, R.string.menu_help, HelpFragment::class)
list += NavTarget(TARGET_FEEDBACK, R.string.menu_feedback, FeedbackFragment::class)
list += NavTarget(TARGET_MESSAGES_DETAILS, R.string.menu_message, MessagesDetailsFragment::class)
list += NavTarget(TARGET_MESSAGES_DETAILS, R.string.menu_message, MessageFragment::class)
list += NavTarget(DRAWER_ITEM_DEBUG, R.string.menu_debug, DebugFragment::class)
list
@ -210,6 +216,7 @@ class MainActivity : AppCompatActivity() {
val navView: NavView by lazy { b.navView }
val drawer: NavDrawer by lazy { navView.drawer }
val bottomSheet: NavBottomSheet by lazy { navView.bottomSheet }
val errorSnackbar: ErrorSnackbar by lazy { ErrorSnackbar(this) }
val swipeRefreshLayout: SwipeRefreshLayoutNoTouch by lazy { b.swipeRefreshLayout }
@ -236,8 +243,14 @@ class MainActivity : AppCompatActivity() {
setTheme(Themes.appTheme)
app.appConfig.language?.let {
setLanguage(it)
}
setContentView(b.root)
errorSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar)
navLoading = true
b.navView.apply {
@ -337,7 +350,7 @@ class MainActivity : AppCompatActivity() {
if (!profileListEmpty) {
handleIntent(intent?.extras)
}
app.db.profileDao().getAllFull().observe(this, Observer { profiles ->
app.db.profileDao().allFull.observe(this, Observer { profiles ->
// TODO fix weird -1 profiles ???
profiles.removeAll { it.id < 0 }
drawer.setProfileList(profiles)
@ -354,7 +367,7 @@ class MainActivity : AppCompatActivity() {
if (app.profile != null)
setDrawerItems()
app.db.metadataDao().getUnreadCounts().observe(this, Observer { unreadCounters ->
app.db.metadataDao().unreadCounts.observe(this, Observer { unreadCounters ->
unreadCounters.map {
it.type = it.thingType
}
@ -363,10 +376,15 @@ class MainActivity : AppCompatActivity() {
b.swipeRefreshLayout.isEnabled = true
b.swipeRefreshLayout.setOnRefreshListener { this.syncCurrentFeature() }
b.swipeRefreshLayout.setColorSchemeResources(
R.color.md_blue_500,
R.color.md_amber_500,
R.color.md_green_500
)
isStoragePermissionGranted()
SyncJob.schedule(app)
SyncWorker.scheduleNext(app)
// APP BACKGROUND
if (app.appConfig.appBackground != null) {
@ -456,7 +474,7 @@ class MainActivity : AppCompatActivity() {
.withIcon(CommunityMaterial.Icon2.cmd_sync)
.withOnClickListener(View.OnClickListener {
bottomSheet.close()
app.apiEdziennik.guiSyncFeature(app, this, App.profileId, R.string.sync_dialog_title, R.string.sync_dialog_text, R.string.sync_done, fragmentToFeature(navTargetId))
SyncViewListDialog(this, navTargetId)
}),
BottomSheetSeparatorItem(false),
BottomSheetPrimaryItem(false)
@ -474,6 +492,13 @@ class MainActivity : AppCompatActivity() {
.withIcon(CommunityMaterial.Icon.cmd_android_debug_bridge)
.withOnClickListener(View.OnClickListener { loadTarget(DRAWER_ITEM_DEBUG) })
}
EventBus.getDefault().register(this)
}
override fun onDestroy() {
EventBus.getDefault().unregister(this)
super.onDestroy()
}
var profileListEmptyListener = {
@ -488,7 +513,7 @@ class MainActivity : AppCompatActivity() {
profileListEmptyListener()
}
DRAWER_PROFILE_SYNC_ALL -> {
SyncJob.run(app)
EdziennikTask.sync().enqueue(this)
}
else -> {
loadTarget(id)
@ -508,52 +533,94 @@ class MainActivity : AppCompatActivity() {
fun syncCurrentFeature() {
swipeRefreshLayout.isRefreshing = true
Toast.makeText(this, fragmentToSyncName(navTargetId), Toast.LENGTH_SHORT).show()
val callback = object : SyncCallback {
override fun onLoginFirst(profileList: List<Profile>, loginStore: LoginStore) {
}
override fun onSuccess(activityContext: Context, profileFull: ProfileFull) {
swipeRefreshLayout.isRefreshing = false
}
override fun onError(activityContext: Context, error: AppError) {
swipeRefreshLayout.isRefreshing = false
app.apiEdziennik.guiShowErrorSnackbar(this@MainActivity, error)
}
override fun onProgress(progressStep: Int) {
}
override fun onActionStarted(stringResId: Int) {
}
val fragmentParam = when (navTargetId) {
DRAWER_ITEM_MESSAGES -> MessagesFragment.pageSelection
else -> 0
}
val feature = fragmentToFeature(navTargetId)
if (feature == FEATURE_ALL) {
swipeRefreshLayout.isRefreshing = false
app.apiEdziennik.guiSync(app, this, App.profileId, R.string.sync_dialog_title, R.string.sync_dialog_text, R.string.sync_done)
} else {
app.apiEdziennik.guiSyncSilent(app, this, App.profileId, callback, feature)
EdziennikTask.syncProfile(
App.profileId,
listOf(navTargetId to fragmentParam)
).enqueue(this)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onSyncStartedEvent(event: ApiTaskStartedEvent) {
swipeRefreshLayout.isRefreshing = true
if (event.profileId == App.profileId) {
navView.toolbar.apply {
subtitleFormat = null
subtitleFormatWithUnread = null
subtitle = getString(R.string.toolbar_subtitle_syncing)
}
}
}
private fun fragmentToFeature(currentFragment: Int): Int {
return when (currentFragment) {
DRAWER_ITEM_TIMETABLE -> FEATURE_TIMETABLE
DRAWER_ITEM_AGENDA -> FEATURE_AGENDA
DRAWER_ITEM_GRADES -> FEATURE_GRADES
DRAWER_ITEM_HOMEWORK -> FEATURE_HOMEWORK
DRAWER_ITEM_BEHAVIOUR -> FEATURE_NOTICES
DRAWER_ITEM_ATTENDANCE -> FEATURE_ATTENDANCE
DRAWER_ITEM_MESSAGES -> when (MessagesFragment.pageSelection) {
1 -> FEATURE_MESSAGES_OUTBOX
else -> FEATURE_MESSAGES_INBOX
@Subscribe(threadMode = ThreadMode.MAIN)
fun onSyncProgressEvent(event: ApiTaskProgressEvent) {
if (event.profileId == App.profileId) {
navView.toolbar.apply {
subtitleFormat = null
subtitleFormatWithUnread = null
subtitle = if (event.progress < 0f)
event.progressText ?: ""
else
getString(R.string.toolbar_subtitle_syncing_format, event.progress.roundToInt(), event.progressText ?: "")
}
DRAWER_ITEM_ANNOUNCEMENTS -> FEATURE_ANNOUNCEMENTS
else -> FEATURE_ALL
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onSyncProfileFinishedEvent(event: ApiTaskFinishedEvent) {
if (event.profileId == App.profileId) {
navView.toolbar.apply {
subtitleFormat = R.string.toolbar_subtitle
subtitleFormatWithUnread = R.plurals.toolbar_subtitle_with_unread
subtitle = "Gotowe"
}
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onSyncFinishedEvent(event: ApiTaskAllFinishedEvent) {
swipeRefreshLayout.isRefreshing = false
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onSyncErrorEvent(event: ApiTaskErrorEvent) {
navView.toolbar.apply {
subtitleFormat = R.string.toolbar_subtitle
subtitleFormatWithUnread = R.plurals.toolbar_subtitle_with_unread
subtitle = "Gotowe"
}
errorSnackbar.addError(event.error).show()
}
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onAppManagerDetectedEvent(event: AppManagerDetectedEvent) {
if (app.appConfig.dontShowAppManagerDialog)
return
MaterialAlertDialogBuilder(this)
.setTitle(R.string.app_manager_dialog_title)
.setMessage(R.string.app_manager_dialog_text)
.setPositiveButton(R.string.ok) { dialog, which ->
try {
for (intent in appManagerIntentList) {
if (packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
startActivity(intent)
}
}
} catch (e: Exception) {
try {
startActivity(Intent(Settings.ACTION_SETTINGS))
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(this, R.string.app_manager_open_failed, Toast.LENGTH_SHORT).show()
}
}
}
.setNeutralButton(R.string.dont_ask_again) { dialog, which ->
app.appConfig.dontShowAppManagerDialog = true
app.saveConfig("dontShowAppManagerDialog")
}
.setCancelable(false)
.show()
}
private fun fragmentToSyncName(currentFragment: Int): Int {
return when (currentFragment) {
DRAWER_ITEM_TIMETABLE -> R.string.sync_feature_timetable
@ -584,11 +651,11 @@ class MainActivity : AppCompatActivity() {
}
private fun handleIntent(extras: Bundle?) {
Log.d(TAG, "handleIntent() {")
d(TAG, "handleIntent() {")
extras?.keySet()?.forEach { key ->
Log.d(TAG, " \"$key\": "+extras.get(key))
d(TAG, " \"$key\": "+extras.get(key))
}
Log.d(TAG, "}")
d(TAG, "}")
if (extras?.containsKey("reloadProfileId") == true) {
val reloadProfileId = extras.getInt("reloadProfileId", -1)
@ -634,7 +701,8 @@ class MainActivity : AppCompatActivity() {
}
intentTargetId != -1 -> {
drawer.currentProfile = app.profile.id
loadTarget(intentTargetId, extras)
if (navTargetId != intentTargetId)
loadTarget(intentTargetId, extras)
}
else -> {
drawer.currentProfile = app.profile.id
@ -712,7 +780,7 @@ class MainActivity : AppCompatActivity() {
fun loadProfile(id: Int) = loadProfile(id, navTargetId)
fun loadProfile(id: Int, arguments: Bundle?) = loadProfile(id, navTargetId, arguments)
fun loadProfile(id: Int, drawerSelection: Int, arguments: Bundle? = null) {
Log.d("NavDebug", "loadProfile(id = $id, drawerSelection = $drawerSelection)")
//d("NavDebug", "loadProfile(id = $id, drawerSelection = $drawerSelection)")
if (app.profile != null && App.profileId == id) {
drawer.currentProfile = app.profile.id
loadTarget(drawerSelection, arguments)
@ -752,7 +820,7 @@ class MainActivity : AppCompatActivity() {
}
}
private fun loadTarget(target: NavTarget, arguments: Bundle? = null) {
Log.d("NavDebug", "loadItem(id = ${target.id})")
d("NavDebug", "loadItem(id = ${target.id})")
bottomSheet.close()
bottomSheet.removeAllContextual()
@ -765,7 +833,7 @@ class MainActivity : AppCompatActivity() {
navView.bottomBar.fabExtended = false
navView.bottomBar.setFabOnClickListener(null)
Log.d("NavDebug", "Navigating from ${navTarget.fragmentClass?.java?.simpleName} to ${target.fragmentClass?.java?.simpleName}")
d("NavDebug", "Navigating from ${navTarget.fragmentClass?.java?.simpleName} to ${target.fragmentClass?.java?.simpleName}")
val fragment = target.fragmentClass?.java?.newInstance() ?: return
fragment.arguments = arguments
@ -824,9 +892,9 @@ class MainActivity : AppCompatActivity() {
}
}
Log.d("NavDebug", "Current fragment ${navTarget.fragmentClass?.java?.simpleName}, pop to home ${navTarget.popToHome}, back stack:")
d("NavDebug", "Current fragment ${navTarget.fragmentClass?.java?.simpleName}, pop to home ${navTarget.popToHome}, back stack:")
navBackStack.forEachIndexed { index, target2 ->
Log.d("NavDebug", " - $index: ${target2.fragmentClass?.java?.simpleName}")
d("NavDebug", " - $index: ${target2.fragmentClass?.java?.simpleName}")
}
transaction.replace(R.id.fragment, fragment)
@ -917,7 +985,7 @@ class MainActivity : AppCompatActivity() {
}
fun setDrawerItems() {
Log.d("NavDebug", "setDrawerItems() app.profile = ${app.profile ?: "null"}")
d("NavDebug", "setDrawerItems() app.profile = ${app.profile ?: "null"}")
val drawerItems = arrayListOf<IDrawerItem<*>>()
val drawerProfiles = arrayListOf<ProfileSettingDrawerItem>()
@ -975,7 +1043,7 @@ class MainActivity : AppCompatActivity() {
}
loadTarget(DRAWER_ITEM_SETTINGS, null)
} else if (item.itemId == 2) {
app.apiEdziennik.guiRemoveProfile(this@MainActivity, profileId, profile.name?.getText(this).toString())
ProfileRemoveDialog(this, profileId, profile.name?.getText(this)?.toString() ?: "?")
}
true
}

View File

@ -8,24 +8,22 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import android.util.Log;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull;
import pl.szczodrzynski.edziennik.receivers.BootReceiver;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Time;
import pl.szczodrzynski.edziennik.receivers.BootReceiver;
import pl.szczodrzynski.edziennik.sync.SyncJob;
import pl.szczodrzynski.edziennik.sync.SyncService;
import static androidx.core.app.NotificationCompat.PRIORITY_DEFAULT;
import static androidx.core.app.NotificationCompat.PRIORITY_MAX;
import static pl.szczodrzynski.edziennik.sync.SyncService.ACTION_CANCEL;
public class Notifier {
@ -36,14 +34,14 @@ public class Notifier {
private static String CHANNEL_GET_DATA_DESC;
private static final String GROUP_KEY_GET_DATA = "pl.szczodrzynski.edziennik.GET_DATA";
private static final int ID_NOTIFICATIONS = 1337002;
private static String CHANNEL_NOTIFICATIONS_NAME;
private static String CHANNEL_NOTIFICATIONS_DESC;
public static final int ID_NOTIFICATIONS = 1337002;
public static String CHANNEL_NOTIFICATIONS_NAME;
public static String CHANNEL_NOTIFICATIONS_DESC;
public static final String GROUP_KEY_NOTIFICATIONS = "pl.szczodrzynski.edziennik.NOTIFICATIONS";
private static final int ID_NOTIFICATIONS_QUIET = 1337002;
private static String CHANNEL_NOTIFICATIONS_QUIET_NAME;
private static String CHANNEL_NOTIFICATIONS_QUIET_DESC;
public static final int ID_NOTIFICATIONS_QUIET = 1337002;
public static String CHANNEL_NOTIFICATIONS_QUIET_NAME;
public static String CHANNEL_NOTIFICATIONS_QUIET_DESC;
public static final String GROUP_KEY_NOTIFICATIONS_QUIET = "pl.szczodrzynski.edziennik.NOTIFICATIONS_QUIET";
private static final int ID_UPDATES = 1337003;
@ -52,9 +50,9 @@ public class Notifier {
private static final String GROUP_KEY_UPDATES = "pl.szczodrzynski.edziennik.UPDATES";
private App app;
private NotificationManager notificationManager;
public NotificationManager notificationManager;
private NotificationCompat.Builder getDataNotificationBuilder;
private int notificationColor;
public int notificationColor;
Notifier(App _app) {
this.app = _app;
@ -71,7 +69,7 @@ public class Notifier {
notificationColor = ContextCompat.getColor(app.getContext(), R.color.colorPrimary);
notificationManager = (NotificationManager) app.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channelGetData = new NotificationChannel(GROUP_KEY_GET_DATA, CHANNEL_GET_DATA_NAME, NotificationManager.IMPORTANCE_LOW);
NotificationChannel channelGetData = new NotificationChannel(GROUP_KEY_GET_DATA, CHANNEL_GET_DATA_NAME, NotificationManager.IMPORTANCE_MIN);
channelGetData.setDescription(CHANNEL_GET_DATA_DESC);
notificationManager.createNotificationChannel(channelGetData);
@ -109,13 +107,13 @@ public class Notifier {
return app.appConfig.quietHoursStart > 0 && now >= start && now <= end;
}
private int getNotificationDefaults() {
public int getNotificationDefaults() {
return (shouldBeQuiet() ? 0 : Notification.DEFAULT_ALL);
}
private String getNotificationGroup() {
public String getNotificationGroup() {
return shouldBeQuiet() ? GROUP_KEY_NOTIFICATIONS_QUIET : GROUP_KEY_NOTIFICATIONS;
}
private int getNotificationPriority() {
public int getNotificationPriority() {
return shouldBeQuiet() ? PRIORITY_DEFAULT : PRIORITY_MAX;
}
@ -126,17 +124,17 @@ public class Notifier {
| |__| | (_| | || (_| | | |__| | __/ |_
|_____/ \__,_|\__\__,_| \_____|\___|\_*/
public Notification notificationGetDataShow(int maxProgress) {
Intent notificationIntent = new Intent(app.getContext(), SyncService.class);
/*Intent notificationIntent = new Intent(app.getContext(), SyncService.class);
notificationIntent.setAction(ACTION_CANCEL);
PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0,
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);*/
getDataNotificationBuilder = new NotificationCompat.Builder(app, GROUP_KEY_GET_DATA)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setColor(notificationColor)
.setContentTitle(app.getString(R.string.notification_get_data_title))
.setContentText(app.getString(R.string.notification_get_data_text))
.addAction(R.drawable.ic_notification, app.getString(R.string.notification_get_data_cancel), pendingIntent)
//.addAction(R.drawable.ic_notification, app.getString(R.string.notification_get_data_cancel), pendingIntent)
//.setGroup(GROUP_KEY_GET_DATA)
.setOngoing(true)
.setProgress(maxProgress, 0, false)
@ -208,10 +206,8 @@ public class Notifier {
@Override
protected void onHandleIntent(Intent intent) {
SyncJob.run((App) getApplication(), intent.getExtras().getInt("failedProfileId", -1), -1);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
assert notificationManager != null;
notificationManager.cancel(ID_GET_DATA_ERROR);
}
}
@ -315,13 +311,14 @@ public class Notifier {
\____/| .__/ \__,_|\__,_|\__\___||___/
| |
|*/
public void notificationUpdatesShow(String updateVersion, String updateUrl, String updateFilename) {
public void notificationUpdatesShow(String updateVersion, String updateUrl, String updateFilename, boolean updateDirect) {
if (!app.appConfig.notifyAboutUpdates)
return;
Intent notificationIntent = new Intent(app.getContext(), BootReceiver.NotificationActionService.class)
.putExtra("update_version", updateVersion)
.putExtra("update_url", updateUrl)
.putExtra("update_filename", updateFilename);
.putExtra("update_filename", updateFilename)
.putExtra("update_direct", updateDirect);
PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0,
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);

View File

@ -1,450 +0,0 @@
package pl.szczodrzynski.edziennik;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.util.SparseArray;
import android.view.View;
import android.widget.RemoteViews;
import com.mikepenz.iconics.IconicsColor;
import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.iconics.IconicsSize;
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull;
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange;
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile;
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.ItemWidgetTimetableModel;
import pl.szczodrzynski.edziennik.utils.models.Time;
import pl.szczodrzynski.edziennik.utils.models.Week;
import pl.szczodrzynski.edziennik.widgets.WidgetConfig;
import pl.szczodrzynski.edziennik.sync.SyncJob;
import pl.szczodrzynski.edziennik.widgets.timetable.LessonDetailsActivity;
import pl.szczodrzynski.edziennik.widgets.timetable.WidgetTimetableService;
import static pl.szczodrzynski.edziennik.ExtensionsKt.filterOutArchived;
import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_HOMEWORK;
import static pl.szczodrzynski.edziennik.utils.Utils.bs;
public class WidgetTimetable extends AppWidgetProvider {
public static final String ACTION_SYNC_DATA = "ACTION_SYNC_DATA";
private static final String TAG = "WidgetTimetable";
private static int modeInt = 0;
public WidgetTimetable() {
// Start the worker thread
//HandlerThread sWorkerThread = new HandlerThread("WidgetTimetable-worker");
//sWorkerThread.start();
//Handler sWorkerQueue = new Handler(sWorkerThread.getLooper());
}
public static SparseArray<List<ItemWidgetTimetableModel>> timetables = null;
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_SYNC_DATA.equals(intent.getAction())){
SyncJob.run((App) context.getApplicationContext());
}
super.onReceive(context, intent);
}
public static PendingIntent getPendingSelfIntent(Context context, String action) {
Intent intent = new Intent(context, WidgetTimetable.class);
intent.setAction(action);
return getPendingSelfIntent(context, intent);
}
public static PendingIntent getPendingSelfIntent(Context context, Intent intent) {
return PendingIntent.getBroadcast(context, 0, intent, 0);
}
public static Bitmap drawableToBitmap (Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable)drawable).getBitmap();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
ComponentName thisWidget = new ComponentName(context, WidgetTimetable.class);
timetables = new SparseArray<>();
//timetables.clear();
App app = (App)context.getApplicationContext();
int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
// There may be multiple widgets active, so update all of them
for (int appWidgetId : allWidgetIds) {
//d(TAG, "thr "+Thread.currentThread().getName());
WidgetConfig widgetConfig = app.appConfig.widgetTimetableConfigs.get(appWidgetId);
if (widgetConfig == null) {
widgetConfig = new WidgetConfig(app.profileFirstId());
app.appConfig.widgetTimetableConfigs.put(appWidgetId, widgetConfig);
app.appConfig.savePending = true;
}
RemoteViews views;
if (widgetConfig.bigStyle) {
views = new RemoteViews(context.getPackageName(), widgetConfig.darkTheme ? R.layout.widget_timetable_dark_big : R.layout.widget_timetable_big);
}
else {
views = new RemoteViews(context.getPackageName(), widgetConfig.darkTheme ? R.layout.widget_timetable_dark : R.layout.widget_timetable);
}
PorterDuff.Mode mode = PorterDuff.Mode.DST_IN;
/*if (widgetConfig.darkTheme) {
switch (modeInt) {
case 0:
mode = PorterDuff.Mode.ADD;
d(TAG, "ADD");
break;
case 1:
mode = PorterDuff.Mode.DST_ATOP;
d(TAG, "DST_ATOP");
break;
case 2:
mode = PorterDuff.Mode.DST_IN;
d(TAG, "DST_IN");
break;
case 3:
mode = PorterDuff.Mode.DST_OUT;
d(TAG, "DST_OUT");
break;
case 4:
mode = PorterDuff.Mode.DST_OVER;
d(TAG, "DST_OVER");
break;
case 5:
mode = PorterDuff.Mode.LIGHTEN;
d(TAG, "LIGHTEN");
break;
case 6:
mode = PorterDuff.Mode.MULTIPLY;
d(TAG, "MULTIPLY");
break;
case 7:
mode = PorterDuff.Mode.OVERLAY;
d(TAG, "OVERLAY");
break;
case 8:
mode = PorterDuff.Mode.SCREEN;
d(TAG, "SCREEN");
break;
case 9:
mode = PorterDuff.Mode.SRC_ATOP;
d(TAG, "SRC_ATOP");
break;
case 10:
mode = PorterDuff.Mode.SRC_IN;
d(TAG, "SRC_IN");
break;
case 11:
mode = PorterDuff.Mode.SRC_OUT;
d(TAG, "SRC_OUT");
break;
case 12:
mode = PorterDuff.Mode.SRC_OVER;
d(TAG, "SRC_OVER");
break;
case 13:
mode = PorterDuff.Mode.XOR;
d(TAG, "XOR");
break;
default:
modeInt = 0;
mode = PorterDuff.Mode.ADD;
d(TAG, "ADD");
break;
}
}*/
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
// this code seems to crash the launcher on >= P
float transparency = widgetConfig.opacity; //0...1
long colorFilter = 0x01000000L * (long) (255f * transparency);
try {
final Method[] declaredMethods = Class.forName("android.widget.RemoteViews").getDeclaredMethods();
final int len = declaredMethods.length;
if (len > 0) {
for (int m = 0; m < len; m++) {
final Method method = declaredMethods[m];
if (method.getName().equals("setDrawableParameters")) {
method.setAccessible(true);
method.invoke(views, R.id.widgetTimetableListView, true, -1, (int) colorFilter, mode, -1);
method.invoke(views, R.id.widgetTimetableHeader, true, -1, (int) colorFilter, mode, -1);
break;
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
Intent refreshIntent = new Intent(context, WidgetTimetable.class);
refreshIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
refreshIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
PendingIntent pendingRefreshIntent = PendingIntent.getBroadcast(context,
0, refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.widgetTimetableRefresh, pendingRefreshIntent);
views.setOnClickPendingIntent(R.id.widgetTimetableSync, WidgetTimetable.getPendingSelfIntent(context, ACTION_SYNC_DATA));
views.setImageViewBitmap(R.id.widgetTimetableRefresh, new IconicsDrawable(context, CommunityMaterial.Icon2.cmd_refresh)
.color(IconicsColor.colorInt(Color.WHITE))
.size(IconicsSize.dp(widgetConfig.bigStyle ? 24 : 16)).toBitmap());
views.setImageViewBitmap(R.id.widgetTimetableSync, new IconicsDrawable(context, CommunityMaterial.Icon2.cmd_sync)
.color(IconicsColor.colorInt(Color.WHITE))
.size(IconicsSize.dp(widgetConfig.bigStyle ? 24 : 16)).toBitmap());
boolean unified = widgetConfig.profileId == -1;
List<Profile> profileList = new ArrayList<>();
if (unified) {
profileList = app.db.profileDao().getAllNow();
filterOutArchived(profileList);
}
else {
Profile profile = app.db.profileDao().getByIdNow(widgetConfig.profileId);
if (profile != null) {
profileList.add(profile);
}
}
//d(TAG, "Profiles: "+ Arrays.toString(profileList.toArray()));
if (profileList == null || profileList.size() == 0) {
views.setViewVisibility(R.id.widgetTimetableLoading, View.VISIBLE);
views.setTextViewText(R.id.widgetTimetableLoading, app.getString(R.string.widget_timetable_profile_doesnt_exist));
}
else {
views.setViewVisibility(R.id.widgetTimetableLoading, View.GONE);
//Register profile;
long bellSyncDiffMillis = 0;
if (app.appConfig.bellSyncDiff != null) {
bellSyncDiffMillis = app.appConfig.bellSyncDiff.hour * 60 * 60 * 1000 + app.appConfig.bellSyncDiff.minute * 60 * 1000 + app.appConfig.bellSyncDiff.second * 1000;
bellSyncDiffMillis *= app.appConfig.bellSyncMultiplier;
bellSyncDiffMillis *= -1;
}
List<ItemWidgetTimetableModel> lessonList = new ArrayList<>();
Time syncedNow = Time.fromMillis(Time.getNow().getInMillis() + bellSyncDiffMillis);
Date today = Date.getToday();
int openProfileId = -1;
Date displayingDate = null;
int displayingWeekDay = 0;
if (unified) {
views.setTextViewText(R.id.widgetTimetableSubtitle, app.getString(R.string.widget_timetable_title_unified));
}
else {
views.setTextViewText(R.id.widgetTimetableSubtitle, profileList.get(0).getName());
openProfileId = profileList.get(0).getId();
}
List<LessonFull> lessons = app.db.lessonDao().getAllWeekNow(unified ? -1 : openProfileId, today.clone().stepForward(0, 0, -today.getWeekDay()), today);
int scrollPos = 0;
for (Profile profile: profileList) {
Date profileDisplayingDate = HomeFragment.findDateWithLessons(profile.getId(), lessons, syncedNow, 1);
int profileDisplayingWeekDay = profileDisplayingDate.getWeekDay();
int dayDiff = Date.diffDays(profileDisplayingDate, Date.getToday());
//d(TAG, "For profile "+profile.name+" displayingDate is "+profileDisplayingDate.getStringY_m_d());
if (displayingDate == null || profileDisplayingDate.getValue() < displayingDate.getValue()) {
displayingDate = profileDisplayingDate;
displayingWeekDay = profileDisplayingWeekDay;
//d(TAG, "Setting as global dd");
if (dayDiff == 0) {
views.setTextViewText(R.id.widgetTimetableTitle, app.getString(R.string.day_today_format, Week.getFullDayName(displayingWeekDay)));
} else if (dayDiff == 1) {
views.setTextViewText(R.id.widgetTimetableTitle, app.getString(R.string.day_tomorrow_format, Week.getFullDayName(displayingWeekDay)));
} else {
views.setTextViewText(R.id.widgetTimetableTitle, Week.getFullDayName(displayingWeekDay) + " " + profileDisplayingDate.getStringDm());
}
}
}
for (Profile profile: profileList) {
int pos = 0;
List<EventFull> events = app.db.eventDao().getAllByDateNow(profile.getId(), displayingDate);
if (events == null)
events = new ArrayList<>();
if (unified) {
ItemWidgetTimetableModel separator = new ItemWidgetTimetableModel();
separator.profileId = profile.getId();
separator.bigStyle = widgetConfig.bigStyle;
separator.darkTheme = widgetConfig.darkTheme;
separator.separatorProfileName = profile.getName();
lessonList.add(separator);
}
for (LessonFull lesson : lessons) {
//d(TAG, "Profile "+profile.id+" Lesson profileId "+lesson.profileId+" weekDay "+lesson.weekDay+", "+lesson);
if (profile.getId() != lesson.profileId || displayingWeekDay != lesson.weekDay)
continue;
//d(TAG, "Not skipped");
ItemWidgetTimetableModel model = new ItemWidgetTimetableModel();
model.bigStyle = widgetConfig.bigStyle;
model.darkTheme = widgetConfig.darkTheme;
model.profileId = profile.getId();
model.lessonDate = displayingDate;
model.startTime = lesson.startTime;
model.endTime = lesson.endTime;
model.lessonPassed = (syncedNow.getValue() > lesson.endTime.getValue()) && displayingWeekDay == Week.getTodayWeekDay();
model.lessonCurrent = (Time.inRange(lesson.startTime, lesson.endTime, syncedNow)) && displayingWeekDay == Week.getTodayWeekDay();
if (model.lessonCurrent) {
scrollPos = pos;
} else if (model.lessonPassed) {
scrollPos = pos + 1;
}
pos++;
model.subjectName = bs(lesson.subjectLongName);
model.classroomName = lesson.classroomName;
model.bellSyncDiffMillis = bellSyncDiffMillis;
if (lesson.changeId != 0) {
if (lesson.changeType == LessonChange.TYPE_CHANGE) {
model.lessonChange = true;
if (lesson.changedClassroomName()) {
model.newClassroomName = lesson.changeClassroomName;
}
if (lesson.changedSubjectLongName()) {
model.newSubjectName = lesson.changeSubjectLongName;
}
}
if (lesson.changeType == LessonChange.TYPE_CANCELLED) {
model.lessonCancelled = true;
}
}
for (EventFull event : events) {
if (event.startTime == null)
continue;
if (event.eventDate.getValue() == displayingDate.getValue()
&& event.startTime.getValue() == lesson.startTime.getValue()) {
model.eventColors.add(event.type == TYPE_HOMEWORK ? ItemWidgetTimetableModel.EVENT_COLOR_HOMEWORK : event.getColor());
}
}
lessonList.add(model);
}
}
if (lessonList.size() == 0) {
views.setViewVisibility(R.id.widgetTimetableLoading, View.VISIBLE);
views.setRemoteAdapter(R.id.widgetTimetableListView, new Intent());
views.setTextViewText(R.id.widgetTimetableLoading, app.getString(R.string.widget_timetable_no_lessons));
appWidgetManager.updateAppWidget(appWidgetId, views);
}
else {
views.setViewVisibility(R.id.widgetTimetableLoading, View.GONE);
timetables.put(appWidgetId, lessonList);
//WidgetTimetableListProvider.widgetsLessons.put(appWidgetId, lessons);
//views.setRemoteAdapter(R.id.widgetTimetableListView, new Intent());
Intent listIntent = new Intent(context, WidgetTimetableService.class);
listIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
listIntent.setData(Uri.parse(listIntent.toUri(Intent.URI_INTENT_SCHEME)));
views.setRemoteAdapter(R.id.widgetTimetableListView, listIntent);
// template to handle the click listener for each item
Intent intentTemplate = new Intent(context, LessonDetailsActivity.class);
// Old activities shouldn't be in the history stack
intentTemplate.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntentTimetable = PendingIntent.getActivity(context,
0,
intentTemplate,
0);
views.setPendingIntentTemplate(R.id.widgetTimetableListView, pendingIntentTimetable);
Intent openIntent = new Intent(context, MainActivity.class);
openIntent.setAction("android.intent.action.MAIN");
if (!unified) {
openIntent.putExtra("profileId", openProfileId);
openIntent.putExtra("timetableDate", displayingDate.getValue());
}
openIntent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE);
PendingIntent pendingOpenIntent = PendingIntent.getActivity(context,
appWidgetId, openIntent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.widgetTimetableHeader, pendingOpenIntent);
if (!unified)
views.setScrollPosition(R.id.widgetTimetableListView, scrollPos);
}
}
appWidgetManager.updateAppWidget(appWidgetId, views);
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widgetTimetableListView);
}
//modeInt++;
}
@Override
public void onEnabled(Context context) {
// Enter relevant functionality for when the first widget is created
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
App app = (App) context.getApplicationContext();
for (int appWidgetId: appWidgetIds) {
app.appConfig.widgetTimetableConfigs.remove(appWidgetId);
}
app.saveConfig("widgetTimetableConfigs");
}
}

View File

@ -0,0 +1,371 @@
package pl.szczodrzynski.edziennik
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.util.SparseArray
import android.view.View
import android.widget.RemoteViews
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.ItemWidgetTimetableModel
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.edziennik.utils.models.Week
import pl.szczodrzynski.edziennik.widgets.WidgetConfig
import pl.szczodrzynski.edziennik.widgets.timetable.LessonDialogActivity
import pl.szczodrzynski.edziennik.widgets.timetable.WidgetTimetableService
import java.lang.reflect.InvocationTargetException
class WidgetTimetable : AppWidgetProvider() {
override fun onReceive(context: Context, intent: Intent) {
if (ACTION_SYNC_DATA == intent.action) {
EdziennikTask.sync().enqueue(context)
}
super.onReceive(context, intent)
}
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
val thisWidget = ComponentName(context, WidgetTimetable::class.java)
timetables = SparseArray()
//timetables.clear();
val app = context.applicationContext as App
var bellSyncDiffMillis: Long = 0
if (app.appConfig.bellSyncDiff != null) {
bellSyncDiffMillis = (app.appConfig.bellSyncDiff.hour * 60 * 60 * 1000 + app.appConfig.bellSyncDiff.minute * 60 * 1000 + app.appConfig.bellSyncDiff.second * 1000).toLong()
bellSyncDiffMillis *= app.appConfig.bellSyncMultiplier.toLong()
bellSyncDiffMillis *= -1
}
val allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget)
allWidgetIds?.forEach { appWidgetId ->
var widgetConfig = app.appConfig.widgetTimetableConfigs[appWidgetId]
if (widgetConfig == null) {
widgetConfig = WidgetConfig(app.profileFirstId())
app.appConfig.widgetTimetableConfigs[appWidgetId] = widgetConfig
app.appConfig.savePending = true
}
val views = if (widgetConfig.bigStyle) {
RemoteViews(context.packageName, if (widgetConfig.darkTheme) R.layout.widget_timetable_dark_big else R.layout.widget_timetable_big)
} else {
RemoteViews(context.packageName, if (widgetConfig.darkTheme) R.layout.widget_timetable_dark else R.layout.widget_timetable)
}
val refreshIntent = Intent(app, WidgetTimetable::class.java)
refreshIntent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
refreshIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds)
val pendingRefreshIntent = PendingIntent.getBroadcast(context,
0, refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT)
views.setOnClickPendingIntent(R.id.widgetTimetableRefresh, pendingRefreshIntent)
views.setOnClickPendingIntent(R.id.widgetTimetableSync, getPendingSelfIntent(context, ACTION_SYNC_DATA))
views.setImageViewBitmap(R.id.widgetTimetableRefresh, IconicsDrawable(context, CommunityMaterial.Icon2.cmd_refresh)
.colorInt(Color.WHITE)
.sizeDp(if (widgetConfig.bigStyle) 24 else 16).toBitmap())
views.setImageViewBitmap(R.id.widgetTimetableSync, IconicsDrawable(context, CommunityMaterial.Icon2.cmd_sync)
.colorInt(Color.WHITE)
.sizeDp(if (widgetConfig.bigStyle) 24 else 16).toBitmap())
prepareAppWidget(app, appWidgetId, views, widgetConfig, bellSyncDiffMillis)
appWidgetManager.updateAppWidget(appWidgetId, views)
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widgetTimetableListView)
}
}
private fun prepareAppWidget(
app: App,
appWidgetId: Int,
views: RemoteViews,
widgetConfig: WidgetConfig,
bellSyncDiffMillis: Long
) {
// get the current bell-synced time
val now = Time.fromMillis(Time.getNow().inMillis + bellSyncDiffMillis)
// set the widget transparency
val mode = PorterDuff.Mode.DST_IN
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
// this code seems to crash the launcher on >= P
val transparency = widgetConfig.opacity //0...1
val colorFilter = 0x01000000L * (255f * transparency).toLong()
try {
val declaredMethods = Class.forName("android.widget.RemoteViews").declaredMethods
val len = declaredMethods.size
if (len > 0) {
for (m in 0 until len) {
val method = declaredMethods[m]
if (method.name == "setDrawableParameters") {
method.isAccessible = true
method.invoke(views, R.id.widgetTimetableListView, true, -1, colorFilter.toInt(), mode, -1)
method.invoke(views, R.id.widgetTimetableHeader, true, -1, colorFilter.toInt(), mode, -1)
break
}
}
}
} catch (e: ClassNotFoundException) {
e.printStackTrace()
} catch (e: InvocationTargetException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
} catch (e: IllegalArgumentException) {
e.printStackTrace()
}
}
val unified = widgetConfig.profileId == -1
// get all profiles or one profile with the specified id
val profileList = if (unified)
app.db.profileDao().allNow.filterOutArchived()
else
listOfNotNull(app.db.profileDao().getByIdNow(widgetConfig.profileId))
// no profile was found
if (profileList.isEmpty()) {
views.setViewVisibility(R.id.widgetTimetableLoading, View.VISIBLE)
views.setTextViewText(R.id.widgetTimetableLoading, app.getString(R.string.widget_timetable_profile_doesnt_exist))
return
}
views.setViewVisibility(R.id.widgetTimetableLoading, View.GONE)
// set lesson search bounds
val today = Date.getToday()
val searchEnd = today.clone().stepForward(0, 0, 7)
var scrollPos = 0
var profileId: Int? = null
var displayingDate: Date? = null
val models = mutableListOf<ItemWidgetTimetableModel>()
// get all lessons within the search bounds
val lessonList = app.db.timetableDao().getBetweenDatesNow(today, searchEnd)
for (profile in profileList) {
// add a profile separator with its name
if (unified) {
val separator = ItemWidgetTimetableModel()
separator.profileId = profile.id
separator.bigStyle = widgetConfig.bigStyle
separator.darkTheme = widgetConfig.darkTheme
separator.separatorProfileName = profile.name
models.add(separator)
}
// search for lessons to display
val timetableDate = Date.getToday()
var checkedDays = 0
var lessons = lessonList.filter { it.profileId == profile.id && it.displayDate == timetableDate && it.type != Lesson.TYPE_NO_LESSONS }
while ((lessons.isEmpty() || lessons.none {
it.displayDate != today || (it.displayDate == today && it.displayEndTime != null && it.displayEndTime!! >= now)
}) && checkedDays < 7) {
timetableDate.stepForward(0, 0, 1)
lessons = lessonList.filter { it.profileId == profile.id && it.displayDate == timetableDate && it.type != Lesson.TYPE_NO_LESSONS }
checkedDays++
}
// set the displayingDate to show in the header
if (!unified) {
if (lessons.isNotEmpty())
displayingDate = timetableDate
profileId = profile.id
}
// get all events for the current date
val events = app.db.eventDao().getAllByDateNow(profile.id, timetableDate)?.filterNotNull() ?: emptyList()
lessons.forEachIndexed { pos, lesson ->
val model = ItemWidgetTimetableModel()
model.bigStyle = widgetConfig.bigStyle
model.darkTheme = widgetConfig.darkTheme
model.profileId = profile.id
model.lessonId = lesson.id
model.lessonDate = timetableDate
model.startTime = lesson.displayStartTime
model.endTime = lesson.displayEndTime
// check if the lesson has already passed or it's currently in progress
if (lesson.displayDate == today) {
lesson.displayEndTime?.let { endTime ->
model.lessonPassed = now > endTime
lesson.displayStartTime?.let { startTime ->
model.lessonCurrent = now in startTime..endTime
}
}
}
// set where should the list view scroll to
if (model.lessonCurrent) {
scrollPos = pos
} else if (model.lessonPassed) {
scrollPos = pos + 1
}
// set the subject and classroom name
model.subjectName = lesson.displaySubjectName
model.classroomName = lesson.displayClassroom
// set the bell sync to calculate progress in ListProvider
model.bellSyncDiffMillis = bellSyncDiffMillis
// make the model aware of the lesson type
when (lesson.type) {
Lesson.TYPE_CANCELLED -> {
model.lessonCancelled = true
}
Lesson.TYPE_CHANGE,
Lesson.TYPE_SHIFTED_SOURCE,
Lesson.TYPE_SHIFTED_TARGET -> {
model.lessonChange = true
}
}
// add every event on this lesson
for (event in events) {
if (event.startTime != null && event.startTime != lesson.displayStartTime)
continue
model.eventColors.add(if (event.type == TYPE_HOMEWORK) ItemWidgetTimetableModel.EVENT_COLOR_HOMEWORK else event.getColor())
}
models += model
}
}
if (unified) {
// set the title for an unified widget
views.setTextViewText(R.id.widgetTimetableTitle, app.getString(R.string.widget_timetable_title_unified))
views.setViewVisibility(R.id.widgetTimetableSubtitle, View.GONE)
} else {
// set the title to present the widget's profile
views.setTextViewText(R.id.widgetTimetableTitle, profileList[0].name)
views.setViewVisibility(R.id.widgetTimetableTitle, View.VISIBLE)
// make the subtitle show current date for these lessons
displayingDate?.let {
when (Date.diffDays(it, Date.getToday())) {
0 -> views.setTextViewText(R.id.widgetTimetableSubtitle, app.getString(R.string.day_today_format, Week.getFullDayName(it.weekDay)))
1 -> views.setTextViewText(R.id.widgetTimetableSubtitle, app.getString(R.string.day_tomorrow_format, Week.getFullDayName(it.weekDay)))
else -> views.setTextViewText(R.id.widgetTimetableSubtitle, Week.getFullDayName(it.weekDay) + " " + it.formattedString)
}
}
}
// intent running when the header is clicked
val openIntent = Intent(app, MainActivity::class.java)
openIntent.action = "android.intent.action.MAIN"
if (!unified) {
// per-profile widget should redirect to it + correct day
profileId?.let {
openIntent.putExtra("profileId", it)
}
displayingDate?.let {
openIntent.putExtra("timetableDate", it.value)
}
}
openIntent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE)
val pendingOpenIntent = PendingIntent.getActivity(app, appWidgetId, openIntent, 0)
views.setOnClickPendingIntent(R.id.widgetTimetableHeader, pendingOpenIntent)
if (lessonList.isEmpty()) {
views.setViewVisibility(R.id.widgetTimetableLoading, View.VISIBLE)
views.setRemoteAdapter(R.id.widgetTimetableListView, Intent())
views.setTextViewText(R.id.widgetTimetableLoading, app.getString(R.string.widget_timetable_no_lessons))
return
}
timetables!!.put(appWidgetId, models)
// apply the list service to the list view
val listIntent = Intent(app, WidgetTimetableService::class.java)
listIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
listIntent.data = Uri.parse(listIntent.toUri(Intent.URI_INTENT_SCHEME))
views.setRemoteAdapter(R.id.widgetTimetableListView, listIntent)
// create an intent used to display the lesson details dialog
val intentTemplate = Intent(app, LessonDialogActivity::class.java)
intentTemplate.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
val pendingIntentTimetable = PendingIntent.getActivity(app, appWidgetId, intentTemplate, 0)
views.setPendingIntentTemplate(R.id.widgetTimetableListView, pendingIntentTimetable)
if (!unified)
views.setScrollPosition(R.id.widgetTimetableListView, scrollPos)
}
override fun onEnabled(context: Context) {
// Enter relevant functionality for when the first widget is created
}
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
val app = context.applicationContext as App
for (appWidgetId in appWidgetIds) {
app.appConfig.widgetTimetableConfigs.remove(appWidgetId)
}
app.saveConfig("widgetTimetableConfigs")
}
companion object {
val ACTION_SYNC_DATA = "ACTION_SYNC_DATA"
private val TAG = "WidgetTimetable"
private val modeInt = 0
var timetables: SparseArray<List<ItemWidgetTimetableModel>>? = null
fun getPendingSelfIntent(context: Context, action: String): PendingIntent {
val intent = Intent(context, WidgetTimetable::class.java)
intent.action = action
return getPendingSelfIntent(context, intent)
}
fun getPendingSelfIntent(context: Context, intent: Intent): PendingIntent {
return PendingIntent.getBroadcast(context, 0, intent, 0)
}
fun drawableToBitmap(drawable: Drawable): Bitmap {
if (drawable is BitmapDrawable) {
return drawable.bitmap
}
val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
}
}
}

View File

@ -0,0 +1,253 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.IBinder
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.v2.events.*
import pl.szczodrzynski.edziennik.api.v2.events.requests.ServiceCloseRequest
import pl.szczodrzynski.edziennik.api.v2.events.requests.TaskCancelRequest
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask
import pl.szczodrzynski.edziennik.api.v2.events.task.ErrorReportTask
import pl.szczodrzynski.edziennik.api.v2.events.task.IApiTask
import pl.szczodrzynski.edziennik.api.v2.events.task.NotifyTask
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.utils.Utils.d
import kotlin.math.min
import kotlin.math.roundToInt
class ApiService : Service() {
companion object {
const val TAG = "ApiService"
const val NOTIFICATION_API_CHANNEL_ID = "pl.szczodrzynski.edziennik.GET_DATA"
fun start(context: Context) {
context.startService(Intent(context, ApiService::class.java))
}
fun startAndRequest(context: Context, request: Any) {
context.startService(Intent(context, ApiService::class.java))
EventBus.getDefault().postSticky(request)
}
}
private val app by lazy { applicationContext as App }
private val finishingTaskQueue = mutableListOf(
NotifyTask(),
ErrorReportTask()
)
private val taskQueue = mutableListOf<IApiTask>()
private val errorList = mutableListOf<ApiError>()
private var serviceClosed = false
private var taskCancelled = false
private var taskIsRunning = false
private var taskRunning: IApiTask? = null // for debug purposes
private var taskRunningId = -1
private var taskMaximumId = 0
private var taskProfileId = -1
private var taskProgress = -1f
private var taskProgressText: String? = null
private val notification by lazy { EdziennikNotification(this) }
/* ______ _ _ _ _ _____ _ _ _ _
| ____| | | (_) (_) | / ____| | | | | | |
| |__ __| |_____ ___ _ __ _ __ _| | __ | | __ _| | | |__ __ _ ___| | __
| __| / _` |_ / |/ _ \ '_ \| '_ \| | |/ / | | / _` | | | '_ \ / _` |/ __| |/ /
| |___| (_| |/ /| | __/ | | | | | | | < | |___| (_| | | | |_) | (_| | (__| <
|______\__,_/___|_|\___|_| |_|_| |_|_|_|\_\ \_____\__,_|_|_|_.__/ \__,_|\___|_|\*/
private val taskCallback = object : EdziennikCallback {
override fun onCompleted() {
d(TAG, "Task $taskRunningId (profile $taskProfileId) - $taskProgressText - finished")
//if (!taskCancelled) {
EventBus.getDefault().post(ApiTaskFinishedEvent(taskProfileId))
//}
taskIsRunning = false
taskRunningId = -1
taskRunning = null
taskProfileId = -1
taskProgress = -1f
taskProgressText = null
notification.setIdle().post()
runTask()
}
override fun onError(apiError: ApiError) {
d(TAG, "Task $taskRunningId threw an error - $apiError")
apiError.profileId = taskProfileId
EventBus.getDefault().post(ApiTaskErrorEvent(apiError))
errorList.add(apiError)
apiError.throwable?.printStackTrace()
if (apiError.isCritical) {
taskRunning?.cancel()
notification.setCriticalError().post()
taskRunning = null
taskIsRunning = false
taskRunningId = -1
runTask()
}
else {
notification.addError().post()
}
}
override fun onProgress(step: Float) {
if (step <= 0)
return
if (taskProgress < 0)
taskProgress = 0f
taskProgress += step
taskProgress = min(100f, taskProgress)
d(TAG, "Task $taskRunningId progress: ${taskProgress.roundToInt()}%")
EventBus.getDefault().post(ApiTaskProgressEvent(taskProfileId, taskProgress, taskProgressText))
notification.setProgress(taskProgress).post()
}
override fun onStartProgress(stringRes: Int) {
taskProgressText = getString(stringRes)
d(TAG, "Task $taskRunningId progress: $taskProgressText")
EventBus.getDefault().post(ApiTaskProgressEvent(taskProfileId, taskProgress, taskProgressText))
notification.setProgressText(taskProgressText).post()
}
}
/* _______ _ _ _
|__ __| | | | | (_)
| | __ _ ___| | __ _____ _____ ___ _ _| |_ _ ___ _ __
| |/ _` / __| |/ / / _ \ \/ / _ \/ __| | | | __| |/ _ \| '_ \
| | (_| \__ \ < | __/> < __/ (__| |_| | |_| | (_) | | | |
|_|\__,_|___/_|\_\ \___/_/\_\___|\___|\__,_|\__|_|\___/|_| |*/
private fun runTask() {
if (taskIsRunning)
return
if (taskCancelled || serviceClosed || (taskQueue.isEmpty() && finishingTaskQueue.isEmpty())) {
serviceClosed = false
allCompleted()
return
}
val task = if (taskQueue.isEmpty()) finishingTaskQueue.removeAt(0) else taskQueue.removeAt(0)
task.taskId = ++taskMaximumId
task.prepare(app)
taskIsRunning = true
taskRunningId = task.taskId
taskRunning = task
taskProfileId = task.profileId
taskProgress = -1f
taskProgressText = task.taskName
d(TAG, "Executing task $taskRunningId ($taskProgressText) - $task")
// update the notification
notification.setCurrentTask(taskRunningId, taskProgressText).post()
// post an event
EventBus.getDefault().post(ApiTaskStartedEvent(taskProfileId, task.profile))
try {
when (task) {
is EdziennikTask -> task.run(app, taskCallback)
is NotifyTask -> task.run(app, taskCallback)
is ErrorReportTask -> task.run(app, taskCallback, notification, errorList)
}
} catch (e: Exception) {
taskCallback.onError(ApiError(TAG, EXCEPTION_API_TASK).withThrowable(e))
}
}
private fun allCompleted() {
EventBus.getDefault().post(ApiTaskAllFinishedEvent())
stopSelf()
}
/* ______ _ ____
| ____| | | | _ \
| |____ _____ _ __ | |_| |_) |_ _ ___
| __\ \ / / _ \ '_ \| __| _ <| | | / __|
| |___\ V / __/ | | | |_| |_) | |_| \__ \
|______\_/ \___|_| |_|\__|____/ \__,_|__*/
@Subscribe(sticky = true, threadMode = ThreadMode.ASYNC)
fun onApiTask(task: IApiTask) {
EventBus.getDefault().removeStickyEvent(task)
d(TAG, task.toString())
if (task is EdziennikTask) {
when (task.request) {
is EdziennikTask.SyncRequest -> app.db.profileDao().idsForSyncNow.forEach {
taskQueue += EdziennikTask.syncProfile(it)
}
is EdziennikTask.SyncProfileListRequest -> task.request.profileList.forEach {
taskQueue += EdziennikTask.syncProfile(it)
}
else -> {
taskQueue += task
}
}
}
else {
taskQueue += task
}
d(TAG, "EventBus received an IApiTask: $task")
d(TAG, "Current queue:")
taskQueue.forEach {
d(TAG, " - $it")
}
runTask()
}
@Subscribe(sticky = true, threadMode = ThreadMode.ASYNC)
fun onTaskCancelRequest(request: TaskCancelRequest) {
EventBus.getDefault().removeStickyEvent(request)
d(TAG, request.toString())
taskCancelled = true
taskRunning?.cancel()
}
@Subscribe(sticky = true, threadMode = ThreadMode.ASYNC)
fun onServiceCloseRequest(request: ServiceCloseRequest) {
EventBus.getDefault().removeStickyEvent(request)
d(TAG, request.toString())
serviceClosed = true
taskCancelled = true
taskRunning?.cancel()
stopSelf()
}
/* _____ _ _ _
/ ____| (_) (_) | |
| (___ ___ _ ____ ___ ___ ___ _____ _____ _ __ _ __ _ __| | ___ ___
\___ \ / _ \ '__\ \ / / |/ __/ _ \ / _ \ \ / / _ \ '__| '__| |/ _` |/ _ \/ __|
____) | __/ | \ V /| | (_| __/ | (_) \ V / __/ | | | | | (_| | __/\__ \
|_____/ \___|_| \_/ |_|\___\___| \___/ \_/ \___|_| |_| |_|\__,_|\___||__*/
override fun onCreate() {
d(TAG, "Service created")
EventBus.getDefault().register(this)
notification.setIdle().setCloseAction()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
d(TAG, "Foreground service onStartCommand")
startForeground(EdziennikNotification.NOTIFICATION_ID, notification.notification)
return START_NOT_STICKY
}
override fun onDestroy() {
EventBus.getDefault().unregister(this)
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-19.
*/
package pl.szczodrzynski.edziennik.api.v2
import android.os.Build
import pl.szczodrzynski.edziennik.BuildConfig
const val GET = 0
const val POST = 1
val SYSTEM_USER_AGENT = System.getProperty("http.agent") ?: "Dalvik/2.1.0 Android"
val SERVER_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME} $SYSTEM_USER_AGENT"
const val FAKE_LIBRUS_API = "http://librus.szkolny.eu/api"
const val FAKE_LIBRUS_PORTAL = "http://librus.szkolny.eu"
const val FAKE_LIBRUS_AUTHORIZE = "http://librus.szkolny.eu/authorize.php"
const val FAKE_LIBRUS_LOGIN = "http://librus.szkolny.eu/login_action.php"
const val FAKE_LIBRUS_TOKEN = "http://librus.szkolny.eu/access_token.php"
const val FAKE_LIBRUS_ACCOUNT = "/synergia_accounts_fresh.php?login="
const val FAKE_LIBRUS_ACCOUNTS = "/synergia_accounts.php"
val LIBRUS_USER_AGENT = "$SYSTEM_USER_AGENT LibrusMobileApp"
const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0"
const val LIBRUS_CLIENT_ID = "wmSyUMo8llDAs4y9tJVYY92oyZ6h4lAt7KCuy0Gv"
const val LIBRUS_REDIRECT_URL = "http://localhost/bar"
const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/oauth2/authorize?client_id=$LIBRUS_CLIENT_ID&redirect_uri=$LIBRUS_REDIRECT_URL&response_type=code"
const val LIBRUS_LOGIN_URL = "https://portal.librus.pl/rodzina/login/action"
const val LIBRUS_TOKEN_URL = "https://portal.librus.pl/oauth2/access_token"
const val LIBRUS_ACCOUNT_URL = "/v2/SynergiaAccounts/fresh/" // + login
const val LIBRUS_ACCOUNTS_URL = "/v2/SynergiaAccounts"
/** https://api.librus.pl/2.0 */
const val LIBRUS_API_URL = "https://api.librus.pl/2.0"
/** https://portal.librus.pl/api */
const val LIBRUS_PORTAL_URL = "https://portal.librus.pl/api"
/** https://api.librus.pl/OAuth/Token */
const val LIBRUS_API_TOKEN_URL = "https://api.librus.pl/OAuth/Token"
/** https://api.librus.pl/OAuth/TokenJST */
const val LIBRUS_API_TOKEN_JST_URL = "https://api.librus.pl/OAuth/TokenJST"
const val LIBRUS_API_AUTHORIZATION = "Mjg6ODRmZGQzYTg3YjAzZDNlYTZmZmU3NzdiNThiMzMyYjE="
const val LIBRUS_API_SECRET_JST = "18b7c1ee08216f636a1b1a2440e68398"
const val LIBRUS_API_CLIENT_ID_JST = "49"
//const val LIBRUS_API_CLIENT_ID_JST_REFRESH = "42"
const val LIBRUS_JST_DEMO_CODE = "68656A21"
const val LIBRUS_JST_DEMO_PIN = "1290"
const val LIBRUS_SYNERGIA_URL = "https://synergia.librus.pl"
/** https://synergia.librus.pl/loguj/token/TOKEN/przenies */
const val LIBRUS_SYNERGIA_TOKEN_LOGIN_URL = "https://synergia.librus.pl/loguj/token/TOKEN/przenies"
const val LIBRUS_MESSAGES_URL = "https://wiadomosci.librus.pl/module"
const val LIBRUS_SANDBOX_URL = "https://sandbox.librus.pl/index.php?action="
const val IDZIENNIK_USER_AGENT = SYNERGIA_USER_AGENT
const val IDZIENNIK_WEB_URL = "https://iuczniowie.progman.pl/idziennik"
const val IDZIENNIK_WEB_LOGIN = "login.aspx"
const val IDZIENNIK_WEB_SETTINGS = "mod_panelRodzica/Ustawienia.aspx"
const val IDZIENNIK_WEB_TIMETABLE = "mod_panelRodzica/plan/WS_Plan.asmx/pobierzPlanZajec"
const val IDZIENNIK_WEB_GRADES = "mod_panelRodzica/oceny/WS_ocenyUcznia.asmx/pobierzOcenyUcznia"
const val IDZIENNIK_WEB_MISSING_GRADES = "mod_panelRodzica/brak_ocen/WS_BrakOcenUcznia.asmx/pobierzBrakujaceOcenyUcznia"
const val IDZIENNIK_WEB_EXAMS = "mod_panelRodzica/sprawdziany/mod_sprawdzianyPanel.asmx/pobierzListe"
const val IDZIENNIK_WEB_NOTICES = "mod_panelRodzica/uwagi/WS_uwagiUcznia.asmx/pobierzUwagiUcznia"
const val IDZIENNIK_WEB_ATTENDANCE = "mod_panelRodzica/obecnosci/WS_obecnosciUcznia.asmx/pobierzObecnosciUcznia"
const val IDZIENNIK_WEB_ANNOUNCEMENTS = "mod_panelRodzica/tabOgl/WS_tablicaOgloszen.asmx/GetOgloszenia"
const val IDZIENNIK_WEB_MESSAGES_LIST = "mod_komunikator/WS_wiadomosci.asmx/PobierzListeWiadomosci"
val IDZIENNIK_API_USER_AGENT = SYSTEM_USER_AGENT
const val IDZIENNIK_API_URL = "https://iuczniowie.progman.pl/idziennik/api"
const val IDZIENNIK_API_CURRENT_REGISTER = "Uczniowie/\$STUDENT_ID/AktualnyDziennik"
const val IDZIENNIK_API_GRADES = "Uczniowie/\$STUDENT_ID/Oceny/" /* + semester */
const val IDZIENNIK_API_MESSAGES_INBOX = "Wiadomosci/Odebrane"
const val IDZIENNIK_API_MESSAGES_SENT = "Wiadomosci/Wyslane"
val MOBIDZIENNIK_USER_AGENT = SYSTEM_USER_AGENT
const val VULCAN_API_USER_AGENT = "MobileUserAgent"
const val VULCAN_API_APP_NAME = "VULCAN-Android-ModulUcznia"
const val VULCAN_API_APP_VERSION = "19.4.1.436"
const val VULCAN_API_PASSWORD = "CE75EA598C7743AD9B0B7328DED85B06"
const val VULCAN_API_PASSWORD_FAKELOG = "012345678901234567890123456789AB"
val VULCAN_API_DEVICE_NAME = "Szkolny.eu ${Build.MODEL}"
const val VULCAN_API_ENDPOINT_CERTIFICATE = "mobile-api/Uczen.v3.UczenStart/Certyfikat"
const val VULCAN_API_ENDPOINT_STUDENT_LIST = "mobile-api/Uczen.v3.UczenStart/ListaUczniow"
const val VULCAN_API_ENDPOINT_DICTIONARIES = "mobile-api/Uczen.v3.Uczen/Slowniki"
const val VULCAN_API_ENDPOINT_TIMETABLE = "mobile-api/Uczen.v3.Uczen/PlanLekcjiZeZmianami"
const val VULCAN_API_ENDPOINT_GRADES = "mobile-api/Uczen.v3.Uczen/Oceny"
const val VULCAN_API_ENDPOINT_GRADES_PROPOSITIONS = "mobile-api/Uczen.v3.Uczen/OcenyPodsumowanie"
const val VULCAN_API_ENDPOINT_EVENTS = "mobile-api/Uczen.v3.Uczen/Sprawdziany"
const val VULCAN_API_ENDPOINT_HOMEWORK = "mobile-api/Uczen.v3.Uczen/ZadaniaDomowe"
const val VULCAN_API_ENDPOINT_NOTICES = "mobile-api/Uczen.v3.Uczen/UwagiUcznia"
const val VULCAN_API_ENDPOINT_ATTENDANCE = "mobile-api/Uczen.v3.Uczen/Frekwencje"
const val VULCAN_API_ENDPOINT_MESSAGES_RECEIVED = "mobile-api/Uczen.v3.Uczen/WiadomosciOdebrane"
const val VULCAN_API_ENDPOINT_MESSAGES_SENT = "mobile-api/Uczen.v3.Uczen/WiadomosciWyslane"
const val VULCAN_API_ENDPOINT_MESSAGES_CHANGE_STATUS = "mobile-api/Uczen.v3.Uczen/ZmienStatusWiadomosci"
const val VULCAN_API_ENDPOINT_PUSH = "mobile-api/Uczen.v3.Uczen/UstawPushToken"

View File

@ -0,0 +1,210 @@
package pl.szczodrzynski.edziennik.api.v2
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ANNOUNCEMENTS
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ATTENDANCE
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_BEHAVIOUR
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_GRADES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOME
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.*
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.notices.Notice
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_LUCKY_NUMBER
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_ANNOUNCEMENT
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_ATTENDANCE
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_EVENT
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_GRADE
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_MESSAGE
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_NOTICE
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_TIMETABLE_LESSON_CHANGE
import pl.szczodrzynski.edziennik.data.db.modules.notification.getNotificationTitle
import pl.szczodrzynski.edziennik.utils.models.Date
class DataNotifications(val data: Data) {
companion object {
private const val TAG = "DataNotifications"
}
val app = data.app
val profileId = data.profile?.id ?: -1
val profileName = data.profile?.name ?: ""
val profile = data.profile
val loginStore = data.loginStore
init { run {
if (profile == null) {
return@run
}
for (change in app.db.lessonChangeDao().getNotNotifiedNow(profileId)) {
val text = app.getString(R.string.notification_lesson_change_format, change.changeTypeStr(app), if (change.lessonDate == null) "" else change.lessonDate!!.formattedString, change.subjectLongName)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_TIMETABLE_LESSON_CHANGE),
text = text,
type = TYPE_TIMETABLE_LESSON_CHANGE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_TIMETABLE,
addedDate = change.addedDate
).addExtra("timetableDate", change.lessonDate?.value?.toLong())
}
for (event in app.db.eventDao().getNotNotifiedNow(profileId)) {
val text = if (event.type == Event.TYPE_HOMEWORK)
app.getString(
if (event.subjectLongName.isNullOrEmpty())
R.string.notification_homework_no_subject_format
else
R.string.notification_homework_format,
event.subjectLongName,
event.eventDate.formattedString
)
else
app.getString(
if (event.subjectLongName.isNullOrEmpty())
R.string.notification_event_no_subject_format
else
R.string.notification_event_format,
event.typeName,
event.eventDate.formattedString,
event.subjectLongName
)
val type = if (event.type == Event.TYPE_HOMEWORK) TYPE_NEW_HOMEWORK else TYPE_NEW_EVENT
data.notifications += Notification(
title = app.getNotificationTitle(type),
text = text,
type = type,
profileId = profileId,
profileName = profileName,
viewId = if (event.type == Event.TYPE_HOMEWORK) DRAWER_ITEM_HOMEWORK else DRAWER_ITEM_AGENDA,
addedDate = event.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong())
}
val today = Date.getToday()
val todayValue = today.value
profile.currentSemester = profile.dateToSemester(today)
for (grade in app.db.gradeDao().getNotNotifiedNow(profileId)) {
val gradeName = when (grade.type) {
TYPE_SEMESTER1_PROPOSED, TYPE_SEMESTER2_PROPOSED -> app.getString(R.string.grade_semester_proposed_format_2, grade.name)
TYPE_SEMESTER1_FINAL, TYPE_SEMESTER2_FINAL -> app.getString(R.string.grade_semester_final_format_2, grade.name)
TYPE_YEAR_PROPOSED -> app.getString(R.string.grade_year_proposed_format_2, grade.name)
TYPE_YEAR_FINAL -> app.getString(R.string.grade_year_final_format_2, grade.name)
else -> grade.name
}
val text = app.getString(R.string.notification_grade_format, gradeName, grade.subjectLongName)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_GRADE),
text = text,
type = TYPE_NEW_GRADE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_GRADES,
addedDate = grade.addedDate
).addExtra("gradeId", grade.id).addExtra("gradesSubjectId", grade.subjectId)
}
for (notice in app.db.noticeDao().getNotNotifiedNow(profileId)) {
val noticeTypeStr = if (notice.type == Notice.TYPE_POSITIVE) app.getString(R.string.notification_notice_praise) else if (notice.type == Notice.TYPE_NEGATIVE) app.getString(R.string.notification_notice_warning) else app.getString(R.string.notification_notice_new)
val text = app.getString(R.string.notification_notice_format, noticeTypeStr, notice.teacherFullName, Date.fromMillis(notice.addedDate).formattedString)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_NOTICE),
text = text,
type = TYPE_NEW_NOTICE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_BEHAVIOUR,
addedDate = notice.addedDate
).addExtra("noticeId", notice.id)
}
for (attendance in app.db.attendanceDao().getNotNotifiedNow(profileId)) {
var attendanceTypeStr = app.getString(R.string.notification_type_attendance)
when (attendance.type) {
Attendance.TYPE_ABSENT -> attendanceTypeStr = app.getString(R.string.notification_absence)
Attendance.TYPE_ABSENT_EXCUSED -> attendanceTypeStr = app.getString(R.string.notification_absence_excused)
Attendance.TYPE_BELATED -> attendanceTypeStr = app.getString(R.string.notification_belated)
Attendance.TYPE_BELATED_EXCUSED -> attendanceTypeStr = app.getString(R.string.notification_belated_excused)
Attendance.TYPE_RELEASED -> attendanceTypeStr = app.getString(R.string.notification_release)
}
val text = app.getString(
if (attendance.subjectLongName.isNullOrEmpty())
R.string.notification_attendance_no_lesson_format
else
R.string.notification_attendance_format,
attendanceTypeStr,
attendance.subjectLongName,
attendance.lessonDate.formattedString
)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_ATTENDANCE),
text = text,
type = TYPE_NEW_ATTENDANCE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_ATTENDANCE,
addedDate = attendance.addedDate
).addExtra("attendanceId", attendance.id).addExtra("attendanceSubjectId", attendance.subjectId)
}
for (announcement in app.db.announcementDao().getNotNotifiedNow(profileId)) {
val text = app.context.getString(R.string.notification_announcement_format, announcement.subject)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_ANNOUNCEMENT),
text = text,
type = TYPE_NEW_ANNOUNCEMENT,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_ANNOUNCEMENTS,
addedDate = announcement.addedDate
).addExtra("announcementId", announcement.id)
}
for (message in app.db.messageDao().getReceivedNotNotifiedNow(profileId)) {
val text = app.context.getString(R.string.notification_message_format, message.senderFullName, message.subject)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_MESSAGE),
text = text,
type = TYPE_NEW_MESSAGE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_MESSAGES,
addedDate = message.addedDate
).addExtra("messageType", Message.TYPE_RECEIVED.toLong()).addExtra("messageId", message.id)
}
val luckyNumbers = app.db.luckyNumberDao().getNotNotifiedNow(profileId)
luckyNumbers?.removeAll { it.date < today }
luckyNumbers?.forEach { luckyNumber ->
val text = when {
luckyNumber.date.value == todayValue -> // LN for today
app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_format else R.string.notification_lucky_number_format, luckyNumber.number)
luckyNumber.date.value == todayValue + 1 -> // LN for tomorrow
app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_tomorrow_format else R.string.notification_lucky_number_tomorrow_format, luckyNumber.number)
else -> // LN for later
app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_later_format else R.string.notification_lucky_number_later_format, luckyNumber.date.formattedString, luckyNumber.number)
}
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_LUCKY_NUMBER),
text = text,
type = TYPE_LUCKY_NUMBER,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_HOME,
addedDate = luckyNumber.addedDate
)
}
data.db.metadataDao().setAllNotified(profileId, true)
}}
}

View File

@ -0,0 +1,140 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-1.
*/
package pl.szczodrzynski.edziennik.api.v2
import android.app.Notification
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.PRIORITY_MIN
import pl.szczodrzynski.edziennik.R
import kotlin.math.roundToInt
class EdziennikNotification(val context: Context) {
companion object {
const val NOTIFICATION_ID = 20191001
}
private val notificationManager by lazy { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
private val notificationBuilder: NotificationCompat.Builder by lazy {
NotificationCompat.Builder(context, ApiService.NOTIFICATION_API_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setPriority(PRIORITY_MIN)
.setOngoing(true)
.setLocalOnly(true)
}
val notification: Notification
get() = notificationBuilder.build()
private var errorCount = 0
private var criticalErrorCount = 0
private fun cancelPendingIntent(taskId: Int): PendingIntent {
val intent = Intent("pl.szczodrzynski.edziennik.SZKOLNY_MAIN")
intent.putExtra("task", "TaskCancelRequest")
intent.putExtra("taskId", taskId)
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT) as PendingIntent
}
private val closePendingIntent: PendingIntent
get() {
val intent = Intent("pl.szczodrzynski.edziennik.SZKOLNY_MAIN")
intent.putExtra("task", "ServiceCloseRequest")
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT) as PendingIntent
}
private fun errorCountText(): String? {
var result = ""
if (criticalErrorCount > 0) {
result += context.resources.getQuantityString(R.plurals.critical_errors_format, criticalErrorCount, criticalErrorCount)
}
if (criticalErrorCount > 0 && errorCount > 0) {
result += ", "
}
if (errorCount > 0) {
result += context.resources.getQuantityString(R.plurals.normal_errors_format, errorCount, errorCount)
}
return if (result.isEmpty()) null else result
}
fun setIdle(): EdziennikNotification {
notificationBuilder.setContentTitle(context.getString(R.string.edziennik_notification_api_title))
notificationBuilder.setProgress(0, 0, false)
notificationBuilder.apply {
val str = context.getString(R.string.edziennik_notification_api_text)
setStyle(NotificationCompat.BigTextStyle().bigText(str))
setContentText(str)
}
setCloseAction()
return this
}
fun addError(): EdziennikNotification {
errorCount++
return this
}
fun setCriticalError(): EdziennikNotification {
criticalErrorCount++
notificationBuilder.setContentTitle(context.getString(R.string.edziennik_notification_api_error_title))
notificationBuilder.setProgress(0, 0, false)
notificationBuilder.apply {
val str = errorCountText()
setStyle(NotificationCompat.BigTextStyle().bigText(str))
setContentText(str)
}
setCloseAction()
return this
}
fun setProgress(progress: Float): EdziennikNotification {
notificationBuilder.setProgress(100, progress.roundToInt(), progress < 0f)
return this
}
fun setProgressText(progressText: String?): EdziennikNotification {
notificationBuilder.setContentTitle(progressText)
return this
}
fun setCurrentTask(taskId: Int, progressText: String?): EdziennikNotification {
notificationBuilder.setProgress(100, 0, true)
notificationBuilder.setContentTitle(progressText)
notificationBuilder.apply {
val str = errorCountText()
setStyle(NotificationCompat.BigTextStyle().bigText(str))
setContentText(str)
}
setCancelAction(taskId)
return this
}
fun setCloseAction(): EdziennikNotification {
notificationBuilder.mActions.clear()
notificationBuilder.addAction(
NotificationCompat.Action(
R.drawable.ic_notification,
context.getString(R.string.edziennik_notification_api_close),
closePendingIntent
))
return this
}
private fun setCancelAction(taskId: Int) {
notificationBuilder.mActions.clear()
notificationBuilder.addAction(
NotificationCompat.Action(
R.drawable.ic_notification,
context.getString(R.string.edziennik_notification_api_cancel),
cancelPendingIntent(taskId)
))
}
fun post() {
notificationManager.notify(NOTIFICATION_ID, notification)
}
}

View File

@ -0,0 +1,83 @@
package pl.szczodrzynski.edziennik.api.v2
import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.api.v2.models.Feature
import pl.szczodrzynski.edziennik.api.v2.models.LoginMethod
import pl.szczodrzynski.edziennik.data.db.modules.api.EndpointTimer
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_NEVER
fun Data.prepare(loginMethods: List<LoginMethod>, features: List<Feature>, featureIds: List<Int>, viewId: Int?) {
val data = this
val possibleLoginMethods = data.loginMethods.toMutableList()
for (loginMethod in loginMethods) {
if (loginMethod.isPossible(profile, loginStore))
possibleLoginMethods += loginMethod.loginMethodId
}
//var highestLoginMethod = 0
var endpointList = mutableListOf<Feature>()
val requiredLoginMethods = mutableListOf<Int>()
data.targetEndpointIds.clear()
data.targetLoginMethodIds.clear()
// get all endpoints for every feature, only if possible to login and possible/necessary to sync
for (featureId in featureIds) {
features.filter {
it.featureId == featureId // feature ID matches
&& possibleLoginMethods.containsAll(it.requiredLoginMethods) // is possible to login
&& it.shouldSync?.invoke(data) ?: true // is necessary/possible to sync
}.let {
endpointList.addAll(it)
}
}
val timestamp = System.currentTimeMillis()
endpointList = endpointList
// sort the endpoint list by feature ID and priority
.sortedWith(compareBy(Feature::featureId, Feature::priority))
// select only the most important endpoint for each feature
.distinctBy { it.featureId }
.toMutableList()
// add all endpoint IDs and required login methods, filtering using timers
.onEach { feature ->
feature.endpointIds.forEach { endpoint ->
(data.endpointTimers
.singleOrNull { it.endpointId == endpoint.first } ?: EndpointTimer(data.profile?.id ?: -1, endpoint.first))
.let { timer ->
if (timer.nextSync == SYNC_ALWAYS ||
(viewId != null && timer.viewId == viewId) ||
(timer.nextSync != SYNC_NEVER && timer.nextSync < timestamp)) {
data.targetEndpointIds.add(endpoint.first)
requiredLoginMethods.add(endpoint.second)
}
}
}
}
// check every login method for any dependencies
for (loginMethodId in requiredLoginMethods) {
var requiredLoginMethod: Int? = loginMethodId
while (requiredLoginMethod != LOGIN_METHOD_NOT_NEEDED) {
loginMethods.singleOrNull { it.loginMethodId == requiredLoginMethod }?.let { loginMethod ->
if (requiredLoginMethod != null)
data.targetLoginMethodIds.add(requiredLoginMethod!!)
requiredLoginMethod = loginMethod.requiredLoginMethod(data.profile, data.loginStore)
}
}
}
// sort and distinct every login method and endpoint
data.targetLoginMethodIds = data.targetLoginMethodIds.toHashSet().toMutableList()
data.targetLoginMethodIds.sort()
data.targetEndpointIds = data.targetEndpointIds.toHashSet().toMutableList()
data.targetEndpointIds.sort()
progressCount = targetLoginMethodIds.size + targetEndpointIds.size
progressStep = if (progressCount <= 0) 0f else 100f / progressCount.toFloat()
}

View File

@ -0,0 +1,176 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2
/*const val CODE_OTHER = 0
const val CODE_OK = 1
const val CODE_NO_INTERNET = 10
const val CODE_SSL_ERROR = 13
const val CODE_ARCHIVED = 5
const val CODE_MAINTENANCE = 6
const val CODE_LOGIN_ERROR = 7
const val CODE_ACCOUNT_MISMATCH = 8
const val CODE_APP_SERVER_ERROR = 9
const val CODE_MULTIACCOUNT_SETUP = 12
const val CODE_TIMEOUT = 11
const val CODE_PROFILE_NOT_FOUND = 14
const val CODE_ATTACHMENT_NOT_AVAILABLE = 28
const val CODE_INVALID_LOGIN = 2
const val CODE_INVALID_SERVER_ADDRESS = 21
const val CODE_INVALID_SCHOOL_NAME = 22
const val CODE_INVALID_DEVICE = 23
const val CODE_OLD_PASSWORD = 4
const val CODE_INVALID_TOKEN = 24
const val CODE_EXPIRED_TOKEN = 27
const val CODE_INVALID_SYMBOL = 25
const val CODE_INVALID_PIN = 26
const val CODE_LIBRUS_NOT_ACTIVATED = 29
const val CODE_SYNERGIA_NOT_ACTIVATED = 32
const val CODE_LIBRUS_DISCONNECTED = 31
const val CODE_PROFILE_ARCHIVED = 30*/
const val ERROR_REQUEST_FAILURE = 50
const val ERROR_REQUEST_HTTP_400 = 51
const val ERROR_REQUEST_HTTP_401 = 52
const val ERROR_REQUEST_HTTP_403 = 53
const val ERROR_REQUEST_HTTP_404 = 54
const val ERROR_REQUEST_HTTP_405 = 55
const val ERROR_REQUEST_HTTP_410 = 56
const val ERROR_REQUEST_HTTP_500 = 57
const val ERROR_REQUEST_FAILURE_HOSTNAME_NOT_FOUND = 60
const val ERROR_REQUEST_FAILURE_TIMEOUT = 61
const val ERROR_REQUEST_FAILURE_NO_INTERNET = 62
const val ERROR_RESPONSE_EMPTY = 100
const val ERROR_LOGIN_DATA_MISSING = 101
const val ERROR_PROFILE_MISSING = 105
const val ERROR_INVALID_LOGIN_MODE = 110
const val ERROR_LOGIN_METHOD_NOT_SATISFIED = 111
const val ERROR_NOT_IMPLEMENTED = 112
const val ERROR_NO_STUDENTS_IN_ACCOUNT = 115
const val CODE_INTERNAL_LIBRUS_ACCOUNT_410 = 120
const val CODE_INTERNAL_LIBRUS_SYNERGIA_EXPIRED = 121
const val ERROR_LOGIN_LIBRUS_API_CAPTCHA_NEEDED = 124
const val ERROR_LOGIN_LIBRUS_API_CONNECTION_PROBLEMS = 125
const val ERROR_LOGIN_LIBRUS_API_INVALID_CLIENT = 126
const val ERROR_LOGIN_LIBRUS_API_REG_ACCEPT_NEEDED = 127
const val ERROR_LOGIN_LIBRUS_API_CHANGE_PASSWORD_ERROR = 128
const val ERROR_LOGIN_LIBRUS_API_PASSWORD_CHANGE_REQUIRED = 129
const val ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN = 130
const val ERROR_LOGIN_LIBRUS_API_OTHER = 131
const val ERROR_LOGIN_LIBRUS_PORTAL_CSRF_MISSING = 132
const val ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED = 133
const val ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR = 134
const val ERROR_LOGIN_LIBRUS_PORTAL_SYNERGIA_TOKEN_MISSING = 139
const val ERROR_LIBRUS_API_TOKEN_EXPIRED = 140
const val ERROR_LIBRUS_API_INSUFFICIENT_SCOPES = 141
const val ERROR_LIBRUS_API_OTHER = 142
const val ERROR_LIBRUS_API_ACCESS_DENIED = 143
const val ERROR_LIBRUS_API_RESOURCE_NOT_FOUND = 144
const val ERROR_LIBRUS_API_DATA_NOT_FOUND = 145
const val ERROR_LIBRUS_API_TIMETABLE_NOT_PUBLIC = 146
const val ERROR_LIBRUS_API_RESOURCE_ACCESS_DENIED = 147
const val ERROR_LIBRUS_API_INVALID_REQUEST_PARAMS = 148
const val ERROR_LIBRUS_API_INCORRECT_ENDPOINT = 149
const val ERROR_LIBRUS_API_LUCKY_NUMBER_NOT_ACTIVE = 150
const val ERROR_LIBRUS_API_NOTES_NOT_ACTIVE = 151
const val ERROR_LOGIN_LIBRUS_SYNERGIA_NO_TOKEN = 152
const val ERROR_LOGIN_LIBRUS_SYNERGIA_TOKEN_INVALID = 153
const val ERROR_LOGIN_LIBRUS_SYNERGIA_NO_SESSION_ID = 154
const val ERROR_LIBRUS_MESSAGES_ACCESS_DENIED = 155
const val ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED = 156
const val ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID = 157
const val ERROR_LIBRUS_PORTAL_ACCESS_DENIED = 158
const val ERROR_LIBRUS_PORTAL_API_DISABLED = 159
const val ERROR_LIBRUS_PORTAL_SYNERGIA_DISCONNECTED = 160
const val ERROR_LIBRUS_PORTAL_OTHER = 161
const val ERROR_LIBRUS_PORTAL_SYNERGIA_NOT_FOUND = 162
const val ERROR_LOGIN_LIBRUS_PORTAL_OTHER = 163
const val ERROR_LOGIN_LIBRUS_PORTAL_CODE_EXPIRED = 164
const val ERROR_LOGIN_LIBRUS_PORTAL_CODE_REVOKED = 165
const val ERROR_LOGIN_LIBRUS_PORTAL_NO_CLIENT_ID = 166
const val ERROR_LOGIN_LIBRUS_PORTAL_NO_CODE = 167
const val ERROR_LOGIN_LIBRUS_PORTAL_NO_REFRESH = 168
const val ERROR_LOGIN_LIBRUS_PORTAL_NO_REDIRECT = 169
const val ERROR_LOGIN_LIBRUS_PORTAL_UNSUPPORTED_GRANT = 170
const val ERROR_LOGIN_LIBRUS_PORTAL_INVALID_CLIENT_ID = 171
const val ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_INVALID = 172
const val ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED = 173
const val ERROR_LIBRUS_SYNERGIA_OTHER = 174
const val ERROR_LIBRUS_SYNERGIA_MAINTENANCE = 175
const val ERROR_LIBRUS_MESSAGES_MAINTENANCE = 176
const val ERROR_LIBRUS_MESSAGES_ERROR = 177
const val ERROR_LIBRUS_MESSAGES_OTHER = 178
const val ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN = 179
const val ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN = 180
const val ERROR_LIBRUS_API_MAINTENANCE = 181
const val ERROR_LIBRUS_PORTAL_MAINTENANCE = 182
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD = 202
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_DEVICE = 203
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_ARCHIVED = 204
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_MAINTENANCE = 205
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_ADDRESS = 206
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OTHER = 210
const val ERROR_MOBIDZIENNIK_WEB_ACCESS_DENIED = 211
const val ERROR_MOBIDZIENNIK_WEB_NO_SESSION_KEY = 212
const val ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE = 216
const val ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID = 213
const val ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE = 214
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID = 215
const val ERROR_LOGIN_VULCAN_INVALID_SYMBOL = 301
const val ERROR_LOGIN_VULCAN_INVALID_TOKEN = 302
const val ERROR_LOGIN_VULCAN_INVALID_PIN = 309
const val ERROR_LOGIN_VULCAN_INVALID_PIN_0_REMAINING = 310
const val ERROR_LOGIN_VULCAN_INVALID_PIN_1_REMAINING = 311
const val ERROR_LOGIN_VULCAN_INVALID_PIN_2_REMAINING = 312
const val ERROR_LOGIN_VULCAN_EXPIRED_TOKEN = 321
const val ERROR_LOGIN_VULCAN_OTHER = 322
const val ERROR_LOGIN_VULCAN_ONLY_KINDERGARTEN = 330
const val ERROR_LOGIN_VULCAN_NO_PUPILS = 331
const val ERROR_VULCAN_API_MAINTENANCE = 340
const val ERROR_VULCAN_API_BAD_REQUEST = 341
const val ERROR_VULCAN_API_OTHER = 342
const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN = 401
const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME = 402
const val ERROR_LOGIN_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED = 403
const val ERROR_LOGIN_IDZIENNIK_WEB_MAINTENANCE = 404
const val ERROR_LOGIN_IDZIENNIK_WEB_SERVER_ERROR = 405
const val ERROR_LOGIN_IDZIENNIK_WEB_OTHER = 410
const val ERROR_LOGIN_IDZIENNIK_WEB_API_NO_ACCESS = 411 /* {"d":{"__type":"mds.Web.mod_komunikator.WS_mod_wiadomosci+detailWiadomosci","Wiadomosc":{"_recordId":0,"DataNadania":null,"DataOdczytania":null,"Nadawca":null,"ListaOdbiorcow":[],"Tytul":null,"Text":null,"ListaZal":[]},"Bledy":{"__type":"mds.Module.Globalne+sBledy","CzyJestBlad":true,"ListaBledow":["Nie masz dostępu do tych zasobów!"],"ListaKodowBledow":[]},"czyJestWiecej":false}} */
const val ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION = 420
const val ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH = 421
const val ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER = 422
const val ERROR_IDZIENNIK_WEB_ACCESS_DENIED = 430
const val ERROR_IDZIENNIK_WEB_OTHER = 431
const val ERROR_IDZIENNIK_WEB_MAINTENANCE = 432
const val ERROR_IDZIENNIK_WEB_SERVER_ERROR = 433
const val ERROR_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED = 434
const val ERROR_LOGIN_IDZIENNIK_FIRST_NO_SCHOOL_YEAR = 440
const val ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA = 441
const val ERROR_IDZIENNIK_API_ACCESS_DENIED = 450
const val ERROR_IDZIENNIK_API_OTHER = 451
const val ERROR_TEMPLATE_WEB_OTHER = 801
const val EXCEPTION_API_TASK = 900
const val EXCEPTION_LOGIN_LIBRUS_API_TOKEN = 901
const val EXCEPTION_LOGIN_LIBRUS_PORTAL_TOKEN = 902
const val EXCEPTION_LIBRUS_PORTAL_SYNERGIA_TOKEN = 903
const val EXCEPTION_LIBRUS_API_REQUEST = 904
const val EXCEPTION_LIBRUS_SYNERGIA_REQUEST = 905
const val EXCEPTION_MOBIDZIENNIK_WEB_REQUEST = 906
const val EXCEPTION_VULCAN_API_REQUEST = 907
const val EXCEPTION_NOTIFY_AND_SYNC = 910
const val EXCEPTION_LIBRUS_MESSAGES_REQUEST = 911
const val EXCEPTION_IDZIENNIK_WEB_REQUEST = 912
const val EXCEPTION_IDZIENNIK_WEB_API_REQUEST = 913
const val EXCEPTION_IDZIENNIK_API_REQUEST = 914
const val LOGIN_NO_ARGUMENTS = 1201

View File

@ -0,0 +1,85 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-29.
*/
package pl.szczodrzynski.edziennik.api.v2
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ANNOUNCEMENTS
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ATTENDANCE
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_BEHAVIOUR
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_GRADES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOME
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT
internal const val FEATURE_TIMETABLE = 1
internal const val FEATURE_AGENDA = 2
internal const val FEATURE_GRADES = 3
internal const val FEATURE_HOMEWORK = 4
internal const val FEATURE_BEHAVIOUR = 5
internal const val FEATURE_ATTENDANCE = 6
internal const val FEATURE_MESSAGES_INBOX = 7
internal const val FEATURE_MESSAGES_SENT = 8
internal const val FEATURE_ANNOUNCEMENTS = 9
internal const val FEATURE_ALWAYS_NEEDED = 100
internal const val FEATURE_STUDENT_INFO = 101
internal const val FEATURE_STUDENT_NUMBER = 109
internal const val FEATURE_SCHOOL_INFO = 102
internal const val FEATURE_CLASS_INFO = 103
internal const val FEATURE_TEAM_INFO = 104
internal const val FEATURE_LUCKY_NUMBER = 105
internal const val FEATURE_TEACHERS = 106
internal const val FEATURE_SUBJECTS = 107
internal const val FEATURE_CLASSROOMS = 108
internal const val FEATURE_PUSH_CONFIG = 120
object Features {
private fun getAllNecessary(): List<Int> = listOf(
FEATURE_ALWAYS_NEEDED,
FEATURE_STUDENT_INFO,
FEATURE_STUDENT_NUMBER,
FEATURE_SCHOOL_INFO,
FEATURE_CLASS_INFO,
FEATURE_TEAM_INFO,
FEATURE_LUCKY_NUMBER,
FEATURE_TEACHERS,
FEATURE_SUBJECTS,
FEATURE_CLASSROOMS)
private fun getAllFeatures(): List<Int> = listOf(
FEATURE_TIMETABLE,
FEATURE_AGENDA,
FEATURE_GRADES,
FEATURE_HOMEWORK,
FEATURE_BEHAVIOUR,
FEATURE_ATTENDANCE,
FEATURE_MESSAGES_INBOX,
FEATURE_MESSAGES_SENT,
FEATURE_ANNOUNCEMENTS)
fun getAllIds(): List<Int> = getAllFeatures() + getAllNecessary()
fun getIdsByView(targetId: Int, targetType: Int): List<Int> {
return (when (targetId) {
DRAWER_ITEM_HOME -> getAllFeatures()
DRAWER_ITEM_TIMETABLE -> listOf(FEATURE_TIMETABLE)
DRAWER_ITEM_AGENDA -> listOf(FEATURE_AGENDA)
DRAWER_ITEM_GRADES -> listOf(FEATURE_GRADES)
DRAWER_ITEM_MESSAGES -> when (targetType) {
TYPE_RECEIVED -> listOf(FEATURE_MESSAGES_INBOX)
TYPE_SENT -> listOf(FEATURE_MESSAGES_SENT)
else -> listOf(FEATURE_MESSAGES_INBOX, FEATURE_MESSAGES_SENT)
}
DRAWER_ITEM_HOMEWORK -> listOf(FEATURE_HOMEWORK)
DRAWER_ITEM_BEHAVIOUR -> listOf(FEATURE_BEHAVIOUR)
DRAWER_ITEM_ATTENDANCE -> listOf(FEATURE_ATTENDANCE)
DRAWER_ITEM_ANNOUNCEMENTS -> listOf(FEATURE_ANNOUNCEMENTS)
else -> getAllFeatures()
} + getAllNecessary()).sorted()
}
}

View File

@ -0,0 +1,141 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-20.
*/
package pl.szczodrzynski.edziennik.api.v2
import pl.szczodrzynski.edziennik.api.v2.idziennik.login.IdziennikLoginApi
import pl.szczodrzynski.edziennik.api.v2.idziennik.login.IdziennikLoginWeb
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginApi
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginMessages
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginPortal
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginSynergia
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.login.MobidziennikLoginWeb
import pl.szczodrzynski.edziennik.api.v2.models.LoginMethod
import pl.szczodrzynski.edziennik.api.v2.template.login.TemplateLoginApi
import pl.szczodrzynski.edziennik.api.v2.template.login.TemplateLoginWeb
import pl.szczodrzynski.edziennik.api.v2.vulcan.login.VulcanLoginApi
// librus
// mobidziennik
// idziennik
// vulcan
// mobireg
const val SYNERGIA_API_ENABLED = true
const val LOGIN_TYPE_IDZIENNIK = 3
const val LOGIN_TYPE_TEMPLATE = 21
// LOGIN MODES
const val LOGIN_MODE_IDZIENNIK_WEB = 0
const val LOGIN_MODE_TEMPLATE_WEB = 0
// LOGIN METHODS
const val LOGIN_METHOD_NOT_NEEDED = -1
const val LOGIN_METHOD_IDZIENNIK_WEB = 100
const val LOGIN_METHOD_IDZIENNIK_API = 200
const val LOGIN_METHOD_TEMPLATE_WEB = 100
const val LOGIN_METHOD_TEMPLATE_API = 200
const val LOGIN_TYPE_LIBRUS = 2
const val LOGIN_MODE_LIBRUS_EMAIL = 0
const val LOGIN_MODE_LIBRUS_SYNERGIA = 1
const val LOGIN_MODE_LIBRUS_JST = 2
const val LOGIN_METHOD_LIBRUS_PORTAL = 100
const val LOGIN_METHOD_LIBRUS_API = 200
const val LOGIN_METHOD_LIBRUS_SYNERGIA = 300
const val LOGIN_METHOD_LIBRUS_MESSAGES = 400
val librusLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_PORTAL, LibrusLoginPortal::class.java)
.withIsPossible { _, loginStore ->
loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL
}
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED },
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_API, LibrusLoginApi::class.java)
.withIsPossible { _, loginStore ->
loginStore.mode != LOGIN_MODE_LIBRUS_SYNERGIA || SYNERGIA_API_ENABLED
}
.withRequiredLoginMethod { _, loginStore ->
if (loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL) LOGIN_METHOD_LIBRUS_PORTAL else LOGIN_METHOD_NOT_NEEDED
},
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_SYNERGIA, LibrusLoginSynergia::class.java)
.withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") }
.withRequiredLoginMethod { profile, _ ->
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED
},
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_MESSAGES, LibrusLoginMessages::class.java)
.withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") }
.withRequiredLoginMethod { profile, _ ->
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED
}
)
const val LOGIN_TYPE_MOBIDZIENNIK = 1
const val LOGIN_MODE_MOBIDZIENNIK_WEB = 0
const val LOGIN_METHOD_MOBIDZIENNIK_WEB = 100
const val LOGIN_METHOD_MOBIDZIENNIK_API2 = 300
val mobidziennikLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_MOBIDZIENNIK, LOGIN_METHOD_MOBIDZIENNIK_WEB, MobidziennikLoginWeb::class.java)
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }/*,
LoginMethod(LOGIN_TYPE_MOBIDZIENNIK, LOGIN_METHOD_MOBIDZIENNIK_API2, MobidziennikLoginApi2::class.java)
.withIsPossible { _, loginStore -> loginStore.hasLoginData("email") }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }*/
)
const val LOGIN_TYPE_VULCAN = 4
const val LOGIN_MODE_VULCAN_API = 0
const val LOGIN_MODE_VULCAN_WEB = 1
const val LOGIN_METHOD_VULCAN_WEB_MAIN = 100
const val LOGIN_METHOD_VULCAN_WEB_NEW = 200
const val LOGIN_METHOD_VULCAN_WEB_OLD = 300
const val LOGIN_METHOD_VULCAN_WEB_MESSAGES = 400
const val LOGIN_METHOD_VULCAN_API = 500
val vulcanLoginMethods = listOf(
/*LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_MAIN, VulcanLoginWebMain::class.java)
.withIsPossible { _, _ -> false }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED },
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_NEW, VulcanLoginWebNew::class.java)
.withIsPossible { _, _ -> false }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_VULCAN_WEB_MAIN },
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_OLD, VulcanLoginWebOld::class.java)
.withIsPossible { _, _ -> false }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_VULCAN_WEB_MAIN },*/
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_API, VulcanLoginApi::class.java)
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { _, loginStore ->
if (loginStore.mode == LOGIN_MODE_VULCAN_WEB) LOGIN_METHOD_VULCAN_WEB_NEW else LOGIN_METHOD_NOT_NEEDED
}
)
val idziennikLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_IDZIENNIK, LOGIN_METHOD_IDZIENNIK_WEB, IdziennikLoginWeb::class.java)
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED },
LoginMethod(LOGIN_TYPE_IDZIENNIK, LOGIN_METHOD_IDZIENNIK_API, IdziennikLoginApi::class.java)
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_IDZIENNIK_WEB }
)
val templateLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_TEMPLATE, LOGIN_METHOD_TEMPLATE_WEB, TemplateLoginWeb::class.java)
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED },
LoginMethod(LOGIN_TYPE_TEMPLATE, LOGIN_METHOD_TEMPLATE_API, TemplateLoginApi::class.java)
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_TEMPLATE_WEB }
)

View File

@ -0,0 +1,79 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-6.
*/
package pl.szczodrzynski.edziennik.api.v2
object Regexes {
val MOBIDZIENNIK_GRADES_SUBJECT_NAME by lazy {
"""<div.*?>\n*\s*(.+?)\s*\n*(?:<.*?)??</div>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_GRADES_COLOR by lazy {
"""background-color:([#A-Fa-f0-9]+);""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_GRADES_CATEGORY by lazy {
""">&nbsp;(.+?):</span>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_GRADES_CLASS_AVERAGE by lazy {
"""Średnia ocen:.*<strong>([0-9]*\.?[0-9]*)</strong>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_GRADES_ADDED_DATE by lazy {
"""Wpisano:.*<strong>.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)</strong>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_GRADES_COUNT_TO_AVG by lazy {
"""Liczona do średniej:.*?<strong>nie<br/?></strong>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_GRADES_DETAILS by lazy {
"""<strong.*?>(.+?)</strong>.*?<sup>.+?</sup>.*?<small>\((.+?)\)</small>.*?<span>.*?Wartość oceny:.*?<strong>([0-9.]+)</strong>.*?Wpisał\(a\):.*?<strong>(.+?)</strong>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_EVENT_TYPE by lazy {
"""\(([0-9A-ząęóżźńśłć]*?)\)$""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_LUCKY_NUMBER by lazy {
"""class="szczesliwy_numerek".*>0*([0-9]+)(?:/0*[0-9]+)*</a>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_CLASS_CALENDAR by lazy {
"""events: (.+),$""".toRegex(RegexOption.MULTILINE)
}
val MOBIDZIENNIK_MESSAGE_READ_DATE by lazy {
"""czas przeczytania:.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_MESSAGE_SENT_READ_DATE by lazy {
""".+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_MESSAGE_ATTACHMENT by lazy {
"""href="https://.+?\.mobidziennik.pl/.+?&(?:amp;)?zalacznik=([0-9]+)"(?:.+?<small.+?\(([0-9.]+)\s(M|K|G|)B\))*""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy {
"""<input type="hidden".+?name="([A-z0-9_]+)?".+?value="([A-z0-9_+-/=]+)?".+?>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_ERROR by lazy {
"""id="spanErrorMessage">(.*?)</""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_FIRST_ACCOUNT_NAME by lazy {
"""Imię i nazwisko:.+?">(.+?)</div>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_FIRST_IS_PARENT by lazy {
"""id="ctl00_CzyRodzic" value="([01])" />""".toRegex()
}
val IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR by lazy {
"""name="ctl00\${"$"}dxComboRokSzkolny".+?selected="selected".*?value="([0-9]+)">([0-9/]+)<""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_FIRST_STUDENT_SELECT by lazy {
"""<select.*?name="ctl00\${"$"}dxComboUczniowie".*?</select>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_FIRST_STUDENT by lazy {
"""<option.*?value="([0-9]+)"\sdata-id-ucznia="([A-z0-9]+?)".*?>(.+?)\s(.+?)\s*\((.+?),\s*(.+?)\)</option>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val VULCAN_SHITFT_ANNOTATION by lazy {
"""\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex()
}
}

View File

@ -0,0 +1,179 @@
package pl.szczodrzynski.edziennik.api.v2
import pl.szczodrzynski.edziennik.App.APP_URL
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.data.api.AppError.CODE_APP_SERVER_ERROR
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_SHARED_EVENT
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_NEW_SHARED_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.modules.notification.Notification.Companion.TYPE_SERVER_MESSAGE
import pl.szczodrzynski.edziennik.data.db.modules.notification.getNotificationTitle
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.getJsonArray
import pl.szczodrzynski.edziennik.getLong
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.network.ServerRequest
class ServerSync(val data: Data, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "ServerSync"
}
val app = data.app
val profileId = data.profile?.id ?: -1
val profileName = data.profile?.name ?: ""
val profile = data.profile
val loginStore = data.loginStore
private fun getUsernameId(): String {
if (loginStore.data == null) {
return "NO_LOGIN_STORE"
}
if (profile?.studentData == null) {
return "NO_STUDENT_STORE"
}
return when (data.loginStore.type) {
LoginStore.LOGIN_TYPE_MOBIDZIENNIK -> loginStore.getLoginData("serverName", "MOBI_UN") + ":" + loginStore.getLoginData("username", "MOBI_UN") + ":" + profile.getStudentData("studentId", -1)
LoginStore.LOGIN_TYPE_LIBRUS -> profile.getStudentData("schoolName", "LIBRUS_UN") + ":" + profile.getStudentData("accountLogin", "LIBRUS_LOGIN_UN")
LoginStore.LOGIN_TYPE_IUCZNIOWIE -> loginStore.getLoginData("schoolName", "IUCZNIOWIE_UN") + ":" + loginStore.getLoginData("username", "IUCZNIOWIE_UN") + ":" + profile.getStudentData("registerId", -1)
LoginStore.LOGIN_TYPE_VULCAN -> profile.getStudentData("schoolName", "VULCAN_UN") + ":" + profile.getStudentData("studentId", -1)
LoginStore.LOGIN_TYPE_DEMO -> loginStore.getLoginData("serverName", "DEMO_UN") + ":" + loginStore.getLoginData("username", "DEMO_UN") + ":" + profile.getStudentData("studentId", -1)
else -> "TYPE_UNKNOWN"
}
}
init { run {
if (profile?.registration != Profile.REGISTRATION_ENABLED) {
onSuccess()
return@run
}
val request = ServerRequest(
app,
app.requestScheme+APP_URL+"main.php?sync",
"Edziennik2/REG",
profile,
data.loginStore.type,
getUsernameId()
)
if (profile.empty) {
request.setBodyParameter("first_run", "true")
}
var hasNotifications = true
if (app.appConfig.webPushEnabled) {
data.notifications
.filterNot { it.posted }
.let {
if (it.isEmpty()) {
hasNotifications = false
null
}
else
it
}?.forEachIndexed { index, notification ->
if (notification.type != TYPE_NEW_SHARED_EVENT
&& notification.type != TYPE_SERVER_MESSAGE
&& notification.type != TYPE_NEW_SHARED_HOMEWORK) {
request.setBodyParameter("notify[$index][type]", notification.type.toString())
request.setBodyParameter("notify[$index][title]", notification.title)
request.setBodyParameter("notify[$index][text]", notification.text)
}
}
}
if ((!app.appConfig.webPushEnabled || !hasNotifications) && !profile.enableSharedEvents) {
onSuccess()
return@run
}
val result = request.runSync()
if (result == null) {
data.error(ApiError(TAG, CODE_APP_SERVER_ERROR)
.setCritical(false))
onSuccess()
return@run
}
var apiResponse = result.toString()
if (result.getString("success") != "true") {
data.error(ApiError(TAG, CODE_APP_SERVER_ERROR)
.setCritical(false))
onSuccess()
return@run
}
// HERE PROCESS ALL THE RECEIVED EVENTS
// add them to the profile and create appropriate notifications
result.getJsonArray("events")?.forEach { jEventEl ->
val event = jEventEl.asJsonObject
val teamCode = event.getString("team")
// get the target Team from teamCode
val team = app.db.teamDao().getByCodeNow(profile.id, teamCode)
if (team != null) {
// create the event from Json. Add the missing teamId and !!profileId!!
val eventObject = app.gson.fromJson(event.toString(), Event::class.java)
// proguard. disable for Event.class
if (eventObject.eventDate == null) {
apiResponse += "\n\nEventDate == null\n$event"
}
eventObject.profileId = profileId
eventObject.teamId = team.id
eventObject.addedManually = true
if (eventObject.sharedBy == getUsernameId()) {
eventObject.sharedBy = "self"
eventObject.sharedByName = profile.studentNameLong
}
val typeObject = app.db.eventTypeDao().getByIdNow(profileId, eventObject.type)
app.db.eventDao().add(eventObject)
val metadata = Metadata(
profileId,
if (eventObject.type == TYPE_HOMEWORK) Metadata.TYPE_HOMEWORK else Metadata.TYPE_EVENT,
eventObject.id,
profile.empty,
true,
event.getLong("addedDate") ?: 0
)
val metadataId = app.db.metadataDao().add(metadata)
// notify if the event is new and not first sync
if (metadataId != -1L && !profile.empty) {
val text = app.getString(
R.string.notification_shared_event_format,
eventObject.sharedByName,
if (typeObject != null) typeObject.name else "wydarzenie",
if (eventObject.eventDate == null) "???" else eventObject.eventDate.formattedString,
eventObject.topic
)
val type = if (eventObject.type == TYPE_HOMEWORK) TYPE_NEW_SHARED_HOMEWORK else TYPE_NEW_SHARED_EVENT
data.notifications += Notification(
title = app.getNotificationTitle(type),
text = text,
type = type,
profileId = profileId,
profileName = profileName,
viewId = if (eventObject.type == TYPE_HOMEWORK) DRAWER_ITEM_HOMEWORK else DRAWER_ITEM_AGENDA,
addedDate = metadata.addedDate
).addExtra("eventId", eventObject.id).addExtra("eventDate", eventObject.eventDate.value.toLong())
}
}
}
onSuccess()
}}
}

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.events
class ApiTaskAllFinishedEvent

View File

@ -0,0 +1,9 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.events
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
class ApiTaskErrorEvent(val error: ApiError)

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.events
class ApiTaskFinishedEvent(val profileId: Int)

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.events
class ApiTaskProgressEvent(val profileId: Int, val progress: Float, val progressText: String?)

View File

@ -0,0 +1,9 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.events
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
class ApiTaskStartedEvent(val profileId: Int, val profile: Profile? = null)

View File

@ -0,0 +1,6 @@
package pl.szczodrzynski.edziennik.api.v2.events
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
data class FirstLoginFinishedEvent(val profileList: List<Profile>, val loginStore: LoginStore)

View File

@ -0,0 +1,9 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-12.
*/
package pl.szczodrzynski.edziennik.api.v2.events
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
data class MessageGetEvent(val message: MessageFull)

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-1.
*/
package pl.szczodrzynski.edziennik.api.v2.events.requests
class ServiceCloseRequest

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-1.
*/
package pl.szczodrzynski.edziennik.api.v2.events.requests
class TaskCancelRequest(val taskId: Int)

View File

@ -0,0 +1,93 @@
package pl.szczodrzynski.edziennik.api.v2.events.task
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.idziennik.Idziennik
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.api.v2.librus.Librus
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.Mobidziennik
import pl.szczodrzynski.edziennik.api.v2.template.Template
import pl.szczodrzynski.edziennik.api.v2.vulcan.Vulcan
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTask(profileId) {
companion object {
private const val TAG = "EdziennikTask"
fun firstLogin(loginStore: LoginStore) = EdziennikTask(-1, FirstLoginRequest(loginStore))
fun sync() = EdziennikTask(-1, SyncRequest())
fun syncProfile(profileId: Int, viewIds: List<Pair<Int, Int>>? = null, arguments: JsonObject? = null) = EdziennikTask(profileId, SyncProfileRequest(viewIds, arguments))
fun syncProfileList(profileList: List<Int>) = EdziennikTask(-1, SyncProfileListRequest(profileList))
fun messageGet(profileId: Int, message: MessageFull) = EdziennikTask(profileId, MessageGetRequest(message))
fun announcementsRead(profileId: Int) = EdziennikTask(profileId, AnnouncementsReadRequest())
}
private lateinit var loginStore: LoginStore
override fun prepare(app: App) {
if (request is FirstLoginRequest) {
// get the requested profile and login store
this.profile = null
loginStore = request.loginStore
// save the profile ID and name as the current task's
taskName = app.getString(R.string.edziennik_notification_api_first_login_title)
}
else {
// get the requested profile and login store
val profile = app.db.profileDao().getByIdNow(profileId)
this.profile = profile
if (profile == null) {
return
}
val loginStore = app.db.loginStoreDao().getByIdNow(profile.loginStoreId) ?: return
this.loginStore = loginStore
// save the profile ID and name as the current task's
taskName = app.getString(R.string.edziennik_notification_api_sync_title_format, profile.name)
}
}
private var edziennikInterface: EdziennikInterface? = null
internal fun run(app: App, taskCallback: EdziennikCallback) {
edziennikInterface = when (loginStore.type) {
LOGIN_TYPE_LIBRUS -> Librus(app, profile, loginStore, taskCallback)
LOGIN_TYPE_MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback)
LOGIN_TYPE_VULCAN -> Vulcan(app, profile, loginStore, taskCallback)
LOGIN_TYPE_IDZIENNIK -> Idziennik(app, profile, loginStore, taskCallback)
LOGIN_TYPE_TEMPLATE -> Template(app, profile, loginStore, taskCallback)
else -> null
}
if (edziennikInterface == null) {
return
}
when (request) {
is SyncProfileRequest -> edziennikInterface?.sync(
featureIds = request.viewIds?.flatMap { Features.getIdsByView(it.first, it.second) } ?: Features.getAllIds(),
viewId = request.viewIds?.get(0)?.first,
arguments = request.arguments)
is MessageGetRequest -> edziennikInterface?.getMessage(request.message)
is FirstLoginRequest -> edziennikInterface?.firstLogin()
is AnnouncementsReadRequest -> edziennikInterface?.markAllAnnouncementsAsRead()
}
}
override fun cancel() {
edziennikInterface?.cancel()
}
override fun toString(): String {
return "EdziennikTask(profileId=$profileId, request=$request, edziennikInterface=$edziennikInterface)"
}
data class FirstLoginRequest(val loginStore: LoginStore)
class SyncRequest
data class SyncProfileRequest(val viewIds: List<Pair<Int, Int>>? = null, val arguments: JsonObject? = null)
data class SyncProfileListRequest(val profileList: List<Int>)
data class MessageGetRequest(val message: MessageFull)
class AnnouncementsReadRequest
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-1.
*/
package pl.szczodrzynski.edziennik.api.v2.events.task
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.ApiService
import pl.szczodrzynski.edziennik.api.v2.EdziennikNotification
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.utils.Utils
class ErrorReportTask : IApiTask(-1) {
override fun prepare(app: App) {
taskName = app.getString(R.string.edziennik_notification_api_error_report_title)
}
override fun cancel() {
}
fun run(app: App, taskCallback: EdziennikCallback, notification: EdziennikNotification, errorList: MutableList<ApiError>) {
errorList.forEach { error ->
Utils.d(ApiService.TAG, "Error ${error.tag} profile ${error.profileId}: code ${error.errorCode}")
}
errorList.clear()
taskCallback.onCompleted()
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.events.task
import android.content.Context
import android.content.Intent
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.O
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.v2.ApiService
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
abstract class IApiTask(open val profileId: Int) {
var taskId: Int = 0
var profile: Profile? = null
var taskName: String? = null
/**
* A method called before running the task.
* It is synchronous and its main task is
* to prepare the correct task name.
*/
abstract fun prepare(app: App)
abstract fun cancel()
fun enqueue(context: Context) {
Intent(context, ApiService::class.java).let {
if (SDK_INT >= O)
context.startForegroundService(it)
else
context.startService(it)
}
EventBus.getDefault().postSticky(this)
}
override fun toString(): String {
return "IApiTask(profileId=$profileId, taskId=$taskId, profile=$profile, taskName=$taskName)"
}
}

View File

@ -0,0 +1,89 @@
package pl.szczodrzynski.edziennik.api.v2.events.task
import android.app.PendingIntent
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.Notifier.ID_NOTIFICATIONS
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.utils.models.Notification
import kotlin.math.min
class NotifyTask : IApiTask(-1) {
override fun prepare(app: App) {
taskName = app.getString(R.string.edziennik_notification_api_notify_title)
}
override fun cancel() {
}
fun run(app: App, taskCallback: EdziennikCallback) {
val list = app.db.notificationDao().getNotPostedNow()
val notificationList = list.subList(0, min(15, list.size))
var unreadCount = list.size
for (notification in notificationList) {
val intent = Intent(app, MainActivity::class.java)
notification.fillIntent(intent)
val pendingIntent = PendingIntent.getActivity(app, notification.id, intent, 0)
val notificationBuilder = NotificationCompat.Builder(app, app.notifier.notificationGroup)
// title, text, type, date
.setContentTitle(notification.title)
.setContentText(notification.text)
.setSubText(Notification.stringType(app, notification.type))
.setWhen(notification.addedDate)
.setTicker(app.getString(R.string.notification_ticker_format, Notification.stringType(app, notification.type)))
// icon, color, lights, priority
.setSmallIcon(R.drawable.ic_notification)
.setColor(app.notifier.notificationColor)
.setLights(-0xff0001, 2000, 2000)
.setPriority(app.notifier.notificationPriority)
// channel, group, style
.setChannelId(app.notifier.notificationGroup)
.setGroup(app.notifier.notificationGroup)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
.setStyle(NotificationCompat.BigTextStyle().bigText(notification.text))
// intent, auto cancel
.setContentIntent(pendingIntent)
.setAutoCancel(true)
if (!app.notifier.shouldBeQuiet()) {
notificationBuilder.setDefaults(app.notifier.notificationDefaults)
}
app.notifier.notificationManager.notify(notification.id, notificationBuilder.build())
}
if (notificationList.isNotEmpty() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val intent = Intent(app, MainActivity::class.java)
intent.action = "android.intent.action.MAIN"
intent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_NOTIFICATIONS)
val pendingIntent = PendingIntent.getActivity(app, ID_NOTIFICATIONS,
intent, 0)
val groupBuilder = NotificationCompat.Builder(app, app.notifier.notificationGroup)
.setSmallIcon(R.drawable.ic_notification)
.setColor(app.notifier.notificationColor)
.setContentTitle(app.getString(R.string.notification_new_notification_title_format, unreadCount))
.setGroupSummary(true)
.setAutoCancel(true)
.setChannelId(app.notifier.notificationGroup)
.setGroup(app.notifier.notificationGroup)
.setLights(-0xff0001, 2000, 2000)
.setPriority(app.notifier.notificationPriority)
.setContentIntent(pendingIntent)
.setStyle(NotificationCompat.BigTextStyle())
if (!app.notifier.shouldBeQuiet()) {
groupBuilder.setDefaults(app.notifier.notificationDefaults)
}
app.notifier.notificationManager.notify(ID_NOTIFICATIONS, groupBuilder.build())
}
app.db.notificationDao().setAllPosted()
taskCallback.onCompleted()
}
}

View File

@ -0,0 +1,176 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-25.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik
import androidx.core.util.set
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_IDZIENNIK_API
import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_IDZIENNIK_WEB
import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
fun isWebLoginValid() = loginExpiryTime-30 > currentTimeUnix() && webSessionId.isNotNullNorEmpty() && webAuth.isNotNullNorEmpty()
fun isApiLoginValid() = apiExpiryTime-30 > currentTimeUnix() && apiBearer.isNotNullNorEmpty()
override fun satisfyLoginMethods() {
loginMethods.clear()
if (isWebLoginValid()) {
loginMethods += LOGIN_METHOD_IDZIENNIK_WEB
app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("ASP.NET_SessionId_iDziennik")
.value(webSessionId!!)
.domain("iuczniowie.progman.pl")
.secure().httpOnly().build(),
Cookie.Builder()
.name(".ASPXAUTH")
.value(webAuth!!)
.domain("iuczniowie.progman.pl")
.secure().httpOnly().build()
))
}
if (isApiLoginValid())
loginMethods += LOGIN_METHOD_IDZIENNIK_API
}
private var mLoginExpiryTime: Long? = null
var loginExpiryTime: Long
get() { mLoginExpiryTime = mLoginExpiryTime ?: loginStore.getLoginData("loginExpiryTime", 0L); return mLoginExpiryTime ?: 0L }
set(value) { loginStore.putLoginData("loginExpiryTime", value); mLoginExpiryTime = value }
private var mApiExpiryTime: Long? = null
var apiExpiryTime: Long
get() { mApiExpiryTime = mApiExpiryTime ?: loginStore.getLoginData("apiExpiryTime", 0L); return mApiExpiryTime ?: 0L }
set(value) { loginStore.putLoginData("apiExpiryTime", value); mApiExpiryTime = value }
/* __ __ _
\ \ / / | |
\ \ /\ / /__| |__
\ \/ \/ / _ \ '_ \
\ /\ / __/ |_) |
\/ \/ \___|_._*/
private var mWebSchoolName: String? = null
var webSchoolName: String?
get() { mWebSchoolName = mWebSchoolName ?: loginStore.getLoginData("schoolName", null); return mWebSchoolName }
set(value) { loginStore.putLoginData("schoolName", value); mWebSchoolName = value }
private var mWebUsername: String? = null
var webUsername: String?
get() { mWebUsername = mWebUsername ?: loginStore.getLoginData("username", null); return mWebUsername }
set(value) { loginStore.putLoginData("username", value); mWebUsername = value }
private var mWebPassword: String? = null
var webPassword: String?
get() { mWebPassword = mWebPassword ?: loginStore.getLoginData("password", null); return mWebPassword }
set(value) { loginStore.putLoginData("password", value); mWebPassword = value }
private var mWebSessionId: String? = null
var webSessionId: String?
get() { mWebSessionId = mWebSessionId ?: loginStore.getLoginData("webSessionId", null); return mWebSessionId }
set(value) { loginStore.putLoginData("webSessionId", value); mWebSessionId = value }
private var mWebAuth: String? = null
var webAuth: String?
get() { mWebAuth = mWebAuth ?: loginStore.getLoginData("webAuth", null); return mWebAuth }
set(value) { loginStore.putLoginData("webAuth", value); mWebAuth = value }
/* _
/\ (_)
/ \ _ __ _
/ /\ \ | '_ \| |
/ ____ \| |_) | |
/_/ \_\ .__/|_|
| |
|*/
private var mApiBearer: String? = null
var apiBearer: String?
get() { mApiBearer = mApiBearer ?: loginStore.getLoginData("apiBearer", null); return mApiBearer }
set(value) { loginStore.putLoginData("apiBearer", value); mApiBearer = value }
/* ____ _ _
/ __ \| | | |
| | | | |_| |__ ___ _ __
| | | | __| '_ \ / _ \ '__|
| |__| | |_| | | | __/ |
\____/ \__|_| |_|\___|*/
private var mStudentId: String? = null
var studentId: String?
get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", null); return mStudentId }
set(value) { profile?.putStudentData("studentId", value) ?: return; mStudentId = value }
private var mRegisterId: Int? = null
var registerId: Int
get() { mRegisterId = mRegisterId ?: profile?.getStudentData("registerId", 0); return mRegisterId ?: 0 }
set(value) { profile?.putStudentData("registerId", value) ?: return; mRegisterId = value }
private var mSchoolYearId: Int? = null
var schoolYearId: Int
get() { mSchoolYearId = mSchoolYearId ?: profile?.getStudentData("schoolYearId", 0); return mSchoolYearId ?: 0 }
set(value) { profile?.putStudentData("schoolYearId", value) ?: return; mSchoolYearId = value }
/* _ _ _ _ _
| | | | | (_) |
| | | | |_ _| |___
| | | | __| | / __|
| |__| | |_| | \__ \
\____/ \__|_|_|__*/
fun getSubject(name: String, id: Long?, shortName: String): Subject {
var subject = if (id == null)
subjectList.singleOrNull { it.longName == name }
else
subjectList.singleOrNull { it.id == id }
if (subject == null) {
subject = Subject(profileId, id ?: name.crc16().toLong(), name, shortName)
subjectList[subject.id] = subject
}
return subject
}
fun getTeacher(firstName: String, lastName: String): Teacher {
val teacher = teacherList.singleOrNull { it.fullName == "$firstName $lastName" }
return validateTeacher(teacher, firstName, lastName)
}
fun getTeacher(firstNameChar: Char, lastName: String): Teacher {
val teacher = teacherList.singleOrNull { it.shortName == "$firstNameChar.$lastName" }
return validateTeacher(teacher, firstNameChar.toString(), lastName)
}
fun getTeacherByLastFirst(nameLastFirst: String): Teacher {
val nameParts = nameLastFirst.split(" ")
return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[1], nameParts[0])
}
fun getTeacherByFirstLast(nameFirstLast: String): Teacher {
val nameParts = nameFirstLast.split(" ")
return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[0], nameParts[1])
}
fun getTeacherByFDotLast(nameFDotLast: String): Teacher {
val nameParts = nameFDotLast.split(".")
return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[0][0], nameParts[1])
}
fun getTeacherByFDotSpaceLast(nameFDotSpaceLast: String): Teacher {
val nameParts = nameFDotSpaceLast.split(".")
return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[0][0], nameParts[1])
}
private fun validateTeacher(teacher: Teacher?, firstName: String, lastName: String): Teacher {
(teacher ?: Teacher(profileId, -1, firstName, lastName).apply {
id = shortName.crc16().toLong()
teacherList[id] = this
}).apply {
if (firstName.length > 1)
name = firstName
surname = lastName
return this
}
}
}

View File

@ -0,0 +1,114 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-25.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikData
import pl.szczodrzynski.edziennik.api.v2.idziennik.firstlogin.IdziennikFirstLogin
import pl.szczodrzynski.edziennik.api.v2.idziennik.login.IdziennikLogin
import pl.szczodrzynski.edziennik.api.v2.idziennikLoginMethods
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.prepare
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d
class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
companion object {
private const val TAG = "Idziennik"
}
val internalErrorList = mutableListOf<Int>()
val data: DataIdziennik
init {
data = DataIdziennik(app, profile, loginStore).apply {
callback = wrapCallback(this@Idziennik.callback)
satisfyLoginMethods()
}
}
private fun completed() {
data.saveData()
data.notifyAndSyncEvents {
callback.onCompleted()
}
}
/* _______ _ _ _ _ _
|__ __| | /\ | | (_) | | |
| | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___
| | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \
| | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | |
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
__/ |
|__*/
override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
data.arguments = arguments
data.prepare(idziennikLoginMethods, IdziennikFeatures, featureIds, viewId)
d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}")
d(TAG, "Endpoint IDs: ${data.targetEndpointIds}")
IdziennikLogin(data) {
IdziennikData(data) {
completed()
}
}
}
override fun getMessage(message: MessageFull) {
}
override fun markAllAnnouncementsAsRead() {
}
override fun firstLogin() {
IdziennikFirstLogin(data) {
completed()
}
}
override fun cancel() {
d(TAG, "Cancelled")
data.cancel()
}
private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback {
return object : EdziennikCallback {
override fun onCompleted() {
callback.onCompleted()
}
override fun onProgress(step: Float) {
callback.onProgress(step)
}
override fun onStartProgress(stringRes: Int) {
callback.onStartProgress(stringRes)
}
override fun onError(apiError: ApiError) {
when (apiError.errorCode) {
in internalErrorList -> {
// finish immediately if the same error occurs twice during the same sync
callback.onError(apiError)
}
CODE_INTERNAL_LIBRUS_ACCOUNT_410 -> {
internalErrorList.add(apiError.errorCode)
loginStore.removeLoginData("refreshToken") // force a clean login
//loginLibrus()
}
else -> callback.onError(apiError)
}
}
}
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-25.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.models.Feature
const val ENDPOINT_IDZIENNIK_WEB_TIMETABLE = 1030
const val ENDPOINT_IDZIENNIK_WEB_GRADES = 1040
const val ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES = 1050
const val ENDPOINT_IDZIENNIK_WEB_EXAMS = 1060
const val ENDPOINT_IDZIENNIK_WEB_NOTICES = 1070
const val ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS = 1080
const val ENDPOINT_IDZIENNIK_WEB_ATTENDANCE = 1090
const val ENDPOINT_IDZIENNIK_WEB_MESSAGES_INBOX = 1110
const val ENDPOINT_IDZIENNIK_WEB_MESSAGES_SENT = 1120
const val ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER = 2010
const val ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX = 2110
const val ENDPOINT_IDZIENNIK_API_MESSAGES_SENT = 2120
val IdziennikFeatures = listOf(
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_TIMETABLE, listOf(
ENDPOINT_IDZIENNIK_WEB_TIMETABLE to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_GRADES, listOf(
ENDPOINT_IDZIENNIK_WEB_GRADES to LOGIN_METHOD_IDZIENNIK_WEB,
ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_AGENDA, listOf(
ENDPOINT_IDZIENNIK_WEB_EXAMS to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_BEHAVIOUR, listOf(
ENDPOINT_IDZIENNIK_WEB_NOTICES to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_ATTENDANCE, listOf(
ENDPOINT_IDZIENNIK_WEB_ATTENDANCE to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_ANNOUNCEMENTS, listOf(
ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
/*Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_MESSAGES_INBOX, listOf(
ENDPOINT_IDZIENNIK_WEB_MESSAGES_INBOX to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)).withPriority(2),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_MESSAGES_SENT, listOf(
ENDPOINT_IDZIENNIK_WEB_MESSAGES_SENT to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)).withPriority(2),*/
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_MESSAGES_INBOX, listOf(
ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX to LOGIN_METHOD_IDZIENNIK_API
), listOf(LOGIN_METHOD_IDZIENNIK_API)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_MESSAGES_SENT, listOf(
ENDPOINT_IDZIENNIK_API_MESSAGES_SENT to LOGIN_METHOD_IDZIENNIK_API
), listOf(LOGIN_METHOD_IDZIENNIK_API)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_LUCKY_NUMBER, listOf(
ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER to LOGIN_METHOD_IDZIENNIK_API
), listOf(LOGIN_METHOD_IDZIENNIK_API))
)

View File

@ -0,0 +1,116 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-29.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.data
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.TextCallbackHandler
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.Utils
import java.net.HttpURLConnection
open class IdziennikApi(open val data: DataIdziennik) {
companion object {
const val TAG = "IdziennikApi"
}
val profileId
get() = data.profile?.id ?: -1
val profile
get() = data.profile
fun apiGet(tag: String, endpointTemplate: String, method: Int = GET, parameters: Map<String, Any> = emptyMap(), onSuccess: (json: JsonElement) -> Unit) {
val endpoint = endpointTemplate.replace("\$STUDENT_ID", data.studentId ?: "")
Utils.d(tag, "Request: Idziennik/API - $IDZIENNIK_API_URL/$endpoint")
val callback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
if (text == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
val json = try {
JsonParser().parse(text)
} catch (_: Exception) { null }
var error: String? = null
if (json == null) {
error = text
}
else if (json is JsonObject) {
error = if (response?.code() == 200) null else
json.getString("message") ?: json.toString()
}
error?.let { code ->
when (code) {
"Authorization has been denied for this request." -> ERROR_IDZIENNIK_API_ACCESS_DENIED
else -> ERROR_IDZIENNIK_API_OTHER
}.let { errorCode ->
data.error(ApiError(tag, errorCode)
.withApiResponse(text)
.withResponse(response))
return
}
}
try {
onSuccess(json!!)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_IDZIENNIK_API_REQUEST)
.withResponse(response)
.withThrowable(e)
.withApiResponse(text))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url("$IDZIENNIK_API_URL/$endpoint")
.userAgent(IDZIENNIK_API_USER_AGENT)
.addHeader("Authorization", "Bearer ${data.apiBearer}")
.apply {
when (method) {
GET -> get()
POST -> {
postJson()
val json = JsonObject()
parameters.map { (name, value) ->
when (value) {
is JsonObject -> json.add(name, value)
is JsonArray -> json.add(name, value)
is String -> json.addProperty(name, value)
is Int -> json.addProperty(name, value)
is Long -> json.addProperty(name, value)
is Float -> json.addProperty(name, value)
is Char -> json.addProperty(name, value)
}
}
setJsonBody(json)
}
}
}
.allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED)
.allowErrorCode(HttpURLConnection.HTTP_INTERNAL_ERROR)
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-25.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.data
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.idziennik.*
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.api.IdziennikApiCurrentRegister
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.api.IdziennikApiMessagesInbox
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.api.IdziennikApiMessagesSent
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.web.*
import pl.szczodrzynski.edziennik.utils.Utils
class IdziennikData(val data: DataIdziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "IdziennikData"
}
init {
nextEndpoint(onSuccess)
}
private fun nextEndpoint(onSuccess: () -> Unit) {
if (data.targetEndpointIds.isEmpty()) {
onSuccess()
return
}
if (data.cancelled) {
onSuccess()
return
}
useEndpoint(data.targetEndpointIds.removeAt(0)) {
data.progress(data.progressStep)
nextEndpoint(onSuccess)
}
}
private fun useEndpoint(endpointId: Int, onSuccess: () -> Unit) {
Utils.d(TAG, "Using endpoint $endpointId")
when (endpointId) {
ENDPOINT_IDZIENNIK_WEB_TIMETABLE -> {
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
IdziennikWebTimetable(data, onSuccess)
}
ENDPOINT_IDZIENNIK_WEB_GRADES -> {
data.startProgress(R.string.edziennik_progress_endpoint_grades)
IdziennikWebGrades(data, onSuccess)
}
ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES -> {
data.startProgress(R.string.edziennik_progress_endpoint_proposed_grades)
IdziennikWebProposedGrades(data, onSuccess)
}
ENDPOINT_IDZIENNIK_WEB_EXAMS -> {
data.startProgress(R.string.edziennik_progress_endpoint_exams)
IdziennikWebExams(data, onSuccess)
}
ENDPOINT_IDZIENNIK_WEB_NOTICES -> {
data.startProgress(R.string.edziennik_progress_endpoint_notices)
IdziennikWebNotices(data, onSuccess)
}
ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS -> {
data.startProgress(R.string.edziennik_progress_endpoint_announcements)
IdziennikWebAnnouncements(data, onSuccess)
}
ENDPOINT_IDZIENNIK_WEB_ATTENDANCE -> {
data.startProgress(R.string.edziennik_progress_endpoint_attendance)
IdziennikWebAttendance(data, onSuccess)
}
ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER -> {
data.startProgress(R.string.edziennik_progress_endpoint_lucky_number)
IdziennikApiCurrentRegister(data, onSuccess)
}
ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX -> {
data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox)
IdziennikApiMessagesInbox(data, onSuccess)
}
ENDPOINT_IDZIENNIK_API_MESSAGES_SENT -> {
data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox)
IdziennikApiMessagesSent(data, onSuccess)
}
else -> onSuccess()
}
}
}

View File

@ -0,0 +1,158 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-25.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.data
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.JsonCallbackHandler
import im.wangchao.mhttp.callback.TextCallbackHandler
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection.HTTP_INTERNAL_ERROR
import java.net.HttpURLConnection.HTTP_UNAUTHORIZED
open class IdziennikWeb(open val data: DataIdziennik) {
companion object {
const val TAG = "IdziennikWeb"
}
val profileId
get() = data.profile?.id ?: -1
val profile
get() = data.profile
fun webApiGet(tag: String, endpoint: String, parameters: Map<String, Any> = emptyMap(), onSuccess: (json: JsonObject) -> Unit) {
d(tag, "Request: Idziennik/Web/API - $IDZIENNIK_WEB_URL/$endpoint")
val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) {
if (json == null && response?.parserErrorBody == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
when {
response?.code() == HTTP_UNAUTHORIZED -> ERROR_IDZIENNIK_WEB_ACCESS_DENIED
response?.code() == HTTP_INTERNAL_ERROR -> ERROR_IDZIENNIK_WEB_SERVER_ERROR
response?.parserErrorBody != null -> when {
response.parserErrorBody.contains("Identyfikator zgłoszenia") -> ERROR_IDZIENNIK_WEB_SERVER_ERROR
response.parserErrorBody.contains("Hasło dostępu do systemu wygasło") -> ERROR_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED
response.parserErrorBody.contains("Trwają prace konserwacyjne") -> ERROR_IDZIENNIK_WEB_MAINTENANCE
else -> ERROR_IDZIENNIK_WEB_OTHER
}
else -> null
}?.let { errorCode ->
data.error(ApiError(TAG, errorCode)
.withApiResponse(json?.toString() ?: response?.parserErrorBody)
.withResponse(response))
return
}
if (json == null) {
data.error(ApiError(tag, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
try {
onSuccess(json)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_IDZIENNIK_WEB_API_REQUEST)
.withResponse(response)
.withThrowable(e)
.withApiResponse(json))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url("$IDZIENNIK_WEB_URL/$endpoint")
.userAgent(IDZIENNIK_USER_AGENT)
.postJson()
.apply {
val json = JsonObject()
parameters.map { (name, value) ->
when (value) {
is JsonObject -> json.add(name, value)
is JsonArray -> json.add(name, value)
is String -> json.addProperty(name, value)
is Int -> json.addProperty(name, value)
is Long -> json.addProperty(name, value)
is Float -> json.addProperty(name, value)
is Char -> json.addProperty(name, value)
}
}
setJsonBody(json)
}
.allowErrorCode(HTTP_UNAUTHORIZED)
.allowErrorCode(HTTP_INTERNAL_ERROR)
.callback(callback)
.build()
.enqueue()
}
fun webGet(tag: String, endpoint: String, onSuccess: (text: String) -> Unit) {
d(tag, "Request: Idziennik/Web - $IDZIENNIK_WEB_URL/$endpoint")
val callback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
if (text == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
if (!text.contains("czyWyswietlicDostepMobilny")) {
when {
text.contains("Identyfikator zgłoszenia") -> ERROR_IDZIENNIK_WEB_SERVER_ERROR
text.contains("Hasło dostępu do systemu wygasło") -> ERROR_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED
text.contains("Trwają prace konserwacyjne") -> ERROR_IDZIENNIK_WEB_MAINTENANCE
else -> ERROR_IDZIENNIK_WEB_OTHER
}.let { errorCode ->
data.error(ApiError(TAG, errorCode)
.withApiResponse(text)
.withResponse(response))
return
}
}
try {
onSuccess(text)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_IDZIENNIK_WEB_REQUEST)
.withResponse(response)
.withThrowable(e)
.withApiResponse(text))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url("$IDZIENNIK_WEB_URL/$endpoint")
.userAgent(IDZIENNIK_USER_AGENT)
.get()
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-29.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.data.api
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.DAY
import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_API_CURRENT_REGISTER
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikApi
import pl.szczodrzynski.edziennik.data.db.modules.luckynumber.LuckyNumber
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.getInt
import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class IdziennikApiCurrentRegister(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikApi(data) {
companion object {
private const val TAG = "IdziennikApiCurrentRegister"
}
init {
data.profile?.luckyNumber = -1
data.profile?.luckyNumberDate = null
apiGet(TAG, IDZIENNIK_API_CURRENT_REGISTER) { json ->
if (json !is JsonObject) {
onSuccess()
return@apiGet
}
var nextSync = System.currentTimeMillis() + 14*DAY*1000
val settings = json.getJsonObject("ustawienia")?.apply {
profile?.dateSemester1Start = getString("poczatekSemestru1")?.let { Date.fromY_m_d(it) }
profile?.dateSemester2Start = getString("koniecSemestru1")?.let { Date.fromY_m_d(it).stepForward(0, 0, 1) }
profile?.dateYearEnd = getString("koniecSemestru2")?.let { Date.fromY_m_d(it) }
}
json.getInt("szczesliwyNumerek")?.let { luckyNumber ->
val luckyNumberDate = Date.getToday()
settings.getString("godzinaPublikacjiSzczesliwegoLosu")
?.let { Time.fromH_m(it) }
?.let { publishTime ->
val now = Time.getNow()
if (publishTime.value < 150000 && now.value < publishTime.value) {
nextSync = luckyNumberDate.combineWith(publishTime)
luckyNumberDate.stepForward(0, 0, -1) // the lucky number is still for yesterday
}
else if (publishTime.value >= 150000 && now.value > publishTime.value) {
luckyNumberDate.stepForward(0, 0, 1) // the lucky number is already for tomorrow
nextSync = luckyNumberDate.combineWith(publishTime)
}
else if (publishTime.value < 150000) {
nextSync = luckyNumberDate
.clone()
.stepForward(0, 0, 1)
.combineWith(publishTime)
}
else {
nextSync = luckyNumberDate.combineWith(publishTime)
}
}
val luckyNumberObject = LuckyNumber(
data.profileId,
Date.getToday(),
luckyNumber
)
data.luckyNumberList.add(luckyNumberObject)
data.metadataList.add(
Metadata(
profileId,
Metadata.TYPE_LUCKY_NUMBER,
luckyNumberObject.date.value.toLong(),
data.profile?.empty ?: false,
data.profile?.empty ?: false,
System.currentTimeMillis()
))
}
data.setSyncNext(ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER, syncAt = nextSync)
onSuccess()
}
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-30.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.data.api
import com.google.gson.JsonArray
import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_API_MESSAGES_INBOX
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikApi
import pl.szczodrzynski.edziennik.asJsonObjectList
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_DELETED
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.getBoolean
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.Utils.crc32
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikApiMessagesInbox(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikApi(data) {
companion object {
private const val TAG = "IdziennikApiMessagesInbox"
}
init {
apiGet(TAG, IDZIENNIK_API_MESSAGES_INBOX) { json ->
if (json !is JsonArray) {
onSuccess()
return@apiGet
}
json.asJsonObjectList()?.forEach { jMessage ->
val subject = jMessage.getString("tytul")
if (subject?.contains("(") == true && subject.startsWith("iDziennik - "))
return@forEach
if (subject?.startsWith("Uwaga dla ucznia (klasa:") == true)
return@forEach
val messageIdStr = jMessage.getString("id")
val messageId = crc32((messageIdStr + "0").toByteArray())
var body = "[META:$messageIdStr;-1]"
body += jMessage.getString("tresc")?.replace("\n".toRegex(), "<br>")
val readDate = if (jMessage.getBoolean("odczytana") == true) Date.fromIso(jMessage.getString("wersjaRekordu")) else 0
val sentDate = Date.fromIso(jMessage.getString("dataWyslania"))
val sender = jMessage.getAsJsonObject("nadawca")
val rTeacher = data.getTeacher(
sender.getString("imie") ?: "",
sender.getString("nazwisko") ?: ""
)
rTeacher.loginId = sender.getString("id") + ":" + sender.getString("usr")
val message = Message(
profileId,
messageId,
subject,
body,
if (jMessage.getBoolean("rekordUsuniety") == true) TYPE_DELETED else TYPE_RECEIVED,
rTeacher.id,
-1
)
val messageRecipient = MessageRecipient(
profileId,
-1 /* me */,
-1,
readDate,
/*messageId*/ messageId
)
data.messageIgnoreList.add(message)
data.messageRecipientList.add(messageRecipient)
data.messageMetadataList.add(Metadata(
profileId,
Metadata.TYPE_MESSAGE,
message.id,
readDate > 0,
readDate > 0 || profile?.empty ?: false,
sentDate
))
}
data.setSyncNext(ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-30.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.data.api
import com.google.gson.JsonArray
import pl.szczodrzynski.edziennik.DAY
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_API_MESSAGES_SENT
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_API_MESSAGES_SENT
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikApi
import pl.szczodrzynski.edziennik.asJsonObjectList
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.Utils.crc32
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikApiMessagesSent(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikApi(data) {
companion object {
private const val TAG = "IdziennikApiMessagesSent"
}
init {
apiGet(TAG, IDZIENNIK_API_MESSAGES_SENT) { json ->
if (json !is JsonArray) {
onSuccess()
return@apiGet
}
json.asJsonObjectList()?.forEach { jMessage ->
val messageIdStr = jMessage.get("id").asString
val messageId = crc32((messageIdStr + "1").toByteArray())
val subject = jMessage.get("tytul").asString
var body = "[META:$messageIdStr;-1]"
body += jMessage.get("tresc").asString.replace("\n".toRegex(), "<br>")
val sentDate = Date.fromIso(jMessage.get("dataWyslania").asString)
val message = Message(
profileId,
messageId,
subject,
body,
TYPE_SENT,
-1,
-1
)
for (recipientEl in jMessage.getAsJsonArray("odbiorcy")) {
val recipient = recipientEl.asJsonObject
var firstName = recipient.get("imie").asString
var lastName = recipient.get("nazwisko").asString
if (firstName.isEmpty() || lastName.isEmpty()) {
firstName = "usunięty"
lastName = "użytkownik"
}
val rTeacher = data.getTeacher(firstName, lastName)
rTeacher.loginId = recipient.get("id").asString + ":" + recipient.get("usr").asString
val messageRecipient = MessageRecipient(
profileId,
rTeacher.id,
-1,
-1,
/*messageId*/ messageId
)
data.messageRecipientIgnoreList.add(messageRecipient)
}
data.messageIgnoreList.add(message)
data.metadataList.add(Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true, sentDate))
}
data.setSyncNext(ENDPOINT_IDZIENNIK_API_MESSAGES_SENT, DAY, DRAWER_ITEM_MESSAGES)
onSuccess()
}
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-28.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.api.v2.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_ANNOUNCEMENTS
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.data.db.modules.announcements.Announcement
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikWebAnnouncements(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikWeb(data) {
companion object {
private const val TAG = "IdziennikWebAnnouncements"
}
init {
val param = JsonObject()
param.add("parametryFiltrow", JsonArray())
webApiGet(TAG, IDZIENNIK_WEB_ANNOUNCEMENTS, mapOf(
"uczenId" to (data.studentId ?: ""),
"param" to param
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
for (jAnnouncementEl in json.getAsJsonArray("ListK")) {
val jAnnouncement = jAnnouncementEl.asJsonObject
// jAnnouncement
val announcementId = jAnnouncement.get("Id").asLong
val rTeacher = data.getTeacherByFirstLast(jAnnouncement.get("Autor").asString)
val addedDate = java.lang.Long.parseLong(jAnnouncement.get("DataDodania").asString.replace("[^\\d]".toRegex(), ""))
val startDate = Date.fromMillis(java.lang.Long.parseLong(jAnnouncement.get("DataWydarzenia").asString.replace("[^\\d]".toRegex(), "")))
val announcementObject = Announcement(
profileId,
announcementId,
jAnnouncement.get("Temat").asString,
jAnnouncement.get("Tresc").asString,
startDate,
null,
rTeacher.id
)
data.announcementList.add(announcementObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_ANNOUNCEMENT,
announcementObject.id,
profile?.empty ?: false,
profile?.empty ?: false,
addedDate
))
}
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -0,0 +1,144 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-28.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web
import pl.szczodrzynski.edziennik.api.v2.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_ATTENDANCE
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_ATTENDANCE
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.crc16
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance
import pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance.*
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class IdziennikWebAttendance(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikWeb(data) {
companion object {
private const val TAG = "IdziennikWebAttendance"
}
private var attendanceYear = Date.getToday().year
private var attendanceMonth = Date.getToday().month
private var attendancePrevMonthChecked = false
init {
getAttendance()
}
private fun getAttendance() {
webApiGet(TAG, IDZIENNIK_WEB_ATTENDANCE, mapOf(
"idPozDziennika" to data.registerId,
"mc" to attendanceMonth,
"rok" to attendanceYear,
"dataTygodnia" to ""
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
for (jAttendanceEl in json.getAsJsonArray("Obecnosci")) {
val jAttendance = jAttendanceEl.asJsonObject
// jAttendance
val attendanceTypeIdziennik = jAttendance.get("TypObecnosci").asInt
if (attendanceTypeIdziennik == 5 || attendanceTypeIdziennik == 7)
continue
val attendanceDate = Date.fromY_m_d(jAttendance.get("Data").asString)
val attendanceTime = Time.fromH_m(jAttendance.get("OdDoGodziny").asString)
if (attendanceDate.combineWith(attendanceTime) > System.currentTimeMillis())
continue
val attendanceId = jAttendance.get("IdLesson").asString.crc16().toLong()
val rSubject = data.getSubject(jAttendance.get("Przedmiot").asString, jAttendance.get("IdPrzedmiot").asLong, "")
val rTeacher = data.getTeacherByFDotSpaceLast(jAttendance.get("PrzedmiotNauczyciel").asString)
var attendanceName = "obecność"
var attendanceType = Attendance.TYPE_CUSTOM
when (attendanceTypeIdziennik) {
1 /* nieobecność usprawiedliwiona */ -> {
attendanceName = "nieobecność usprawiedliwiona"
attendanceType = TYPE_ABSENT_EXCUSED
}
2 /* spóźnienie */ -> {
attendanceName = "spóźnienie"
attendanceType = TYPE_BELATED
}
3 /* nieobecność nieusprawiedliwiona */ -> {
attendanceName = "nieobecność nieusprawiedliwiona"
attendanceType = TYPE_ABSENT
}
4 /* zwolnienie */, 9 /* zwolniony / obecny */ -> {
attendanceType = TYPE_RELEASED
if (attendanceTypeIdziennik == 4)
attendanceName = "zwolnienie"
if (attendanceTypeIdziennik == 9)
attendanceName = "zwolnienie / obecność"
}
0 /* obecny */, 8 /* Wycieczka */ -> {
attendanceType = TYPE_PRESENT
if (attendanceTypeIdziennik == 8)
attendanceName = "wycieczka"
}
}
val semester = profile?.dateToSemester(attendanceDate) ?: 1
val attendanceObject = Attendance(
profileId,
attendanceId,
rTeacher.id,
rSubject.id,
semester,
attendanceName,
attendanceDate,
attendanceTime,
attendanceType
)
data.attendanceList.add(attendanceObject)
if (attendanceObject.type != TYPE_PRESENT) {
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_ATTENDANCE,
attendanceObject.id,
profile?.empty ?: false,
profile?.empty ?: false,
System.currentTimeMillis()
))
}
}
val attendanceDateValue = attendanceYear * 10000 + attendanceMonth * 100
if (profile?.empty == true && attendanceDateValue > profile?.getSemesterStart(1)?.value ?: 99999999) {
attendancePrevMonthChecked = true // do not need to check prev month later
attendanceMonth--
if (attendanceMonth < 1) {
attendanceMonth = 12
attendanceYear--
}
getAttendance()
} else if (!attendancePrevMonthChecked /* get also the previous month */) {
attendanceMonth--
if (attendanceMonth < 1) {
attendanceMonth = 12
attendanceYear--
}
attendancePrevMonthChecked = true
getAttendance()
} else {
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_ATTENDANCE, SYNC_ALWAYS)
onSuccess()
}
}
}
}

View File

@ -0,0 +1,114 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-28.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.api.v2.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_EXAMS
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_EXAMS
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.lessons.Lesson
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikWebExams(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikWeb(data) {
companion object {
private const val TAG = "IdziennikWebExams"
}
private var examsYear = Date.getToday().year
private var examsMonth = Date.getToday().month
private var examsMonthsChecked = 0
private var examsNextMonthChecked = false // TO DO temporary // no more // idk
init {
getExams()
}
private fun getExams() {
val param = JsonObject()
param.addProperty("strona", 1)
param.addProperty("iloscNaStrone", "99")
param.addProperty("iloscRekordow", -1)
param.addProperty("kolumnaSort", "ss.Nazwa,sp.Data_sprawdzianu")
param.addProperty("kierunekSort", 0)
param.addProperty("maxIloscZaznaczonych", 0)
param.addProperty("panelFiltrow", 0)
webApiGet(TAG, IDZIENNIK_WEB_EXAMS, mapOf(
"idP" to data.registerId,
"rok" to examsYear,
"miesiac" to examsMonth,
"param" to param
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
for (jExamEl in json.getAsJsonArray("ListK")) {
val jExam = jExamEl.asJsonObject
// jExam
val eventId = jExam.get("_recordId").asLong
val rSubject = data.getSubject(jExam.get("przedmiot").asString, -1, "")
val rTeacher = data.getTeacherByLastFirst(jExam.get("wpisal").asString)
val examDate = Date.fromY_m_d(jExam.get("data").asString)
val lessonObject = Lesson.getByWeekDayAndSubject(data.lessonList, examDate.weekDay, rSubject.id)
val examTime = lessonObject?.startTime
val eventType = if (jExam.get("rodzaj").asString == "sprawdzian/praca klasowa") Event.TYPE_EXAM else Event.TYPE_SHORT_QUIZ
val eventObject = Event(
profileId,
eventId,
examDate,
examTime,
jExam.get("zakres").asString,
-1,
eventType,
false,
rTeacher.id,
rSubject.id,
data.teamClass?.id ?: -1
)
data.eventList.add(eventObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_EVENT,
eventObject.id,
profile?.empty ?: false,
profile?.empty ?: false,
System.currentTimeMillis()
))
}
if (profile?.empty == true && examsMonthsChecked < 3 /* how many months backwards to check? */) {
examsMonthsChecked++
examsMonth--
if (examsMonth < 1) {
examsMonth = 12
examsYear--
}
getExams()
} else if (!examsNextMonthChecked /* get also one month forward */) {
val showDate = Date.getToday().stepForward(0, 1, 0)
examsYear = showDate.year
examsMonth = showDate.month
examsNextMonthChecked = true
getExams()
} else {
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_EXAMS, SYNC_ALWAYS)
onSuccess()
}
}
}
}

View File

@ -0,0 +1,155 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-28.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web
import android.graphics.Color
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_GRADES
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_GRADES
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikWebGrades(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikWeb(data) {
companion object {
private const val TAG = "IdziennikWebGrades"
}
init {
webApiGet(TAG, IDZIENNIK_WEB_GRADES, mapOf(
"idPozDziennika" to data.registerId
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
json.getJsonArray("Przedmioty")?.asJsonObjectList()?.onEach { subjectJson ->
val subject = data.getSubject(
subjectJson.getString("Przedmiot") ?: return@onEach,
subjectJson.getLong("IdPrzedmiotu") ?: return@onEach,
subjectJson.getString("Przedmiot") ?: return@onEach
)
subjectJson.getJsonArray("Oceny")?.asJsonObjectList()?.forEach { grade ->
val id = grade.getLong("idK") ?: return@forEach
val category = grade.getString("Kategoria") ?: ""
val name = grade.getString("Ocena") ?: "?"
val semester = grade.getInt("Semestr") ?: 1
val teacher = data.getTeacherByLastFirst(grade.getString("Wystawil") ?: return@forEach)
val countToAverage = grade.getBoolean("DoSredniej") ?: true
var value = grade.getFloat("WartoscDoSred") ?: 0.0f
val weight = if (countToAverage)
grade.getFloat("Waga") ?: 0.0f
else
0.0f
val gradeColor = grade.getString("Kolor") ?: ""
var colorInt = 0xff2196f3.toInt()
if (gradeColor.isNotEmpty()) {
colorInt = Color.parseColor("#$gradeColor")
}
val gradeObject = Grade(
profileId,
id,
category,
colorInt,
"",
name,
value,
weight,
semester,
teacher.id,
subject.id)
when (grade.getInt("Typ")) {
0 -> {
val history = grade.getJsonArray("Historia")?.asJsonObjectList()
if (history?.isNotEmpty() == true) {
var sum = gradeObject.value * gradeObject.weight
var count = gradeObject.weight
for (historyItem in history) {
val countToTheAverage = historyItem.getBoolean("DoSredniej") ?: false
value = historyItem.get("WartoscDoSred").asFloat
val weight = historyItem.get("Waga").asFloat
if (value > 0 && countToTheAverage) {
sum += value * weight
count += weight
}
val historyObject = Grade(
profileId,
gradeObject.id * -1,
historyItem.get("Kategoria").asString,
Color.parseColor("#" + historyItem.get("Kolor").asString),
historyItem.get("Uzasadnienie").asString,
historyItem.get("Ocena").asString,
value,
if (value > 0f && countToTheAverage) weight * -1f else 0f,
historyItem.get("Semestr").asInt,
teacher.id,
subject.id)
historyObject.parentId = gradeObject.id
val addedDate = historyItem.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis()
data.gradeList.add(historyObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_GRADE,
historyObject.id,
true,
true,
addedDate
))
}
// update the current grade's value with an average of all historical grades and itself
if (sum > 0 && count > 0) {
gradeObject.value = sum / count
}
gradeObject.isImprovement = true // gradeObject is the improved grade. Originals are historyObjects
}
}
1 -> {
gradeObject.type = Grade.TYPE_SEMESTER1_FINAL
gradeObject.name = name
gradeObject.weight = 0f
}
2 -> {
gradeObject.type = Grade.TYPE_YEAR_FINAL
gradeObject.name = name
gradeObject.weight = 0f
}
}
val addedDate = grade.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis()
data.gradeList.add(gradeObject)
data.metadataList.add(
Metadata(
profileId,
Metadata.TYPE_GRADE,
id,
data.profile?.empty ?: false,
data.profile?.empty ?: false,
addedDate
))
}
}
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_GRADES, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-28.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web
import pl.szczodrzynski.edziennik.api.v2.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_NOTICES
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_NOTICES
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.crc16
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.notices.Notice
import pl.szczodrzynski.edziennik.data.db.modules.notices.Notice.*
import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikWebNotices(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikWeb(data) {
companion object {
private const val TAG = "IdziennikWebNotices"
}
init {
webApiGet(TAG, IDZIENNIK_WEB_NOTICES, mapOf(
"idPozDziennika" to data.registerId
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
for (jNoticeEl in json.getAsJsonArray("SUwaga")) {
val jNotice = jNoticeEl.asJsonObject
// jNotice
val noticeId = jNotice.get("id").asString.crc16().toLong()
val rTeacher = data.getTeacherByLastFirst(jNotice.get("Nauczyciel").asString)
val addedDate = Date.fromY_m_d(jNotice.get("Data").asString)
var nType = TYPE_NEUTRAL
val jType = jNotice.get("Typ").asString
if (jType == "n") {
nType = TYPE_NEGATIVE
} else if (jType == "p") {
nType = TYPE_POSITIVE
}
val noticeObject = Notice(
profileId,
noticeId,
jNotice.get("Tresc").asString,
jNotice.get("Semestr").asInt,
nType,
rTeacher.id)
data.noticeList.add(noticeObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_NOTICE,
noticeObject.id,
profile?.empty ?: false,
profile?.empty ?: false,
addedDate.inMillis
))
}
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_NOTICES, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -0,0 +1,108 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-28.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web
import pl.szczodrzynski.edziennik.api.v2.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_MISSING_GRADES
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.TYPE_SEMESTER1_PROPOSED
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.TYPE_YEAR_PROPOSED
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.utils.Utils.getWordGradeValue
class IdziennikWebProposedGrades(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikWeb(data) {
companion object {
private const val TAG = "IdziennikWebProposedGrades"
}
init {
webApiGet(TAG, IDZIENNIK_WEB_MISSING_GRADES, mapOf(
"idPozDziennika" to data.registerId
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
val jSubjects = json.getAsJsonArray("Przedmioty")
for (jSubjectEl in jSubjects) {
val jSubject = jSubjectEl.getAsJsonObject()
// jSubject
val rSubject = data.getSubject(jSubject.get("Przedmiot").getAsString(), -1, jSubject.get("Przedmiot").getAsString())
val semester1Proposed = jSubject.get("OcenaSem1").getAsString()
val semester2Proposed = jSubject.get("OcenaSem2").getAsString()
val semester1Value = getWordGradeValue(semester1Proposed)
val semester2Value = getWordGradeValue(semester2Proposed)
val semester1Id = rSubject.id * -100 - 1
val semester2Id = rSubject.id * -100 - 2
if (semester1Proposed != "") {
val gradeObject = Grade(
profileId,
semester1Id,
"",
-1,
"",
semester1Value.toString(),
semester1Value.toFloat(),
0f,
1,
-1,
rSubject.id)
gradeObject.type = TYPE_SEMESTER1_PROPOSED
data.gradeList.add(gradeObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_GRADE,
gradeObject.id,
profile?.empty ?: false,
profile?.empty ?: false,
System.currentTimeMillis()
))
}
if (semester2Proposed != "") {
val gradeObject = Grade(
profileId,
semester2Id,
"",
-1,
"",
semester2Value.toString(),
semester2Value.toFloat(),
0f,
2,
-1,
rSubject.id)
gradeObject.type = TYPE_YEAR_PROPOSED
data.gradeList.add(gradeObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_GRADE,
gradeObject.id,
profile?.empty ?: false,
profile?.empty ?: false,
System.currentTimeMillis()
))
}
}
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -0,0 +1,128 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-27.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web
import androidx.core.util.set
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_TIMETABLE
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_TIMETABLE
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.lessons.Lesson
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange.TYPE_CANCELLED
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange.TYPE_CHANGE
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonRange
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.edziennik.utils.models.Week
class IdziennikWebTimetable(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikWeb(data) {
companion object {
private const val TAG = "IdziennikWebTimetable"
}
init {
val weekStart = Week.getWeekStart()
if (Date.getToday().weekDay > 4) {
weekStart.stepForward(0, 0, 7)
}
webApiGet(TAG, IDZIENNIK_WEB_TIMETABLE, mapOf(
"idPozDziennika" to data.registerId,
"pidRokSzkolny" to data.schoolYearId,
"data" to weekStart.stringY_m_d+"T10:00:00.000Z"
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
json.getJsonArray("GodzinyLekcyjne")?.asJsonObjectList()?.forEach { range ->
val lessonRange = LessonRange(
profileId,
range.getInt("LiczbaP") ?: return@forEach,
range.getString("Poczatek")?.let { Time.fromH_m(it) } ?: return@forEach,
range.getString("Koniec")?.let { Time.fromH_m(it) } ?: return@forEach
)
data.lessonRanges[lessonRange.lessonNumber] = lessonRange
}
json.getJsonArray("Przedmioty")?.asJsonObjectList()?.forEach { lesson ->
val subject = data.getSubject(
lesson.getString("Nazwa") ?: return@forEach,
lesson.getLong("Id"),
lesson.getString("Skrot") ?: ""
)
val teacher = data.getTeacherByFDotLast(lesson.getString("Nauczyciel") ?: return@forEach)
val weekDay = lesson.getInt("DzienTygodnia")?.minus(1) ?: return@forEach
val lessonRange = data.lessonRanges[lesson.getInt("Godzina")?.plus(1) ?: return@forEach]
val lessonObject = Lesson(
profileId,
weekDay,
lessonRange.startTime,
lessonRange.endTime
).apply {
subjectId = subject.id
teacherId = teacher.id
teamId = data.teamClass?.id ?: -1
classroomName = lesson.getString("NazwaSali") ?: ""
}
data.lessonList.add(lessonObject)
val type = lesson.getInt("TypZastepstwa") ?: -1
if (type != -1) {
// we have a lesson change to process
val lessonChangeObject = LessonChange(
profileId,
weekStart.clone().stepForward(0, 0, weekDay),
lessonObject.startTime,
lessonObject.endTime
)
lessonChangeObject.teamId = lessonObject.teamId
lessonChangeObject.teacherId = lessonObject.teacherId
lessonChangeObject.subjectId = lessonObject.subjectId
lessonChangeObject.classroomName = lessonObject.classroomName
when (type) {
0 -> lessonChangeObject.type = TYPE_CANCELLED
1, 2, 3, 4, 5 -> {
lessonChangeObject.type = TYPE_CHANGE
val newTeacher = lesson.getString("NauZastepujacy")
val newSubject = lesson.getString("PrzedmiotZastepujacy")
if (newTeacher != null) {
lessonChangeObject.teacherId = data.getTeacherByFDotLast(newTeacher).id
}
if (newSubject != null) {
lessonChangeObject.subjectId = data.getSubject(newSubject, null, "").id
}
}
}
data.lessonChangeList.add(lessonChangeObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_LESSON_CHANGE,
lessonChangeObject.id,
profile?.empty ?: false,
profile?.empty ?: false,
System.currentTimeMillis()
))
}
}
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_TIMETABLE, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-27.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.firstlogin
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.api.v2.ERROR_LOGIN_IDZIENNIK_FIRST_NO_SCHOOL_YEAR
import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_SETTINGS
import pl.szczodrzynski.edziennik.api.v2.Regexes
import pl.szczodrzynski.edziennik.api.v2.events.FirstLoginFinishedEvent
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.api.v2.idziennik.login.IdziennikLoginWeb
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.fixName
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.swapFirstLastName
class IdziennikFirstLogin(val data: DataIdziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "IdziennikFirstLogin"
}
private val web = IdziennikWeb(data)
private val profileList = mutableListOf<Profile>()
init {
IdziennikLoginWeb(data) {
web.webGet(TAG, IDZIENNIK_WEB_SETTINGS) { text ->
//val accounts = json.getJsonArray("accounts")
val isParent = Regexes.IDZIENNIK_LOGIN_FIRST_IS_PARENT.find(text)?.get(1) != "0"
val accountNameLong = if (isParent)
Regexes.IDZIENNIK_LOGIN_FIRST_ACCOUNT_NAME.find(text)?.get(1)?.swapFirstLastName()?.fixName()
else
null
var schoolYearName: String? = null
val schoolYear = Regexes.IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR.find(text)?.let {
schoolYearName = it[2]
it[1].toIntOrNull()
} ?: run {
data.error(ApiError(TAG, ERROR_LOGIN_IDZIENNIK_FIRST_NO_SCHOOL_YEAR)
.withApiResponse(text))
return@webGet
}
Regexes.IDZIENNIK_LOGIN_FIRST_STUDENT.findAll(text)
.toMutableList()
.reversed()
.forEach { match ->
val registerId = match[1].toIntOrNull() ?: return@forEach
val studentId = match[2]
val firstName = match[3]
val lastName = match[4]
val className = match[5] + " " + match[6]
val profile = Profile()
profile.studentNameLong = "$firstName $lastName".fixName()
profile.studentNameShort = "$firstName ${lastName[0]}.".fixName()
profile.accountNameLong = accountNameLong
profile.studentClassName = className
profile.studentSchoolYear = schoolYearName
profile.name = profile.studentNameLong
profile.subname = data.webUsername
profile.empty = true
profile.putStudentData("studentId", studentId)
profile.putStudentData("registerId", registerId)
profile.putStudentData("schoolYearId", schoolYear)
profileList.add(profile)
}
EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore))
onSuccess()
}
}
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-25.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.login
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_IDZIENNIK_API
import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_IDZIENNIK_WEB
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.utils.Utils
class IdziennikLogin(val data: DataIdziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "IdziennikLogin"
}
private var cancelled = false
init {
nextLoginMethod(onSuccess)
}
private fun nextLoginMethod(onSuccess: () -> Unit) {
if (data.targetLoginMethodIds.isEmpty()) {
onSuccess()
return
}
if (cancelled) {
onSuccess()
return
}
useLoginMethod(data.targetLoginMethodIds.removeAt(0)) { usedMethodId ->
data.progress(data.progressStep)
if (usedMethodId != -1)
data.loginMethods.add(usedMethodId)
nextLoginMethod(onSuccess)
}
}
private fun useLoginMethod(loginMethodId: Int, onSuccess: (usedMethodId: Int) -> Unit) {
// this should never be true
if (data.loginMethods.contains(loginMethodId)) {
onSuccess(-1)
return
}
Utils.d(TAG, "Using login method $loginMethodId")
when (loginMethodId) {
LOGIN_METHOD_IDZIENNIK_WEB -> {
data.startProgress(R.string.edziennik_progress_login_idziennik_web)
IdziennikLoginWeb(data) { onSuccess(loginMethodId) }
}
LOGIN_METHOD_IDZIENNIK_API -> {
data.startProgress(R.string.edziennik_progress_login_idziennik_api)
IdziennikLoginApi(data) { onSuccess(loginMethodId) }
}
}
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-27.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.login
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
class IdziennikLoginApi(val data: DataIdziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "IdziennikLoginApi"
}
init { run {
if (data.isApiLoginValid()) {
onSuccess()
}
else {
onSuccess()
}
}}
}

View File

@ -0,0 +1,148 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-26.
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.login
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.HOUR
import pl.szczodrzynski.edziennik.MINUTE
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.getUnixDate
import pl.szczodrzynski.edziennik.utils.Utils
class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "IdziennikLoginWeb"
}
init { run {
if (data.isWebLoginValid()) {
data.app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("ASP.NET_SessionId_iDziennik")
.value(data.webSessionId!!)
.domain("iuczniowie.progman.pl")
.secure().httpOnly().build(),
Cookie.Builder()
.name(".ASPXAUTH")
.value(data.webAuth!!)
.domain("iuczniowie.progman.pl")
.secure().httpOnly().build()
))
onSuccess()
}
else {
data.app.cookieJar.clearForDomain("iuczniowie.progman.pl")
if (data.webSchoolName != null && data.webUsername != null && data.webPassword != null) {
loginWithCredentials()
}
else {
data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING))
}
}
}}
private fun loginWithCredentials() {
Utils.d(TAG, "Request: Idziennik/Login/Web - $IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN")
val loginCallback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
if (text.isNullOrEmpty()) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
// login succeeded: there is a start page
if (text.contains("czyWyswietlicDostepMobilny")) {
val cookies = data.app.cookieJar.getForDomain("iuczniowie.progman.pl")
run {
data.webSessionId = cookies.singleOrNull { it.name() == "ASP.NET_SessionId_iDziennik" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION
data.webAuth = cookies.singleOrNull { it.name() == ".ASPXAUTH" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH
data.apiBearer = cookies.singleOrNull { it.name() == "Bearer" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER
data.loginExpiryTime = response.getUnixDate() + 30 * MINUTE /* after about 40 minutes the login didn't work already */
data.apiExpiryTime = response.getUnixDate() + 12 * HOUR /* actually it expires after 24 hours but I'm not sure when does the token refresh. */
return@run null
}?.let { errorCode ->
data.error(ApiError(TAG, errorCode)
.withApiResponse(text)
.withResponse(response))
return
}
onSuccess()
return
}
val errorText = Regexes.IDZIENNIK_LOGIN_ERROR.find(text)?.get(1)
when {
errorText?.contains("nieprawidłową nazwę szkoły") == true -> ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME
errorText?.contains("nieprawidłowy login lub hasło") == true -> ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN
text.contains("Identyfikator zgłoszenia") -> ERROR_LOGIN_IDZIENNIK_WEB_SERVER_ERROR
text.contains("Hasło dostępu do systemu wygasło") -> ERROR_LOGIN_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED
text.contains("Trwają prace konserwacyjne") -> ERROR_LOGIN_IDZIENNIK_WEB_MAINTENANCE
else -> ERROR_LOGIN_IDZIENNIK_WEB_OTHER
}.let { errorCode ->
data.error(ApiError(TAG, errorCode)
.withApiResponse(text)
.withResponse(response))
return
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
val getCallback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
Request.builder()
.url("$IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN")
.userAgent(IDZIENNIK_USER_AGENT)
.addHeader("Origin", "https://iuczniowie.progman.pl")
.addHeader("Referer", "$IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN")
.apply {
Regexes.IDZIENNIK_LOGIN_HIDDEN_FIELDS.findAll(text ?: return@apply).forEach {
addParameter(it[1], it[2])
}
}
.addParameter("ctl00\$ContentPlaceHolder\$nazwaPrzegladarki", IDZIENNIK_USER_AGENT)
.addParameter("ctl00\$ContentPlaceHolder\$NazwaSzkoly", data.webSchoolName)
.addParameter("ctl00\$ContentPlaceHolder\$UserName", data.webUsername)
.addParameter("ctl00\$ContentPlaceHolder\$Password", data.webPassword)
.addParameter("ctl00\$ContentPlaceHolder\$captcha", "")
.addParameter("ctl00\$ContentPlaceHolder\$Logowanie", "Zaloguj")
.post()
.allowErrorCode(502)
.callback(loginCallback)
.build()
.enqueue()
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url("$IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN")
.userAgent(IDZIENNIK_USER_AGENT)
.get()
.allowErrorCode(502)
.callback(getCallback)
.build()
.enqueue()
}
}

View File

@ -0,0 +1,17 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-29.
*/
package pl.szczodrzynski.edziennik.api.v2.interfaces
import pl.szczodrzynski.edziennik.api.v2.models.Feature
import pl.szczodrzynski.edziennik.api.v2.models.LoginMethod
/**
* A callback passed only to an e-register class.
* All [Feature]s and [LoginMethod]s receive this callback,
* but may only use [EndpointCallback]'s methods.
*/
interface EdziennikCallback : EndpointCallback {
fun onCompleted()
}

View File

@ -0,0 +1,16 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-29.
*/
package pl.szczodrzynski.edziennik.api.v2.interfaces
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
interface EdziennikInterface {
fun sync(featureIds: List<Int>, viewId: Int? = null, arguments: JsonObject? = null)
fun getMessage(message: MessageFull)
fun markAllAnnouncementsAsRead()
fun firstLogin()
fun cancel()
}

View File

@ -0,0 +1,18 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-29.
*/
package pl.szczodrzynski.edziennik.api.v2.interfaces
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.models.Feature
import pl.szczodrzynski.edziennik.api.v2.models.LoginMethod
/**
* A callback passed to all [Feature]s and [LoginMethod]s
*/
interface EndpointCallback {
fun onError(apiError: ApiError)
fun onProgress(step: Float)
fun onStartProgress(stringRes: Int)
}

View File

@ -0,0 +1,272 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_LIBRUS_API
import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_LIBRUS_MESSAGES
import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_LIBRUS_PORTAL
import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_LIBRUS_SYNERGIA
import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.currentTimeUnix
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
fun isPortalLoginValid() = portalTokenExpiryTime-30 > currentTimeUnix() && portalRefreshToken.isNotNullNorEmpty() && portalAccessToken.isNotNullNorEmpty()
fun isApiLoginValid() = apiTokenExpiryTime-30 > currentTimeUnix() && apiAccessToken.isNotNullNorEmpty()
fun isSynergiaLoginValid() = synergiaSessionIdExpiryTime-30 > currentTimeUnix() && synergiaSessionId.isNotNullNorEmpty()
fun isMessagesLoginValid() = messagesSessionIdExpiryTime-30 > currentTimeUnix() && messagesSessionId.isNotNullNorEmpty()
override fun satisfyLoginMethods() {
loginMethods.clear()
if (isPortalLoginValid())
loginMethods += LOGIN_METHOD_LIBRUS_PORTAL
if (isApiLoginValid())
loginMethods += LOGIN_METHOD_LIBRUS_API
if (isSynergiaLoginValid()) {
loginMethods += LOGIN_METHOD_LIBRUS_SYNERGIA
app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("DZIENNIKSID")
.value(synergiaSessionId!!)
.domain("synergia.librus.pl")
.secure().httpOnly().build()
))
}
if (isMessagesLoginValid()) {
loginMethods += LOGIN_METHOD_LIBRUS_MESSAGES
app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("DZIENNIKSID")
.value(messagesSessionId!!)
.domain("wiadomosci.librus.pl")
.secure().httpOnly().build()
))
}
}
fun getColor(id: Int?): Int {
return when (id) {
1 -> 0xFFF0E68C
2 -> 0xFF87CEFA
3 -> 0xFFB0C4DE
4 -> 0xFFF0F8FF
5 -> 0xFFF0FFFF
6 -> 0xFFF5F5DC
7 -> 0xFFFFEBCD
8 -> 0xFFFFF8DC
9 -> 0xFFA9A9A9
10 -> 0xFFBDB76B
11 -> 0xFF8FBC8F
12 -> 0xFFDCDCDC
13 -> 0xFFDAA520
14 -> 0xFFE6E6FA
15 -> 0xFFFFA07A
16 -> 0xFF32CD32
17 -> 0xFF66CDAA
18 -> 0xFF66CDAA
19 -> 0xFFC0C0C0
20 -> 0xFFD2B48C
21 -> 0xFF3333FF
22 -> 0xFF7B68EE
23 -> 0xFFBA55D3
24 -> 0xFFFFB6C1
25 -> 0xFFFF1493
26 -> 0xFFDC143C
27 -> 0xFFFF0000
28 -> 0xFFFF8C00
29 -> 0xFFFFD700
30 -> 0xFFADFF2F
31 -> 0xFF7CFC00
else -> 0xff2196f3
}.toInt()
}
/* _____ _ _
| __ \ | | | |
| |__) |__ _ __| |_ __ _| |
| ___/ _ \| '__| __/ _` | |
| | | (_) | | | || (_| | |
|_| \___/|_| \__\__,_|*/
private var mPortalEmail: String? = null
var portalEmail: String?
get() { mPortalEmail = mPortalEmail ?: loginStore.getLoginData("email", null); return mPortalEmail }
set(value) { loginStore.putLoginData("email", value); mPortalEmail = value }
private var mPortalPassword: String? = null
var portalPassword: String?
get() { mPortalPassword = mPortalPassword ?: loginStore.getLoginData("password", null); return mPortalPassword }
set(value) { loginStore.putLoginData("password", value); mPortalPassword = value }
private var mPortalAccessToken: String? = null
var portalAccessToken: String?
get() { mPortalAccessToken = mPortalAccessToken ?: loginStore.getLoginData("accessToken", null); return mPortalAccessToken }
set(value) { loginStore.putLoginData("accessToken", value); mPortalAccessToken = value }
private var mPortalRefreshToken: String? = null
var portalRefreshToken: String?
get() { mPortalRefreshToken = mPortalRefreshToken ?: loginStore.getLoginData("refreshToken", null); return mPortalRefreshToken }
set(value) { loginStore.putLoginData("refreshToken", value); mPortalRefreshToken = value }
private var mPortalTokenExpiryTime: Long? = null
var portalTokenExpiryTime: Long
get() { mPortalTokenExpiryTime = mPortalTokenExpiryTime ?: loginStore.getLoginData("tokenExpiryTime", 0L); return mPortalTokenExpiryTime ?: 0L }
set(value) { loginStore.putLoginData("tokenExpiryTime", value); mPortalTokenExpiryTime = value }
/* _____ _____
/\ | __ \_ _|
/ \ | |__) || |
/ /\ \ | ___/ | |
/ ____ \| | _| |_
/_/ \_\_| |____*/
/**
* A Synergia login, like 1234567u.
* Used: for login (API Login Method) in Synergia mode.
* Used: for login (Synergia Login Method) in Synergia mode.
* And also in various places in [pl.szczodrzynski.edziennik.api.v2.models.Feature]s
*/
private var mApiLogin: String? = null
var apiLogin: String?
get() { mApiLogin = mApiLogin ?: profile?.getStudentData("accountLogin", null); return mApiLogin }
set(value) { profile?.putStudentData("accountLogin", value) ?: return; mApiLogin = value }
/**
* A Synergia password.
* Used: for login (API Login Method) in Synergia mode.
* Used: for login (Synergia Login Method) in Synergia mode.
*/
private var mApiPassword: String? = null
var apiPassword: String?
get() { mApiPassword = mApiPassword ?: profile?.getStudentData("accountPassword", null); return mApiPassword }
set(value) { profile?.putStudentData("accountPassword", value) ?: return; mApiPassword = value }
/**
* A JST login Code.
* Used only during first login in JST mode.
*/
private var mApiCode: String? = null
var apiCode: String?
get() { mApiCode = mApiCode ?: profile?.getStudentData("accountCode", null); return mApiCode }
set(value) { profile?.putStudentData("accountCode", value) ?: return; mApiCode = value }
/**
* A JST login PIN.
* Used only during first login in JST mode.
*/
private var mApiPin: String? = null
var apiPin: String?
get() { mApiPin = mApiPin ?: profile?.getStudentData("accountPin", null); return mApiPin }
set(value) { profile?.putStudentData("accountPin", value) ?: return; mApiPin = value }
/**
* A Synergia API access token.
* Used in all Api Endpoints.
* Created in Login Method Api.
* Applicable for all login modes.
*/
private var mApiAccessToken: String? = null
var apiAccessToken: String?
get() { mApiAccessToken = mApiAccessToken ?: profile?.getStudentData("accountToken", null); return mApiAccessToken }
set(value) { profile?.putStudentData("accountToken", value) ?: return; mApiAccessToken = value }
/**
* A Synergia API refresh token.
* Used when refreshing the [apiAccessToken] in JST, Synergia modes.
*/
private var mApiRefreshToken: String? = null
var apiRefreshToken: String?
get() { mApiRefreshToken = mApiRefreshToken ?: profile?.getStudentData("accountRefreshToken", null); return mApiRefreshToken }
set(value) { profile?.putStudentData("accountRefreshToken", value) ?: return; mApiRefreshToken = value }
/**
* The expiry time for [apiAccessToken], as a UNIX timestamp.
* Used when refreshing the [apiAccessToken] in JST, Synergia modes.
* Used when refreshing the [apiAccessToken] in Portal mode ([pl.szczodrzynski.edziennik.api.v2.librus.login.SynergiaTokenExtractor])
*/
private var mApiTokenExpiryTime: Long? = null
var apiTokenExpiryTime: Long
get() { mApiTokenExpiryTime = mApiTokenExpiryTime ?: profile?.getStudentData("accountTokenTime", 0L); return mApiTokenExpiryTime ?: 0L }
set(value) { profile?.putStudentData("accountTokenTime", value) ?: return; mApiTokenExpiryTime = value }
/* _____ _
/ ____| (_)
| (___ _ _ _ __ ___ _ __ __ _ _ __ _
\___ \| | | | '_ \ / _ \ '__/ _` | |/ _` |
____) | |_| | | | | __/ | | (_| | | (_| |
|_____/ \__, |_| |_|\___|_| \__, |_|\__,_|
__/ | __/ |
|___/ |__*/
/**
* A Synergia web Session ID (DZIENNIKSID).
* Used in endpoints with Synergia login method.
*/
private var mSynergiaSessionId: String? = null
var synergiaSessionId: String?
get() { mSynergiaSessionId = mSynergiaSessionId ?: profile?.getStudentData("accountSID", null); return mSynergiaSessionId }
set(value) { profile?.putStudentData("accountSID", value) ?: return; mSynergiaSessionId = value }
/**
* The expiry time for [synergiaSessionId], as a UNIX timestamp.
* Used in endpoints with Synergia login method.
* TODO verify how long is the session ID valid.
*/
private var mSynergiaSessionIdExpiryTime: Long? = null
var synergiaSessionIdExpiryTime: Long
get() { mSynergiaSessionIdExpiryTime = mSynergiaSessionIdExpiryTime ?: profile?.getStudentData("accountSIDTime", 0L); return mSynergiaSessionIdExpiryTime ?: 0L }
set(value) { profile?.putStudentData("accountSIDTime", value) ?: return; mSynergiaSessionIdExpiryTime = value }
/**
* A Messages web Session ID (DZIENNIKSID).
* Used in endpoints with Messages login method.
*/
private var mMessagesSessionId: String? = null
var messagesSessionId: String?
get() { mMessagesSessionId = mMessagesSessionId ?: profile?.getStudentData("messagesSID", null); return mMessagesSessionId }
set(value) { profile?.putStudentData("messagesSID", value) ?: return; mMessagesSessionId = value }
/**
* The expiry time for [messagesSessionId], as a UNIX timestamp.
* Used in endpoints with Messages login method.
* TODO verify how long is the session ID valid.
*/
private var mMessagesSessionIdExpiryTime: Long? = null
var messagesSessionIdExpiryTime: Long
get() { mMessagesSessionIdExpiryTime = mMessagesSessionIdExpiryTime ?: profile?.getStudentData("messagesSIDTime", 0L); return mMessagesSessionIdExpiryTime ?: 0L }
set(value) { profile?.putStudentData("messagesSIDTime", value) ?: return; mMessagesSessionIdExpiryTime = value }
/* ____ _ _
/ __ \| | | |
| | | | |_| |__ ___ _ __
| | | | __| '_ \ / _ \ '__|
| |__| | |_| | | | __/ |
\____/ \__|_| |_|\___|*/
var isPremium
get() = profile?.getStudentData("isPremium", false) ?: false
set(value) { profile?.putStudentData("isPremium", value) }
private var mSchoolName: String? = null
var schoolName: String?
get() { mSchoolName = mSchoolName ?: profile?.getStudentData("schoolName", null); return mSchoolName }
set(value) { profile?.putStudentData("schoolName", value) ?: return; mSchoolName = value }
private var mUnitId: Long? = null
var unitId: Long
get() { mUnitId = mUnitId ?: profile?.getStudentData("unitId", 0L); return mUnitId ?: 0L }
set(value) { profile?.putStudentData("unitId", value) ?: return; mUnitId = value }
private var mStartPointsSemester1: Int? = null
var startPointsSemester1: Int
get() { mStartPointsSemester1 = mStartPointsSemester1 ?: profile?.getStudentData("startPointsSemester1", 0); return mStartPointsSemester1 ?: 0 }
set(value) { profile?.putStudentData("startPointsSemester1", value) ?: return; mStartPointsSemester1 = value }
private var mStartPointsSemester2: Int? = null
var startPointsSemester2: Int
get() { mStartPointsSemester2 = mStartPointsSemester2 ?: profile?.getStudentData("startPointsSemester2", 0); return mStartPointsSemester2 ?: 0 }
set(value) { profile?.putStudentData("startPointsSemester2", value) ?: return; mStartPointsSemester2 = value }
private var mEnablePointGrades: Boolean? = null
var enablePointGrades: Boolean
get() { mEnablePointGrades = mEnablePointGrades ?: profile?.getStudentData("enablePointGrades", true); return mEnablePointGrades ?: true }
set(value) { profile?.putStudentData("enablePointGrades", value) ?: return; mEnablePointGrades = value }
private var mEnableDescriptiveGrades: Boolean? = null
var enableDescriptiveGrades: Boolean
get() { mEnableDescriptiveGrades = mEnableDescriptiveGrades ?: profile?.getStudentData("enableDescriptiveGrades", true); return mEnableDescriptiveGrades ?: true }
set(value) { profile?.putStudentData("enableDescriptiveGrades", value) ?: return; mEnableDescriptiveGrades = value }
}

View File

@ -0,0 +1,202 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusData
import pl.szczodrzynski.edziennik.api.v2.librus.data.messages.LibrusMessagesGetMessage
import pl.szczodrzynski.edziennik.api.v2.librus.data.synergia.LibrusSynergiaMarkAllAnnouncementsAsRead
import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.LibrusFirstLogin
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLogin
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginApi
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginMessages
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginSynergia
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d
class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
companion object {
private const val TAG = "Librus"
}
val internalErrorList = mutableListOf<Int>()
val data: DataLibrus
init {
data = DataLibrus(app, profile, loginStore).apply {
callback = wrapCallback(this@Librus.callback)
satisfyLoginMethods()
}
}
private fun completed() {
data.saveData()
data.notifyAndSyncEvents {
callback.onCompleted()
}
}
/* _______ _ _ _ _ _
|__ __| | /\ | | (_) | | |
| | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___
| | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \
| | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | |
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
__/ |
|__*/
override fun sync(featureIds: List<Int>, viewId: Int?, arguments: JsonObject?) {
data.arguments = arguments
data.prepare(librusLoginMethods, LibrusFeatures, featureIds, viewId)
login()
}
private fun login() {
d(TAG, "Trying to login with ${data.targetLoginMethodIds}")
if (internalErrorList.isNotEmpty()) {
d(TAG, " - Internal errors:")
internalErrorList.forEach { d(TAG, " - code $it") }
}
LibrusLogin(data) {
data()
}
}
private fun data() {
d(TAG, "Endpoint IDs: ${data.targetEndpointIds}")
if (internalErrorList.isNotEmpty()) {
d(TAG, " - Internal errors:")
internalErrorList.forEach { d(TAG, " - code $it") }
}
LibrusData(data) {
completed()
}
}
override fun getMessage(message: MessageFull) {
LibrusLoginApi(data) {
LibrusLoginSynergia(data) {
LibrusLoginMessages(data) {
LibrusMessagesGetMessage(data, message) {
completed()
}
}
}
}
}
override fun markAllAnnouncementsAsRead() {
LibrusLoginApi(data) {
LibrusLoginSynergia(data) {
LibrusSynergiaMarkAllAnnouncementsAsRead(data) {
completed()
}
}
}
}
override fun firstLogin() {
LibrusFirstLogin(data) {
completed()
}
}
override fun cancel() {
d(TAG, "Cancelled")
data.cancel()
}
private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback {
return object : EdziennikCallback {
override fun onCompleted() {
callback.onCompleted()
}
override fun onProgress(step: Float) {
callback.onProgress(step)
}
override fun onStartProgress(stringRes: Int) {
callback.onStartProgress(stringRes)
}
override fun onError(apiError: ApiError) {
if (apiError.errorCode in internalErrorList) {
// finish immediately if the same error occurs twice during the same sync
callback.onError(apiError)
return
}
internalErrorList.add(apiError.errorCode)
when (apiError.errorCode) {
ERROR_LIBRUS_PORTAL_ACCESS_DENIED -> {
data.loginMethods.remove(LOGIN_METHOD_LIBRUS_PORTAL)
data.targetLoginMethodIds.add(LOGIN_METHOD_LIBRUS_PORTAL)
data.targetLoginMethodIds.sort()
data.portalTokenExpiryTime = 0
login()
}
ERROR_LIBRUS_API_ACCESS_DENIED,
ERROR_LIBRUS_API_TOKEN_EXPIRED -> {
data.loginMethods.remove(LOGIN_METHOD_LIBRUS_API)
data.targetLoginMethodIds.add(LOGIN_METHOD_LIBRUS_API)
data.targetLoginMethodIds.sort()
data.apiTokenExpiryTime = 0
login()
}
ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED -> {
data.loginMethods.remove(LOGIN_METHOD_LIBRUS_SYNERGIA)
data.targetLoginMethodIds.add(LOGIN_METHOD_LIBRUS_SYNERGIA)
data.targetLoginMethodIds.sort()
data.synergiaSessionIdExpiryTime = 0
login()
}
ERROR_LIBRUS_MESSAGES_ACCESS_DENIED -> {
data.loginMethods.remove(LOGIN_METHOD_LIBRUS_MESSAGES)
data.targetLoginMethodIds.add(LOGIN_METHOD_LIBRUS_MESSAGES)
data.targetLoginMethodIds.sort()
data.messagesSessionIdExpiryTime = 0
login()
}
ERROR_LOGIN_LIBRUS_PORTAL_NO_CODE,
ERROR_LOGIN_LIBRUS_PORTAL_CSRF_MISSING,
ERROR_LOGIN_LIBRUS_PORTAL_CODE_REVOKED,
ERROR_LOGIN_LIBRUS_PORTAL_CODE_EXPIRED -> {
login()
}
ERROR_LOGIN_LIBRUS_PORTAL_NO_REFRESH,
ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED,
ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_INVALID -> {
data.portalRefreshToken = null
login()
}
ERROR_LOGIN_LIBRUS_SYNERGIA_TOKEN_INVALID,
ERROR_LOGIN_LIBRUS_SYNERGIA_NO_TOKEN,
ERROR_LOGIN_LIBRUS_SYNERGIA_NO_SESSION_ID -> {
login()
}
ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID -> {
login()
}
// TODO PORTAL CAPTCHA
ERROR_LIBRUS_API_TIMETABLE_NOT_PUBLIC -> {
loginStore.putLoginData("timetableNotPublic", true)
data()
}
ERROR_LIBRUS_API_LUCKY_NUMBER_NOT_ACTIVE,
ERROR_LIBRUS_API_NOTES_NOT_ACTIVE -> {
data()
}
else -> callback.onError(apiError)
}
}
}
}
}

View File

@ -0,0 +1,240 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-11.
*/
package pl.szczodrzynski.edziennik.api.v2.librus
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.models.Feature
const val ENDPOINT_LIBRUS_API_ME = 1001
const val ENDPOINT_LIBRUS_API_SCHOOLS = 1002
const val ENDPOINT_LIBRUS_API_CLASSES = 1003
const val ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES = 1004
const val ENDPOINT_LIBRUS_API_UNITS = 1005
const val ENDPOINT_LIBRUS_API_USERS = 1006
const val ENDPOINT_LIBRUS_API_SUBJECTS = 1007
const val ENDPOINT_LIBRUS_API_CLASSROOMS = 1008
const val ENDPOINT_LIBRUS_API_PUSH_CONFIG = 1010
const val ENDPOINT_LIBRUS_API_TIMETABLES = 1015
const val ENDPOINT_LIBRUS_API_SUBSTITUTIONS = 1016
const val ENDPOINT_LIBRUS_API_NORMAL_GC = 1021
const val ENDPOINT_LIBRUS_API_POINT_GC = 1022
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GC = 1023
const val ENDPOINT_LIBRUS_API_TEXT_GC = 1024
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC = 1025
const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GC = 1026
const val ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS = 1030
const val ENDPOINT_LIBRUS_API_NORMAL_GRADES = 1031
const val ENDPOINT_LIBRUS_API_POINT_GRADES = 1032
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES = 1033
const val ENDPOINT_LIBRUS_API_TEXT_GRADES = 1034
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADES = 1035
const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES = 1036
const val ENDPOINT_LIBRUS_API_EVENT_TYPES = 1040
const val ENDPOINT_LIBRUS_API_EVENTS = 1041
const val ENDPOINT_LIBRUS_API_HOMEWORK = 1050
const val ENDPOINT_LIBRUS_API_LUCKY_NUMBER = 1060
const val ENDPOINT_LIBRUS_API_NOTICE_TYPES = 1070
const val ENDPOINT_LIBRUS_API_NOTICES = 1071
const val ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES = 1080
const val ENDPOINT_LIBRUS_API_ATTENDANCES = 1081
const val ENDPOINT_LIBRUS_API_ANNOUNCEMENTS = 1090
const val ENDPOINT_LIBRUS_API_PT_MEETINGS = 1100
const val ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES = 1109
const val ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS = 1110
const val ENDPOINT_LIBRUS_API_SCHOOL_FREE_DAYS = 1120
const val ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS = 1130
const val ENDPOINT_LIBRUS_SYNERGIA_INFO = 2010
const val ENDPOINT_LIBRUS_SYNERGIA_GRADES = 2020
const val ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK = 2030
const val ENDPOINT_LIBRUS_MESSAGES_RECEIVED = 3010
const val ENDPOINT_LIBRUS_MESSAGES_SENT = 3020
const val ENDPOINT_LIBRUS_MESSAGES_TRASH = 3030
const val ENDPOINT_LIBRUS_MESSAGES_RECEIVERS = 3040
const val ENDPOINT_LIBRUS_MESSAGES_GET = 3040
val LibrusFeatures = listOf(
// push config
Feature(LOGIN_TYPE_LIBRUS, FEATURE_PUSH_CONFIG, listOf(
ENDPOINT_LIBRUS_API_PUSH_CONFIG to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data ->
data.app.appConfig.fcmTokens[LOGIN_TYPE_LIBRUS]?.second?.contains(data.profileId) == false
},
/**
* Timetable - using API.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_TIMETABLE, listOf(
ENDPOINT_LIBRUS_API_TIMETABLES to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_SUBSTITUTIONS to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)),
/**
* Agenda - using API.
* Events, Parent-teacher meetings, free days (teacher/school/class).
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_AGENDA, listOf(
ENDPOINT_LIBRUS_API_EVENTS to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_EVENT_TYPES to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_PT_MEETINGS to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_SCHOOL_FREE_DAYS to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)),
/**
* Grades - using API.
* All grades + categories.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_GRADES, listOf(
ENDPOINT_LIBRUS_API_NORMAL_GC to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_POINT_GC to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_DESCRIPTIVE_GC to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_TEXT_GC to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_BEHAVIOUR_GC to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_NORMAL_GRADES to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_POINT_GRADES to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_TEXT_GRADES to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADES to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)),
/**
* Homework - using API.
* Sync only if account has premium access.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf(
ENDPOINT_LIBRUS_API_HOMEWORK to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data ->
(data as DataLibrus).isPremium
},
/**
* Behaviour - using API.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_BEHAVIOUR, listOf(
ENDPOINT_LIBRUS_API_NOTICES to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)),
/**
* Attendance - using API.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_ATTENDANCE, listOf(
ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_ATTENDANCES to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)),
/**
* Announcements - using API.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_ANNOUNCEMENTS, listOf(
ENDPOINT_LIBRUS_API_ANNOUNCEMENTS to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)),
/**
* Student info - using API.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_STUDENT_INFO, listOf(
ENDPOINT_LIBRUS_API_ME to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)),
/**
* School info - using API.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_SCHOOL_INFO, listOf(
ENDPOINT_LIBRUS_API_SCHOOLS to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_UNITS to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)),
/**
* Class info - using API.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_CLASS_INFO, listOf(
ENDPOINT_LIBRUS_API_CLASSES to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)),
/**
* Team info - using API.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_TEAM_INFO, listOf(
ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)),
/**
* Lucky number - using API.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_LUCKY_NUMBER, listOf(
ENDPOINT_LIBRUS_API_LUCKY_NUMBER to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data -> data.shouldSyncLuckyNumber() },
/**
* Teacher list - using API.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_TEACHERS, listOf(
ENDPOINT_LIBRUS_API_USERS to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)),
/**
* Subject list - using API.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_SUBJECTS, listOf(
ENDPOINT_LIBRUS_API_SUBJECTS to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)),
/**
* Classroom list - using API.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_CLASSROOMS, listOf(
ENDPOINT_LIBRUS_API_CLASSROOMS to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)),
/**
* Student info - using synergia scrapper.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_STUDENT_INFO, listOf(
ENDPOINT_LIBRUS_SYNERGIA_INFO to LOGIN_METHOD_LIBRUS_SYNERGIA
), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA)),
/**
* Student number - using synergia scrapper.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_STUDENT_NUMBER, listOf(
ENDPOINT_LIBRUS_SYNERGIA_INFO to LOGIN_METHOD_LIBRUS_SYNERGIA
), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA)),
/**
* Grades - using API + synergia scrapper.
*/
/*Feature(LOGIN_TYPE_LIBRUS, FEATURE_GRADES, listOf(
ENDPOINT_LIBRUS_API_NORMAL_GC to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_NORMAL_GRADES to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_SYNERGIA_GRADES to LOGIN_METHOD_LIBRUS_SYNERGIA
), listOf(LOGIN_METHOD_LIBRUS_API, LOGIN_METHOD_LIBRUS_SYNERGIA)),*/
/*Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_GRADES, listOf(
ENDPOINT_LIBRUS_SYNERGIA_GRADES to LOGIN_METHOD_LIBRUS_SYNERGIA
), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA)),*/
/**
* Homework - using scrapper.
* Sync only if account has not premium access.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf(
ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK to LOGIN_METHOD_LIBRUS_SYNERGIA
), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA)).withShouldSync { data ->
!(data as DataLibrus).isPremium
},
/**
* Messages inbox - using messages website.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_MESSAGES_INBOX, listOf(
ENDPOINT_LIBRUS_MESSAGES_RECEIVED to LOGIN_METHOD_LIBRUS_MESSAGES
), listOf(LOGIN_METHOD_LIBRUS_MESSAGES)),
/**
* Messages sent - using messages website.
*/
Feature(LOGIN_TYPE_LIBRUS, FEATURE_MESSAGES_SENT, listOf(
ENDPOINT_LIBRUS_MESSAGES_SENT to LOGIN_METHOD_LIBRUS_MESSAGES
), listOf(LOGIN_METHOD_LIBRUS_MESSAGES))
)

View File

@ -0,0 +1,119 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.JsonCallbackHandler
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection.*
open class LibrusApi(open val data: DataLibrus) {
companion object {
private const val TAG = "LibrusApi"
}
val profileId
get() = data.profile?.id ?: -1
val profile
get() = data.profile
fun apiGet(tag: String, endpoint: String, method: Int = GET, payload: JsonObject? = null, onSuccess: (json: JsonObject) -> Unit) {
d(tag, "Request: Librus/Api - ${if (data.fakeLogin) FAKE_LIBRUS_API else LIBRUS_API_URL}/$endpoint")
val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) {
if (response?.code() == HTTP_UNAVAILABLE) {
data.error(ApiError(tag, ERROR_LIBRUS_API_MAINTENANCE)
.withApiResponse(json)
.withResponse(response))
return
}
if (json == null && response?.parserErrorBody == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
val error = if (response?.code() == 200) null else
json.getString("Code") ?:
json.getString("Message") ?:
response?.parserErrorBody
error?.let { code ->
when (code) {
"TokenIsExpired" -> ERROR_LIBRUS_API_TOKEN_EXPIRED
"Insufficient scopes" -> ERROR_LIBRUS_API_INSUFFICIENT_SCOPES
"Request is denied" -> ERROR_LIBRUS_API_ACCESS_DENIED
"Resource not found" -> ERROR_LIBRUS_API_RESOURCE_NOT_FOUND
"NotFound" -> ERROR_LIBRUS_API_DATA_NOT_FOUND
"AccessDeny" -> when (json.getString("Message")) {
"Student timetable is not public" -> ERROR_LIBRUS_API_TIMETABLE_NOT_PUBLIC
else -> ERROR_LIBRUS_API_RESOURCE_ACCESS_DENIED
}
"LuckyNumberIsNotActive" -> ERROR_LIBRUS_API_LUCKY_NUMBER_NOT_ACTIVE
"NotesIsNotActive" -> ERROR_LIBRUS_API_NOTES_NOT_ACTIVE
"InvalidRequest" -> ERROR_LIBRUS_API_INVALID_REQUEST_PARAMS
"Nieprawidłowy węzeł." -> ERROR_LIBRUS_API_INCORRECT_ENDPOINT
else -> ERROR_LIBRUS_API_OTHER
}.let { errorCode ->
data.error(ApiError(tag, errorCode)
.withApiResponse(json)
.withResponse(response))
return
}
}
if (json == null) {
data.error(ApiError(tag, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
try {
onSuccess(json)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_LIBRUS_API_REQUEST)
.withResponse(response)
.withThrowable(e)
.withApiResponse(json))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
// TODO add hotfix for Classrooms 500
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url("${if (data.fakeLogin) FAKE_LIBRUS_API else LIBRUS_API_URL}/$endpoint")
.userAgent(LIBRUS_USER_AGENT)
.addHeader("Authorization", "Bearer ${data.apiAccessToken}")
.apply {
when (method) {
GET -> get()
POST -> post()
}
if (payload != null)
setJsonBody(payload)
}
.allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_FORBIDDEN)
.allowErrorCode(HTTP_UNAUTHORIZED)
.allowErrorCode(HTTP_UNAVAILABLE)
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -0,0 +1,174 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-5.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.librus.*
import pl.szczodrzynski.edziennik.api.v2.librus.data.api.*
import pl.szczodrzynski.edziennik.api.v2.librus.data.messages.LibrusMessagesGetList
import pl.szczodrzynski.edziennik.api.v2.librus.data.synergia.LibrusSynergiaHomework
import pl.szczodrzynski.edziennik.api.v2.librus.data.synergia.LibrusSynergiaInfo
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.utils.Utils
class LibrusData(val data: DataLibrus, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "LibrusEndpoints"
}
init {
nextEndpoint(onSuccess)
}
private fun nextEndpoint(onSuccess: () -> Unit) {
if (data.targetEndpointIds.isEmpty()) {
onSuccess()
return
}
if (data.cancelled) {
onSuccess()
return
}
useEndpoint(data.targetEndpointIds.removeAt(0)) {
data.progress(data.progressStep)
nextEndpoint(onSuccess)
}
}
private fun useEndpoint(endpointId: Int, onSuccess: () -> Unit) {
Utils.d(TAG, "Using endpoint $endpointId")
when (endpointId) {
/**
* API
*/
ENDPOINT_LIBRUS_API_ME -> {
data.startProgress(R.string.edziennik_progress_endpoint_student_info)
LibrusApiMe(data, onSuccess)
}
ENDPOINT_LIBRUS_API_SCHOOLS -> {
data.startProgress(R.string.edziennik_progress_endpoint_school_info)
LibrusApiSchools(data, onSuccess)
}
ENDPOINT_LIBRUS_API_CLASSES -> {
data.startProgress(R.string.edziennik_progress_endpoint_classes)
LibrusApiClasses(data, onSuccess)
}
ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES -> {
data.startProgress(R.string.edziennik_progress_endpoint_teams)
LibrusApiVirtualClasses(data, onSuccess)
}
ENDPOINT_LIBRUS_API_UNITS -> {
data.startProgress(R.string.edziennik_progress_endpoint_units)
LibrusApiUnits(data, onSuccess)
}
ENDPOINT_LIBRUS_API_USERS -> {
data.startProgress(R.string.edziennik_progress_endpoint_teachers)
LibrusApiUsers(data, onSuccess)
}
ENDPOINT_LIBRUS_API_SUBJECTS -> {
data.startProgress(R.string.edziennik_progress_endpoint_subjects)
LibrusApiSubjects(data, onSuccess)
}
ENDPOINT_LIBRUS_API_CLASSROOMS -> {
data.startProgress(R.string.edziennik_progress_endpoint_classrooms)
LibrusApiClassrooms(data, onSuccess)
}
// TODO push config
ENDPOINT_LIBRUS_API_TIMETABLES -> {
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
LibrusApiTimetables(data, onSuccess)
}
ENDPOINT_LIBRUS_API_NORMAL_GRADES -> {
data.startProgress(R.string.edziennik_progress_endpoint_grades)
LibrusApiGrades(data, onSuccess)
}
ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS -> {
data.startProgress(R.string.edziennik_progress_endpoint_grade_comments)
LibrusApiGradeComments(data, onSuccess)
}
ENDPOINT_LIBRUS_API_NORMAL_GC -> {
data.startProgress(R.string.edziennik_progress_endpoint_grade_categories)
LibrusApiGradeCategories(data, onSuccess)
}
// TODO grades
ENDPOINT_LIBRUS_API_EVENT_TYPES -> {
data.startProgress(R.string.edziennik_progress_endpoint_event_types)
LibrusApiEventTypes(data, onSuccess)
}
ENDPOINT_LIBRUS_API_EVENTS -> {
data.startProgress(R.string.edziennik_progress_endpoint_events)
LibrusApiEvents(data, onSuccess)
}
ENDPOINT_LIBRUS_API_HOMEWORK -> {
data.startProgress(R.string.edziennik_progress_endpoint_homework)
LibrusApiHomework(data, onSuccess)
}
ENDPOINT_LIBRUS_API_LUCKY_NUMBER -> {
data.startProgress(R.string.edziennik_progress_endpoint_lucky_number)
LibrusApiLuckyNumber(data, onSuccess)
}
ENDPOINT_LIBRUS_API_NOTICE_TYPES -> {
data.startProgress(R.string.edziennik_progress_endpoint_notice_types)
LibrusApiNoticeTypes(data, onSuccess)
}
ENDPOINT_LIBRUS_API_NOTICES -> {
data.startProgress(R.string.edziennik_progress_endpoint_notices)
LibrusApiNotices(data, onSuccess)
}
ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES -> {
data.startProgress(R.string.edziennik_progress_endpoint_attendance_types)
LibrusApiAttendanceTypes(data, onSuccess)
}
ENDPOINT_LIBRUS_API_ATTENDANCES -> {
data.startProgress(R.string.edziennik_progress_endpoint_attendance)
LibrusApiAttendances(data, onSuccess)
}
ENDPOINT_LIBRUS_API_ANNOUNCEMENTS -> {
data.startProgress(R.string.edziennik_progress_endpoint_announcements)
LibrusApiAnnouncements(data, onSuccess)
}
ENDPOINT_LIBRUS_API_PT_MEETINGS -> {
data.startProgress(R.string.edziennik_progress_endpoint_pt_meetings)
LibrusApiPtMeetings(data, onSuccess)
}
ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES -> {
data.startProgress(R.string.edziennik_progress_endpoint_teacher_free_day_types)
LibrusApiTeacherFreeDayTypes(data, onSuccess)
}
ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS -> {
data.startProgress(R.string.edziennik_progress_endpoint_teacher_free_days)
LibrusApiTeacherFreeDays(data, onSuccess)
}
/**
* SYNERGIA
*/
ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK -> {
data.startProgress(R.string.edziennik_progress_endpoint_homework)
LibrusSynergiaHomework(data, onSuccess)
}
ENDPOINT_LIBRUS_SYNERGIA_INFO -> {
data.startProgress(R.string.edziennik_progress_endpoint_student_info)
LibrusSynergiaInfo(data, onSuccess)
}
/**
* MESSAGES
*/
ENDPOINT_LIBRUS_MESSAGES_RECEIVED -> {
data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox)
LibrusMessagesGetList(data, type = Message.TYPE_RECEIVED, onSuccess = onSuccess)
}
ENDPOINT_LIBRUS_MESSAGES_SENT -> {
data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox)
LibrusMessagesGetList(data, type = Message.TYPE_SENT, onSuccess = onSuccess)
}
else -> onSuccess()
}
}
}

View File

@ -0,0 +1,134 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-24
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.parser.Parser
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.io.StringWriter
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
open class LibrusMessages(open val data: DataLibrus) {
companion object {
private const val TAG = "LibrusMessages"
}
val profileId
get() = data.profile?.id ?: -1
val profile
get() = data.profile
fun messagesGet(tag: String, endpoint: String, method: Int = POST,
parameters: Map<String, Any>? = null, onSuccess: (doc: Document) -> Unit) {
d(tag, "Request: Librus/Messages - $LIBRUS_MESSAGES_URL/$endpoint")
val callback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
if (text.isNullOrEmpty()) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
when {
text.contains("<message>Niepoprawny login i/lub hasło.</message>") -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text)
text.contains("stop.png") -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text)
text.contains("eAccessDeny") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
text.contains("OffLine") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text)
text.contains("<status>error</status>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text)
text.contains("<type>eVarWhitThisNameNotExists</type>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
text.contains("<error>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text)
}
try {
val doc = Jsoup.parse(text, "", Parser.xmlParser())
onSuccess(doc)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_REQUEST)
.withResponse(response)
.withThrowable(e)
.withApiResponse(text))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
data.app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("DZIENNIKSID")
.value(data.messagesSessionId!!)
.domain("wiadomosci.librus.pl")
.secure().httpOnly().build()
))
val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
val doc = docBuilder.newDocument()
val serviceElement = doc.createElement("service")
val headerElement = doc.createElement("header")
val dataElement = doc.createElement("data")
for ((key, value) in parameters.orEmpty()) {
val element = doc.createElement(key)
element.appendChild(doc.createTextNode(value.toString()))
dataElement.appendChild(element)
}
serviceElement.appendChild(headerElement)
serviceElement.appendChild(dataElement)
doc.appendChild(serviceElement)
val transformer = TransformerFactory.newInstance().newTransformer()
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
val stringWriter = StringWriter()
transformer.transform(DOMSource(doc), StreamResult(stringWriter))
val requestXml = stringWriter.toString()
/*val requestXml = xml("service") {
"header" { }
"data" {
for ((key, value) in parameters.orEmpty()) {
key {
-value.toString()
}
}
}
}.toString(PrintOptions(
singleLineTextElements = true,
useSelfClosingTags = true
))*/
Request.builder()
.url("$LIBRUS_MESSAGES_URL/$endpoint")
.userAgent(SYNERGIA_USER_AGENT)
.setTextBody(requestXml, MediaTypeUtils.APPLICATION_XML)
.apply {
when (method) {
GET -> get()
POST -> post()
}
}
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -0,0 +1,104 @@
package pl.szczodrzynski.edziennik.api.v2.librus.data
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.JsonCallbackHandler
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection
open class LibrusPortal(open val data: DataLibrus) {
companion object {
private const val TAG = "LibrusPortal"
}
val profileId
get() = data.profile?.id ?: -1
val profile
get() = data.profile
fun portalGet(tag: String, endpoint: String, method: Int = GET, payload: JsonObject? = null, onSuccess: (json: JsonObject, response: Response?) -> Unit) {
d(tag, "Request: Librus/Portal - ${if (data.fakeLogin) FAKE_LIBRUS_PORTAL else LIBRUS_PORTAL_URL}$endpoint")
val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) {
if (json == null) {
data.error(ApiError(tag, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
val error = if (response?.code() == 200) null else
json.getString("reason") ?:
json.getString("message") ?:
json.getString("hint") ?:
json.getString("Code")
error?.let { code ->
when (code) {
"requires_an_action" -> ERROR_LIBRUS_PORTAL_SYNERGIA_DISCONNECTED
"Access token is invalid" -> ERROR_LIBRUS_PORTAL_ACCESS_DENIED
"ApiDisabled" -> ERROR_LIBRUS_PORTAL_API_DISABLED
"Account not found" -> ERROR_LIBRUS_PORTAL_SYNERGIA_NOT_FOUND
else -> when (json.getString("hint")) {
"Error while decoding to JSON" -> ERROR_LIBRUS_PORTAL_ACCESS_DENIED
else -> ERROR_LIBRUS_PORTAL_OTHER
}
}.let { errorCode ->
data.error(ApiError(tag, errorCode)
.withApiResponse(json)
.withResponse(response))
return
}
}
if (response?.code() == HttpURLConnection.HTTP_OK) {
try {
onSuccess(json, response)
} catch (e: NullPointerException) {
e.printStackTrace()
data.error(ApiError(tag, EXCEPTION_LIBRUS_PORTAL_SYNERGIA_TOKEN)
.withResponse(response)
.withThrowable(e)
.withApiResponse(json))
}
} else {
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withApiResponse(json))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url((if (data.fakeLogin) FAKE_LIBRUS_PORTAL else LIBRUS_PORTAL_URL) + endpoint)
.userAgent(LIBRUS_USER_AGENT)
.addHeader("Authorization", "Bearer ${data.portalAccessToken}")
.apply {
when (method) {
GET -> get()
POST -> post()
}
if (payload != null)
setJsonBody(payload)
}
.allowErrorCode(HttpURLConnection.HTTP_NOT_FOUND)
.allowErrorCode(HttpURLConnection.HTTP_FORBIDDEN)
.allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED)
.allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST)
.allowErrorCode(HttpURLConnection.HTTP_GONE)
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -0,0 +1,93 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.TextCallbackHandler
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.utils.Utils.d
open class LibrusSynergia(open val data: DataLibrus) {
companion object {
private const val TAG = "LibrusSynergia"
}
val profileId
get() = data.profile?.id ?: -1
val profile
get() = data.profile
fun synergiaGet(tag: String, endpoint: String, method: Int = GET,
parameters: Map<String, Any> = emptyMap(), onSuccess: (text: String) -> Unit) {
d(tag, "Request: Librus/Synergia - $LIBRUS_SYNERGIA_URL/$endpoint")
val callback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
if (text.isNullOrEmpty()) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
if (!text.contains("jesteś zalogowany")) {
when {
text.contains("stop.png") -> ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED
text.contains("Przerwa techniczna") -> ERROR_LIBRUS_SYNERGIA_MAINTENANCE
else -> ERROR_LIBRUS_SYNERGIA_OTHER
}.let { errorCode ->
data.error(ApiError(tag, errorCode)
.withResponse(response)
.withApiResponse(text))
return
}
}
try {
onSuccess(text)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_LIBRUS_SYNERGIA_REQUEST)
.withResponse(response)
.withThrowable(e)
.withApiResponse(text))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
/*data.app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name("DZIENNIKSID")
.value(data.synergiaSessionId!!)
.domain("synergia.librus.pl")
.secure().httpOnly().build()
))*/
Request.builder()
.url("$LIBRUS_SYNERGIA_URL/$endpoint")
.userAgent(LIBRUS_USER_AGENT)
.apply {
when (method) {
GET -> get()
POST -> post()
}
parameters.map { (name, value) ->
addParameter(name, value)
}
}
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-13
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_ANNOUNCEMENTS
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.announcements.Announcement
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date
class LibrusApiAnnouncements(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiAnnouncements"
}
init {
apiGet(TAG, "SchoolNotices") { json ->
val announcements = json.getJsonArray("SchoolNotices").asJsonObjectList()
announcements?.forEach { announcement ->
val id = Utils.crc16(announcement.getString("Id")?.toByteArray()
?: return@forEach).toLong()
val subject = announcement.getString("Subject") ?: ""
val text = announcement.getString("Content") ?: ""
val startDate = Date.fromY_m_d(announcement.getString("StartDate"))
val endDate = Date.fromY_m_d(announcement.getString("EndDate"))
val teacherId = announcement.getJsonObject("AddedBy")?.getLong("Id") ?: -1
val addedDate = Date.fromIso(announcement.getString("CreationDate"))
val read = announcement.getBoolean("WasRead") ?: false
val announcementObject = Announcement(
profileId,
id,
subject,
text,
startDate,
endDate,
teacherId
)
data.announcementList.add(announcementObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_ANNOUNCEMENT,
id,
read,
read,
addedDate
))
}
data.setSyncNext(ENDPOINT_LIBRUS_API_ANNOUNCEMENTS, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-13
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import android.graphics.Color
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance
import pl.szczodrzynski.edziennik.data.db.modules.attendance.AttendanceType
class LibrusApiAttendanceTypes(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiAttendanceTypes"
}
init {
apiGet(TAG, "Attendances/Types") { json ->
val attendanceTypes = json.getJsonArray("Types").asJsonObjectList()
attendanceTypes?.forEach { attendanceType ->
val id = attendanceType.getLong("Id") ?: return@forEach
val name = attendanceType.getString("Name") ?: ""
val color = attendanceType.getString("ColorRGB")?.let { Color.parseColor("#$it") } ?: -1
val standardId = when (attendanceType.getBoolean("Standard") ?: false) {
true -> id
false -> attendanceType.getJsonObject("StandardType")?.getLong("Id") ?: id
}
val type = when (standardId) {
1L -> Attendance.TYPE_ABSENT
2L -> Attendance.TYPE_BELATED
3L -> Attendance.TYPE_ABSENT_EXCUSED
4L -> Attendance.TYPE_RELEASED
/*100*/else -> Attendance.TYPE_PRESENT
}
data.attendanceTypes.put(id, AttendanceType(profileId, id, name, type, color))
}
data.setSyncNext(ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES, 4*DAY)
onSuccess()
}
}
}

View File

@ -0,0 +1,78 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-13
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import androidx.core.util.isEmpty
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_ATTENDANCES
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date
class LibrusApiAttendances(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiAttendances"
}
init {
if (data.attendanceTypes.isEmpty()) {
data.db.attendanceTypeDao().getAllNow(profileId).toSparseArray(data.attendanceTypes) { it.id }
}
apiGet(TAG, "Attendances") { json ->
val attendances = json.getJsonArray("Attendances").asJsonObjectList()
attendances?.forEach { attendance ->
val id = Utils.strToInt((attendance.getString("Id") ?: return@forEach)
.replace("[^\\d.]".toRegex(), "")).toLong()
val teacherId = attendance.getJsonObject("AddedBy")?.getLong("Id") ?: -1
val lessonNo = attendance.getInt("LessonNo") ?: return@forEach
val startTime = data.lessonRanges.get(lessonNo).startTime
val lessonDate = Date.fromY_m_d(attendance.getString("Date"))
val subjectId = data.lessonList.singleOrNull {
it.weekDay == lessonDate.weekDay && it.startTime.value == startTime.value
}?.subjectId ?: -1
val semester = attendance.getInt("Semester") ?: return@forEach
val type = attendance.getJsonObject("Type")?.getLong("Id") ?: return@forEach
val typeObject = data.attendanceTypes.get(type)
val topic = typeObject?.name ?: ""
val attendanceObject = Attendance(
profileId,
id,
teacherId,
subjectId,
semester,
topic,
lessonDate,
startTime,
typeObject.type
)
val addedDate = Date.fromIso(attendance.getString("AddDate") ?: return@forEach)
data.attendanceList.add(attendanceObject)
if(typeObject.type != Attendance.TYPE_PRESENT) {
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_ATTENDANCE,
id,
profile?.empty ?: false,
profile?.empty ?: false,
addedDate
))
}
}
data.setSyncNext(ENDPOINT_LIBRUS_API_ATTENDANCES, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-14
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.DAY
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_CLASSES
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.getLong
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.models.Date
class LibrusApiClasses(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiClasses"
}
init {
apiGet(TAG, "Classes") { json ->
json.getJsonObject("Class")?.also { studentClass ->
val id = studentClass.getLong("Id") ?: return@also
val name = studentClass.getString("Number") +
studentClass.getString("Symbol")
val code = data.schoolName + ":" + name
val teacherId = studentClass.getJsonObject("ClassTutor")?.getLong("Id") ?: -1
val teamObject = Team(
profileId,
id,
name,
1,
code,
teacherId
)
data.teamList.put(id, teamObject)
data.unitId = studentClass.getJsonObject("Unit").getLong("Id") ?: 0L
profile?.apply {
dateSemester1Start = Date.fromY_m_d(studentClass.getString("BeginSchoolYear")
?: return@apply)
dateSemester2Start = Date.fromY_m_d(studentClass.getString("EndFirstSemester")
?: return@apply)
dateYearEnd = Date.fromY_m_d(studentClass.getString("EndSchoolYear")
?: return@apply)
}
}
data.setSyncNext(ENDPOINT_LIBRUS_API_CLASSES, 4 * DAY)
onSuccess()
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-24.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_CLASSROOMS
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.classrooms.Classroom
import java.util.*
class LibrusApiClassrooms(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiClassrooms"
}
init {
apiGet(TAG, "Classrooms") { json ->
val classrooms = json.getJsonArray("Classrooms").asJsonObjectList()
classrooms?.forEach { classroom ->
val id = classroom.getLong("Id") ?: return@forEach
val name = classroom.getString("Name")?.toLowerCase(Locale.getDefault()) ?: ""
val symbol = classroom.getString("Symbol")?.toLowerCase(Locale.getDefault()) ?: ""
val nameShort = name.split(" ").onEach { it[0] }.joinToString()
val friendlyName = if (name != symbol && !name.contains(symbol) && !nameShort.contains(symbol)) {
classroom.getString("Symbol") + " " + classroom.getString("Name")
}
else {
classroom.getString("Name") ?: ""
}
data.classrooms.put(id, Classroom(profileId, id, friendlyName))
}
data.setSyncNext(ENDPOINT_LIBRUS_API_CLASSROOMS, 4*DAY)
onSuccess()
}
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-24.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_EVENT_TYPES
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.events.EventType
class LibrusApiEventTypes(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiEventTypes"
}
init {
apiGet(TAG, "HomeWorks/Categories") { json ->
val eventTypes = json.getJsonArray("Categories").asJsonObjectList()
eventTypes?.forEach { eventType ->
val id = eventType.getLong("Id") ?: return@forEach
val name = eventType.getString("Name") ?: ""
val color = data.getColor(eventType.getJsonObject("Color")?.getInt("Id"))
data.eventTypes.put(id, EventType(profileId, id, name, color))
}
data.setSyncNext(ENDPOINT_LIBRUS_API_EVENT_TYPES, 4*DAY)
onSuccess()
}
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-4.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import androidx.core.util.isEmpty
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_EVENTS
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class LibrusApiEvents(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiEvents"
}
init {
if (data.eventTypes.isEmpty()) {
data.db.eventTypeDao().getAllNow(profileId).toSparseArray(data.eventTypes) { it.id }
}
apiGet(TAG, "HomeWorks") { json ->
val events = json.getJsonArray("HomeWorks").asJsonObjectList()
events?.forEach { event ->
val id = event.getLong("Id") ?: return@forEach
val eventDate = Date.fromY_m_d(event.getString("Date"))
val topic = event.getString("Content") ?: ""
val type = event.getJsonObject("Category")?.getInt("Id") ?: -1
val teacherId = event.getJsonObject("CreatedBy")?.getLong("Id") ?: -1
val subjectId = event.getJsonObject("Subject")?.getLong("Id") ?: -1
val teamId = event.getJsonObject("Class")?.getLong("Id") ?: -1
val lessonNo = event.getInt("LessonNo")
val lessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNo }
val startTime = lessonRange?.startTime ?: Time.fromH_m(event.getString("TimeFrom"))
val addedDate = Date.fromIso(event.getString("AddDate"))
val eventObject = Event(
profileId,
id,
eventDate,
startTime,
topic,
-1,
type,
false,
teacherId,
subjectId,
teamId
)
data.eventList.add(eventObject)
data.metadataList.add(
Metadata(
profileId,
Metadata.TYPE_EVENT,
id,
profile?.empty ?: false,
profile?.empty ?: false,
addedDate
))
}
data.setSyncNext(ENDPOINT_LIBRUS_API_EVENTS, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-5
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import android.graphics.Color
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_NORMAL_GC
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeCategory
class LibrusApiGradeCategories(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiGradeCategories"
}
init {
apiGet(TAG, "Grades/Categories") { json ->
json.getJsonArray("Categories")?.asJsonObjectList()?.forEach { category ->
val id = category.getLong("Id") ?: return@forEach
val name = category.getString("Name") ?: ""
val weight = when (category.getBoolean("CountToTheAverage")) {
true -> category.getFloat("Weight") ?: 0f
else -> 0f
}
val color = category.getJsonObject("Color")?.getInt("Id")
?.let { data.getColor(it) } ?: Color.BLUE
val gradeCategoryObject = GradeCategory(
profileId,
id,
weight,
color,
name
)
data.gradeCategories.put(id, gradeCategoryObject)
}
data.setSyncNext(ENDPOINT_LIBRUS_API_NORMAL_GC, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-20
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.asJsonObjectList
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeCategory
import pl.szczodrzynski.edziennik.getJsonArray
import pl.szczodrzynski.edziennik.getLong
import pl.szczodrzynski.edziennik.getString
class LibrusApiGradeComments(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiGradeComments"
}
init {
apiGet(TAG, "Grades/Comments") { json ->
json.getJsonArray("Comments")?.asJsonObjectList()?.forEach { comment ->
val id = comment.getLong("Id") ?: return@forEach
val text = comment.getString("Text")
val gradeCategoryObject = GradeCategory(
profileId,
id,
-1f,
-1,
text
).apply {
type = GradeCategory.TYPE_COMMENT
}
data.gradeCategories.put(id, gradeCategoryObject)
}
data.setSyncNext(ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -0,0 +1,107 @@
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADES
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeCategory
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date
class LibrusApiGrades(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiGrades"
}
init {
apiGet(TAG, "Grades") { json ->
val grades = json.getJsonArray("Grades").asJsonObjectList()
grades?.forEach { grade ->
val id = grade.getLong("Id") ?: return@forEach
val categoryId = grade.getJsonObject("Category")?.getLong("Id") ?: -1
val name = grade.getString("Grade") ?: ""
val semester = grade.getInt("Semester") ?: return@forEach
val teacherId = grade.getJsonObject("AddedBy")?.getLong("Id") ?: -1
val subjectId = grade.getJsonObject("Subject")?.getLong("Id") ?: -1
val addedDate = Date.fromIso(grade.getString("AddDate"))
val category = data.gradeCategories.singleOrNull { it.categoryId == categoryId }
val categoryName = category?.text ?: ""
val color = category?.color ?: -1
var weight = category?.weight ?: 0f
val value = Utils.getGradeValue(name)
if (name == "-" || name == "+"
|| name.equals("np", ignoreCase = true)
|| name.equals("bz", ignoreCase = true)) {
weight = 0f
}
val description = grade.getJsonArray("Comments")?.asJsonObjectList()?.let { comments ->
if (comments.isNotEmpty()) {
data.gradeCategories.singleOrNull {
it.type == GradeCategory.TYPE_COMMENT
&& it.categoryId == comments[0].asJsonObject.getLong("Id")
}?.text
} else null
} ?: ""
val gradeObject = Grade(
profileId,
id,
categoryName,
color,
description,
name,
value,
weight,
semester,
teacherId,
subjectId
)
when {
grade.getBoolean("IsConstituent") ?: false ->
gradeObject.type = Grade.TYPE_NORMAL
grade.getBoolean("IsSemester") ?: false -> // semester final
gradeObject.type = if (gradeObject.semester == 1) Grade.TYPE_SEMESTER1_FINAL else Grade.TYPE_SEMESTER2_FINAL
grade.getBoolean("IsSemesterProposition") ?: false -> // semester proposed
gradeObject.type = if (gradeObject.semester == 1) Grade.TYPE_SEMESTER1_PROPOSED else Grade.TYPE_SEMESTER2_PROPOSED
grade.getBoolean("IsFinal") ?: false -> // year final
gradeObject.type = Grade.TYPE_YEAR_FINAL
grade.getBoolean("IsFinalProposition") ?: false -> // year final
gradeObject.type = Grade.TYPE_YEAR_PROPOSED
}
grade.getJsonObject("Improvement")?.also {
val historicalId = it.getLong("Id")
data.gradeList.firstOrNull { grade -> grade.id == historicalId }?.also { grade ->
grade.parentId = gradeObject.id
if (grade.name == "nb") grade.weight = 0f
}
gradeObject.isImprovement = true
}
data.gradeList.add(gradeObject)
data.metadataList.add(
Metadata(
profileId,
Metadata.TYPE_GRADE,
id,
profile?.empty ?: false,
profile?.empty ?: false,
addedDate
))
}
data.setSyncNext(ENDPOINT_LIBRUS_API_NORMAL_GRADES, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-12.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_HOMEWORK
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.models.Date
class LibrusApiHomework(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiHomework"
}
init {
apiGet(TAG, "HomeWorkAssignments") { json ->
val homeworkList = json.getJsonArray("HomeWorkAssignments").asJsonObjectList()
homeworkList?.forEach { homework ->
val id = homework.getLong("Id") ?: return@forEach
val eventDate = Date.fromY_m_d(homework.getString("DueDate"))
val topic = homework.getString("Topic") + "\n" + homework.getString("Text")
val teacherId = homework.getJsonObject("Teacher")?.getLong("Id") ?: -1
val addedDate = Date.fromY_m_d(homework.getString("Date"))
val eventObject = Event(
profileId,
id,
eventDate,
null,
topic,
-1,
-1,
false,
teacherId,
-1,
-1
)
data.eventList.add(eventObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_HOMEWORK,
id,
profile?.empty ?: false,
profile?.empty ?: false,
addedDate.inMillis
))
}
data.setSyncNext(ENDPOINT_LIBRUS_API_HOMEWORK, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-14
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.DAY
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_LUCKY_NUMBER
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.luckynumber.LuckyNumber
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.getInt
import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class LibrusApiLuckyNumber(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiLuckyNumber"
}
init {
data.profile?.luckyNumber = -1
data.profile?.luckyNumberDate = null
var nextSync = System.currentTimeMillis() + 2*DAY*1000
apiGet(TAG, "LuckyNumbers") { json ->
if (json.isJsonNull) {
//profile?.luckyNumberEnabled = false
} else {
json.getJsonObject("LuckyNumber")?.also { luckyNumberEl ->
val luckyNumberDate = Date.fromY_m_d(luckyNumberEl.getString("LuckyNumberDay")) ?: Date.getToday()
val luckyNumber = luckyNumberEl.getInt("LuckyNumber") ?: -1
val luckyNumberObject = LuckyNumber(
profileId,
luckyNumberDate,
luckyNumber
)
//if (luckyNumberDate > Date.getToday()) {
nextSync = luckyNumberDate.combineWith(Time(15, 0, 0))
//}
data.luckyNumberList.add(luckyNumberObject)
data.metadataList.add(
Metadata(
profileId,
Metadata.TYPE_LUCKY_NUMBER,
luckyNumberObject.date.value.toLong(),
profile?.empty ?: false,
profile?.empty ?: false,
System.currentTimeMillis()
))
}
}
data.setSyncNext(ENDPOINT_LIBRUS_API_LUCKY_NUMBER, syncAt = nextSync)
onSuccess()
}
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-3.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_ME
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
class LibrusApiMe(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiMe"
}
init {
apiGet(TAG, "Me") { json ->
val me = json.getJsonObject("Me")
val account = me?.getJsonObject("Account")
val user = me?.getJsonObject("User")
data.isPremium = account?.getBoolean("IsPremium") == true || account?.getBoolean("IsPremiumDemo") == true
val isParent = account?.getInt("GroupId") == 5
data.profile?.accountNameLong =
if (isParent)
buildFullName(account?.getString("FirstName"), account?.getString("LastName"))
else null
data.profile?.studentNameLong =
buildFullName(user?.getString("FirstName"), user?.getString("LastName"))
data.setSyncNext(ENDPOINT_LIBRUS_API_ME, 2*DAY)
onSuccess()
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-24.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_NOTICE_TYPES
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.notices.NoticeType
class LibrusApiNoticeTypes(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiNoticeTypes"
}
init {
apiGet(TAG, "Notes/Categories") { json ->
val noticeTypes = json.getJsonArray("Categories").asJsonObjectList()
noticeTypes?.forEach { noticeType ->
val id = noticeType.getLong("Id") ?: return@forEach
val name = noticeType.getString("CategoryName") ?: ""
data.noticeTypes.put(id, NoticeType(profileId, id, name))
}
data.setSyncNext(ENDPOINT_LIBRUS_API_NOTICE_TYPES, 4*DAY)
onSuccess()
}
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-24.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import androidx.core.util.isEmpty
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_NOTICES
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.notices.Notice
import pl.szczodrzynski.edziennik.utils.models.Date
class LibrusApiNotices(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiNotices"
}
init {
if (data.noticeTypes.isEmpty()) {
data.db.noticeTypeDao().getAllNow(profileId).toSparseArray(data.noticeTypes) { it.id }
}
apiGet(TAG, "Notes") { json ->
val notes = json.getJsonArray("Notes").asJsonObjectList()
notes?.forEach { note ->
val id = note.getLong("Id") ?: return@forEach
val text = note.getString("Text") ?: ""
val categoryId = note.getJsonObject("Category")?.getLong("Id") ?: -1
val teacherId = note.getJsonObject("AddedBy")?.getLong("Id") ?: -1
val addedDate = note.getString("Date")?.let { Date.fromY_m_d(it) } ?: return@forEach
val type = when (note.getInt("Positive")) {
0 -> Notice.TYPE_NEGATIVE
1 -> Notice.TYPE_POSITIVE
/*2*/else -> Notice.TYPE_NEUTRAL
}
val categoryText = data.noticeTypes[categoryId]?.name ?: ""
val semester = profile?.dateToSemester(addedDate) ?: 1
val noticeObject = Notice(
profileId,
id,
categoryText+"\n"+text,
semester,
type,
teacherId
)
data.noticeList.add(noticeObject)
data.metadataList.add(
Metadata(
profileId,
Metadata.TYPE_NOTICE,
id,
profile?.empty ?: false,
profile?.empty ?: false,
addedDate.inMillis
))
}
data.setSyncNext(ENDPOINT_LIBRUS_API_NOTICES, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -0,0 +1,68 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-24.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_PT_MEETINGS
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class LibrusApiPtMeetings(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiPtMeetings"
}
init {
apiGet(TAG, "ParentTeacherConferences") { json ->
val ptMeetings = json.getJsonArray("ParentTeacherConferences").asJsonObjectList()
ptMeetings?.forEach { meeting ->
val id = meeting.getLong("Id") ?: return@forEach
val topic = meeting.getString("Topic") ?: ""
val teacherId = meeting.getJsonObject("Teacher")?.getLong("Id") ?: -1
val eventDate = meeting.getString("Date")?.let { Date.fromY_m_d(it) } ?: return@forEach
val startTime = meeting.getString("Time")?.let {
if (it == "00:00:00")
null
else
Time.fromH_m_s(it)
}
val eventObject = Event(
profileId,
id,
eventDate,
startTime,
topic,
-1,
Event.TYPE_PT_MEETING,
false,
teacherId,
-1,
data.teamClass?.id ?: -1
)
data.eventList.add(eventObject)
data.metadataList.add(
Metadata(
profileId,
Metadata.TYPE_EVENT,
id,
profile?.empty ?: false,
profile?.empty ?: false,
System.currentTimeMillis()
))
}
data.setSyncNext(ENDPOINT_LIBRUS_API_PT_MEETINGS, 12*HOUR)
onSuccess()
}
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-4.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_SCHOOLS
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonRange
import pl.szczodrzynski.edziennik.utils.models.Time
import java.util.*
class LibrusApiSchools(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiSchools"
}
init {
apiGet(TAG, "Schools") { json ->
val school = json.getJsonObject("School")
val schoolId = school?.getInt("Id")
val schoolNameLong = school?.getString("Name")
// create the school's short name using first letters of each long name's word
// append the town name and save to student data
var schoolNameShort = ""
schoolNameLong?.split(" ")?.forEach {
if (it.isBlank())
return@forEach
schoolNameShort += it[0].toLowerCase()
}
val schoolTown = school?.getString("Town")?.toLowerCase(Locale.getDefault())
data.schoolName = schoolId.toString() + schoolNameShort + "_" + schoolTown
school?.getJsonArray("LessonsRange")?.let { ranges ->
data.lessonRanges.clear()
ranges.forEachIndexed { index, rangeEl ->
val range = rangeEl.asJsonObject
val from = range.getString("From") ?: return@forEachIndexed
val to = range.getString("To") ?: return@forEachIndexed
data.lessonRanges.put(
index,
LessonRange(
profileId,
index,
Time.fromH_m(from),
Time.fromH_m(to)
))
}
}
data.setSyncNext(ENDPOINT_LIBRUS_API_SCHOOLS, 4 * DAY)
onSuccess()
}
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-23.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_SUBJECTS
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject
class LibrusApiSubjects(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiSubjects"
}
init {
apiGet(TAG, "Subjects") { json ->
val subjects = json.getJsonArray("Subjects").asJsonObjectList()
subjects?.forEach { subject ->
val id = subject.getLong("Id") ?: return@forEach
val longName = subject.getString("Name") ?: ""
val shortName = subject.getString("Short") ?: ""
data.subjectList.put(id, Subject(profileId, id, longName, shortName))
}
data.subjectList.put(1, Subject(profileId, 1, "Zachowanie", "zach"))
data.setSyncNext(ENDPOINT_LIBRUS_API_SUBJECTS, 4*DAY)
onSuccess()
}
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-19
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.teachers.TeacherAbsenceType
class LibrusApiTeacherFreeDayTypes(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiTeacherFreeDayTypes"
}
init {
apiGet(TAG, "TeacherFreeDays/Types") { json ->
val teacherAbsenceTypes = json.getJsonArray("Types").asJsonObjectList()
teacherAbsenceTypes?.forEach { teacherAbsenceType ->
val id = teacherAbsenceType.getLong("Id") ?: return@forEach
val name = teacherAbsenceType.getString("Name") ?: return@forEach
val teacherAbsenceTypeObject = TeacherAbsenceType(
profileId,
id,
name
)
data.teacherAbsenceTypes.put(id, teacherAbsenceTypeObject)
}
data.setSyncNext(ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES, 7 * DAY)
onSuccess()
}
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-4.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import androidx.core.util.isEmpty
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.teachers.TeacherAbsence
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class LibrusApiTeacherFreeDays(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiTeacherFreeDays"
}
init {
if (data.teacherAbsenceTypes.isEmpty()) {
data.db.teacherAbsenceTypeDao().getAllNow(profileId).toSparseArray(data.teacherAbsenceTypes) { it.id }
}
apiGet(TAG, "TeacherFreeDays") { json ->
val teacherAbsences = json.getJsonArray("TeacherFreeDays").asJsonObjectList()
teacherAbsences?.forEach { teacherAbsence ->
val id = teacherAbsence.getLong("Id") ?: return@forEach
val teacherId = teacherAbsence.getJsonObject("Teacher")?.getLong("Id")
?: return@forEach
val type = teacherAbsence.getJsonObject("Type").getLong("Id") ?: return@forEach
val name = data.teacherAbsenceTypes.singleOrNull { it.id == type }?.name
val dateFrom = Date.fromY_m_d(teacherAbsence.getString("DateFrom"))
val dateTo = Date.fromY_m_d(teacherAbsence.getString("DateTo"))
val timeFrom = teacherAbsence.getString("TimeFrom")?.let { Time.fromH_m_s(it) }
val timeTo = teacherAbsence.getString("TimeTo")?.let { Time.fromH_m_s(it) }
val teacherAbsenceObject = TeacherAbsence(
profileId,
id,
teacherId,
type,
name,
dateFrom,
dateTo,
timeFrom,
timeTo
)
data.teacherAbsenceList.add(teacherAbsenceObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_TEACHER_ABSENCE,
id,
profile?.empty ?: false,
profile?.empty ?: false,
System.currentTimeMillis()
))
}
data.setSyncNext(ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS, 6*HOUR, DRAWER_ITEM_AGENDA)
onSuccess()
}
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-4.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
class LibrusApiTemplate(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApi"
}
init {
/*apiGet(TAG, "") { json ->
data.setSyncNext(ENDPOINT_LIBRUS_API_, SYNC_ALWAYS)
onSuccess()
}*/
}
}

View File

@ -0,0 +1,192 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-10.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import androidx.core.util.isEmpty
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_TIMETABLES
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class LibrusApiTimetables(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiTimetables"
}
init {
if (data.classrooms.isEmpty()) {
data.db.classroomDao().getAllNow(profileId).toSparseArray(data.classrooms) { it.id }
}
val currentWeekStart = Date.getToday().let { it.stepForward(0, 0, -it.weekDay) }
val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d
apiGet(TAG, "Timetables?weekStart=$getDate") { json ->
val days = json.getJsonObject("Timetable")
days?.entrySet()?.forEach { (dateString, dayEl) ->
val day = dayEl?.asJsonArray
val lessonDate = dateString?.let { Date.fromY_m_d(it) } ?: return@forEach
var lessonsFound = false
day?.forEach { lessonRangeEl ->
val lessonRange = lessonRangeEl?.asJsonArray?.asJsonObjectList()
if (lessonRange?.isNullOrEmpty() == false)
lessonsFound = true
lessonRange?.forEach { lesson ->
parseLesson(lessonDate, lesson)
}
}
if (day.isNullOrEmpty() || !lessonsFound) {
data.lessonNewList.add(Lesson(profileId, lessonDate.value.toLong()).apply {
type = Lesson.TYPE_NO_LESSONS
date = lessonDate
})
}
}
val weekStart = Date.fromY_m_d(getDate)
val weekEnd = weekStart.clone().stepForward(0, 0, 6)
d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate")
data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd))
data.setSyncNext(ENDPOINT_LIBRUS_API_TIMETABLES, SYNC_ALWAYS)
onSuccess()
}
}
private fun parseLesson(lessonDate: Date, lesson: JsonObject) {
val isSubstitution = lesson.getBoolean("IsSubstitutionClass") ?: false
val isCancelled = lesson.getBoolean("IsCanceled") ?: false
val lessonNo = lesson.getInt("LessonNo") ?: return
val startTime = lesson.getString("HourFrom")?.let { Time.fromH_m(it) } ?: return
val endTime = lesson.getString("HourTo")?.let { Time.fromH_m(it) } ?: return
val subjectId = lesson.getJsonObject("Subject")?.getLong("Id")
val teacherId = lesson.getJsonObject("Teacher")?.getLong("Id")
val classroomId = lesson.getJsonObject("Classroom")?.getLong("Id") ?: -1
val virtualClassId = lesson.getJsonObject("VirtualClass")?.getLong("Id")
val teamId = lesson.getJsonObject("Class")?.getLong("Id") ?: virtualClassId
val id = lessonDate.combineWith(startTime) / 6L * 10L + (lesson.hashCode() and 0xFFFF)
val lessonObject = Lesson(profileId, id)
if (isSubstitution && isCancelled) {
// shifted lesson - source
val newDate = lesson.getString("NewDate")?.let { Date.fromY_m_d(it) } ?: return
val newLessonNo = lesson.getInt("NewLessonNo") ?: return
val newStartTime = lesson.getString("NewHourFrom")?.let { Time.fromH_m(it) } ?: return
val newEndTime = lesson.getString("NewHourTo")?.let { Time.fromH_m(it) } ?: return
val newSubjectId = lesson.getJsonObject("NewSubject")?.getLong("Id")
val newTeacherId = lesson.getJsonObject("NewTeacher")?.getLong("Id")
val newClassroomId = lesson.getJsonObject("NewClassroom")?.getLong("Id") ?: -1
val newVirtualClassId = lesson.getJsonObject("NewVirtualClass")?.getLong("Id")
val newTeamId = lesson.getJsonObject("NewClass")?.getLong("Id") ?: newVirtualClassId
lessonObject.let {
it.type = Lesson.TYPE_SHIFTED_SOURCE
it.oldDate = lessonDate
it.oldLessonNumber = lessonNo
it.oldStartTime = startTime
it.oldEndTime = endTime
it.oldSubjectId = subjectId
it.oldTeacherId = teacherId
it.oldTeamId = teamId
it.oldClassroom = data.classrooms[classroomId]?.name
it.date = newDate
it.lessonNumber = newLessonNo
it.startTime = newStartTime
it.endTime = newEndTime
it.subjectId = newSubjectId
it.teacherId = newTeacherId
it.teamId = newTeamId
it.classroom = data.classrooms[newClassroomId]?.name
}
}
else if (isSubstitution) {
// lesson change OR shifted lesson - target
val oldDate = lesson.getString("OrgDate")?.let { Date.fromY_m_d(it) } ?: return
val oldLessonNo = lesson.getInt("OrgLessonNo") ?: return
val oldStartTime = lesson.getString("OrgHourFrom")?.let { Time.fromH_m(it) } ?: return
val oldEndTime = lesson.getString("OrgHourTo")?.let { Time.fromH_m(it) } ?: return
val oldSubjectId = lesson.getJsonObject("OrgSubject")?.getLong("Id")
val oldTeacherId = lesson.getJsonObject("OrgTeacher")?.getLong("Id")
val oldClassroomId = lesson.getJsonObject("OrgClassroom")?.getLong("Id") ?: -1
val oldVirtualClassId = lesson.getJsonObject("OrgVirtualClass")?.getLong("Id")
val oldTeamId = lesson.getJsonObject("OrgClass")?.getLong("Id") ?: oldVirtualClassId
lessonObject.let {
it.type = if (lessonDate == oldDate && lessonNo == oldLessonNo) Lesson.TYPE_CHANGE else Lesson.TYPE_SHIFTED_TARGET
it.oldDate = oldDate
it.oldLessonNumber = oldLessonNo
it.oldStartTime = oldStartTime
it.oldEndTime = oldEndTime
it.oldSubjectId = oldSubjectId
it.oldTeacherId = oldTeacherId
it.oldTeamId = oldTeamId
it.oldClassroom = data.classrooms[oldClassroomId]?.name
it.date = lessonDate
it.lessonNumber = lessonNo
it.startTime = startTime
it.endTime = endTime
it.subjectId = subjectId
it.teacherId = teacherId
it.teamId = teamId
it.classroom = data.classrooms[classroomId]?.name
}
}
else if (isCancelled) {
lessonObject.let {
it.type = Lesson.TYPE_CANCELLED
it.oldDate = lessonDate
it.oldLessonNumber = lessonNo
it.oldStartTime = startTime
it.oldEndTime = endTime
it.oldSubjectId = subjectId
it.oldTeacherId = teacherId
it.oldTeamId = teamId
it.oldClassroom = data.classrooms[classroomId]?.name
}
}
else {
lessonObject.let {
it.type = Lesson.TYPE_NORMAL
it.date = lessonDate
it.lessonNumber = lessonNo
it.startTime = startTime
it.endTime = endTime
it.subjectId = subjectId
it.teacherId = teacherId
it.teamId = teamId
it.classroom = data.classrooms[classroomId]?.name
}
}
if (lessonObject.type != Lesson.TYPE_NORMAL) {
data.metadataList.add(
Metadata(
data.profileId,
Metadata.TYPE_LESSON_CHANGE,
lessonObject.id,
data.profile?.empty ?: false,
data.profile?.empty ?: false,
System.currentTimeMillis()
))
}
data.lessonNewList.add(lessonObject)
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-23.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_UNITS
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
class LibrusApiUnits(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiUnits"
}
init { run {
if (data.unitId == 0L) {
data.setSyncNext(ENDPOINT_LIBRUS_API_UNITS, 12 * DAY)
onSuccess()
return@run
}
apiGet(TAG, "Units") { json ->
val units = json.getJsonArray("Units").asJsonObjectList()
units?.singleOrNull { it.getLong("Id") == data.unitId }?.also { unit ->
val startPoints = unit.getJsonObject("BehaviourGradesSettings")?.getJsonObject("StartPoints")
startPoints?.apply {
data.startPointsSemester1 = getInt("Semester1", defaultValue = 0)
data.startPointsSemester2 = getInt("Semester2", defaultValue = data.startPointsSemester1)
}
unit.getJsonObject("GradesSettings")?.apply {
data.enablePointGrades = getBoolean("PointGradesEnabled", true)
data.enableDescriptiveGrades = getBoolean("DescriptiveGradesEnabled", true)
}
}
data.setSyncNext(ENDPOINT_LIBRUS_API_UNITS, 7 * DAY)
onSuccess()
}
}}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-23.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_USERS
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
class LibrusApiUsers(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiUsers"
}
init {
apiGet(TAG, "Users") { json ->
val users = json.getJsonArray("Users").asJsonObjectList()
users?.forEach { user ->
val id = user.getLong("Id") ?: return@forEach
val firstName = user.getString("FirstName")?.fixName() ?: ""
val lastName = user.getString("LastName")?.fixName() ?: ""
data.teacherList.put(id, Teacher(profileId, id, firstName, lastName))
}
data.setSyncNext(ENDPOINT_LIBRUS_API_USERS, 4*DAY)
onSuccess()
}
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-23.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
class LibrusApiVirtualClasses(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiVirtualClasses"
}
init {
apiGet(TAG, "VirtualClasses") { json ->
val virtualClasses = json.getJsonArray("VirtualClasses").asJsonObjectList()
virtualClasses?.forEach { virtualClass ->
val id = virtualClass.getLong("Id") ?: return@forEach
val name = virtualClass.getString("Name") ?: ""
val teacherId = virtualClass.getJsonObject("Teacher")?.getLong("Id") ?: -1
val code = "${data.schoolName}:$name"
data.teamList.put(id, Team(profileId, id, name, 2, code, teacherId))
}
data.setSyncNext(ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES, 4*DAY)
onSuccess()
}
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-24
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.messages
import pl.szczodrzynski.edziennik.DAY
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
import pl.szczodrzynski.edziennik.api.v2.ERROR_NOT_IMPLEMENTED
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_MESSAGES_RECEIVED
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_MESSAGES_SENT
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusMessages
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
import pl.szczodrzynski.edziennik.singleOrNull
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date
class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int = TYPE_RECEIVED,
archived: Boolean = false, val onSuccess: () -> Unit) : LibrusMessages(data) {
companion object {
const val TAG = "LibrusMessagesGetList"
}
init {
val endpoint = when (type) {
TYPE_RECEIVED -> "Inbox/action/GetList"
Message.TYPE_SENT -> "Outbox/action/GetList"
else -> null
}
if (endpoint != null) {
messagesGet(TAG, endpoint, parameters = mapOf(
"archive" to if (archived) 1 else 0
)) { doc ->
doc.select("GetList data").firstOrNull()?.children()?.forEach { element ->
val id = element.select("messageId").text().toLong()
val subject = element.select("topic").text().trim()
val readDateText = element.select("readDate").text().trim()
val readDate = when (readDateText.isNotBlank()) {
true -> Date.fromIso(readDateText)
else -> 0
}
val sentDate = Date.fromIso(element.select("sendDate").text().trim())
val recipientFirstName = element.select(when (type) {
TYPE_RECEIVED -> "senderFirstName"
else -> "receiverFirstName"
}).text().trim()
val recipientLastName = element.select(when (type) {
TYPE_RECEIVED -> "senderLastName"
else -> "receiverLastName"
}).text().trim()
val recipientId = data.teacherList.singleOrNull {
it.name == recipientFirstName && it.surname == recipientLastName
}?.id ?: {
val teacherObject = Teacher(
profileId,
-1 * Utils.crc16("$recipientFirstName $recipientLastName".toByteArray()).toLong(),
recipientFirstName,
recipientLastName
)
data.teacherList.put(teacherObject.id, teacherObject)
teacherObject.id
}.invoke()
val senderId = when (type) {
TYPE_RECEIVED -> recipientId
else -> -1
}
val receiverId = when (type) {
TYPE_RECEIVED -> -1
else -> recipientId
}
val notified = when (type) {
Message.TYPE_SENT -> true
else -> readDate > 0 || profile?.empty ?: false
}
val messageObject = Message(
profileId,
id,
subject,
null,
type,
senderId,
-1
)
val messageRecipientObject = MessageRecipient(
profileId,
receiverId,
-1,
readDate,
id
)
data.messageIgnoreList.add(messageObject)
data.messageRecipientList.add(messageRecipientObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_MESSAGE,
id,
notified,
notified,
sentDate
))
}
when (type) {
TYPE_RECEIVED -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_RECEIVED, SYNC_ALWAYS)
Message.TYPE_SENT -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_SENT, DAY, DRAWER_ITEM_MESSAGES)
}
onSuccess()
}
} else {
data.error(TAG, ERROR_NOT_IMPLEMENTED)
onSuccess()
}
}
}

View File

@ -0,0 +1,132 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-11
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.messages
import android.util.Base64
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.api.v2.events.MessageGetEvent
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusMessages
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipientFull
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.fixName
import pl.szczodrzynski.edziennik.singleOrNull
import pl.szczodrzynski.edziennik.utils.models.Date
import java.nio.charset.Charset
class LibrusMessagesGetMessage(
override val data: DataLibrus,
private val messageObject: MessageFull,
val onSuccess: () -> Unit
) : LibrusMessages(data) {
companion object {
const val TAG = "LibrusMessagesGetMessage"
}
init { data.profile?.also { profile ->
messagesGet(TAG, "GetMessage", parameters = mapOf(
"messageId" to messageObject.id,
"archive" to 0
)) { doc ->
val message = doc.select("response GetMessage data").first()
val body = Base64.decode(message.select("Message").text(), Base64.DEFAULT)
.toString(Charset.defaultCharset())
.replace("\n", "<br>")
.replace("<!\\[CDATA\\[", "")
.replace("]]>", "")
messageObject.apply {
this.body = body
clearAttachments()
message.select("attachments ArrayItem").forEach {
val attachmentId = it.select("id").text().toLong()
val attachmentName = it.select("filename").text()
addAttachment(attachmentId, attachmentName, -1)
}
}
val messageRecipientList = mutableListOf<MessageRecipientFull>()
when (messageObject.type) {
TYPE_RECEIVED -> {
val senderLoginId = message.select("senderId").text()
data.teacherList.singleOrNull { it.id == messageObject.senderId }?.loginId = senderLoginId
val readDateText = message.select("readDate").text()
val readDate = when (readDateText.isNotEmpty()) {
true -> Date.fromIso(readDateText)
else -> 0
}
val messageRecipientObject = MessageRecipientFull(
profileId,
-1,
-1,
readDate,
messageObject.id
)
messageRecipientObject.fullName = profile.accountNameLong ?: profile.studentNameLong
messageRecipientList.add(messageRecipientObject)
}
TYPE_SENT -> {
message.select("receivers ArrayItem").forEach { receiver ->
val receiverFirstName = receiver.select("firstName").text().fixName()
val receiverLastName = receiver.select("lastName").text().fixName()
val receiverLoginId = receiver.select("receiverId").text()
val teacher = data.teacherList.singleOrNull { it.name == receiverFirstName && it.surname == receiverLastName }
val receiverId = teacher?.id ?: -1
teacher?.loginId = receiverLoginId
val readDateText = message.select("readed").text()
val readDate = when (readDateText.isNotEmpty()) {
true -> Date.fromIso(readDateText)
else -> 0
}
val messageRecipientObject = MessageRecipientFull(
profileId,
receiverId,
-1,
readDate,
messageObject.id
)
messageRecipientObject.fullName = "$receiverFirstName $receiverLastName"
messageRecipientList.add(messageRecipientObject)
}
}
}
if (!messageObject.seen) {
data.messageMetadataList.add(Metadata(
messageObject.profileId,
Metadata.TYPE_MESSAGE,
messageObject.id,
true,
true,
messageObject.addedDate
))
}
messageObject.recipients = messageRecipientList
data.messageRecipientList.addAll(messageRecipientList)
data.messageList.add(messageObject)
EventBus.getDefault().postSticky(MessageGetEvent(messageObject))
onSuccess()
}
} ?: onSuccess()}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-25
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.messages
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusMessages
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
class LibrusMessagesTemplate(override val data: DataLibrus, val onSuccess: () -> Unit) : LibrusMessages(data) {
companion object {
const val TAG = "LibrusMessages"
}
init {
/* messagesGet(TAG, "") { doc ->
data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_, SYNC_ALWAYS)
onSuccess()
} */
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-22.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.synergia
import org.jsoup.Jsoup
import pl.szczodrzynski.edziennik.HOUR
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK
import pl.szczodrzynski.edziennik.api.v2.POST
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusSynergia
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.singleOrNull
import pl.szczodrzynski.edziennik.utils.models.Date
class LibrusSynergiaHomework(override val data: DataLibrus, val onSuccess: () -> Unit) : LibrusSynergia(data) {
companion object {
const val TAG = "LibrusSynergiaHomework"
}
init {
synergiaGet(TAG, "moje_zadania", method = POST, parameters = mapOf(
"dataOd" to
if (data.profile?.empty != false)
profile!!.getSemesterStart(1).stringY_m_d
else
Date.getToday().stringY_m_d,
"dataDo" to profile!!.getSemesterEnd(profile?.currentSemester ?: 2).stringY_m_d,
"przedmiot" to -1
)) { text ->
val doc = Jsoup.parse(text)
doc.select("table.myHomeworkTable > tbody").firstOrNull()?.also { homeworkTable ->
val homeworkElements = homeworkTable.children()
val graphElements = doc.select("table[border].center td[align=left] tbody").first().children()
homeworkElements.forEachIndexed { i, el ->
val elements = el.children()
val subjectName = elements[0].text().trim()
val subjectId = data.subjectList.singleOrNull { it.longName == subjectName }?.id
?: -1
val teacherName = elements[1].text().trim()
val teacherId = data.teacherList.singleOrNull { teacherName == it.fullName }?.id
?: -1
val topic = elements[2].text().trim()
val addedDate = Date.fromY_m_d(elements[4].text().trim()).inMillis
val eventDate = Date.fromY_m_d(elements[6].text().trim())
val id = "/podglad/([0-9]+)'".toRegex().find(
elements[9].select("input").attr("onclick")
)?.get(1)?.toLong() ?: return@forEachIndexed
val startTime = data.lessonList.singleOrNull {
it.weekDay == eventDate.weekDay && it.subjectId == subjectId
}?.startTime
val moreInfo = graphElements[2 * i + 1].select("td[title]")
.attr("title").trim()
val description = "Treść: (.*)".toRegex(RegexOption.DOT_MATCHES_ALL).find(moreInfo)
?.get(1)?.replace("<br.*/>".toRegex(), "\n")?.trim()
val notified = when (profile?.empty) {
true -> true
false -> Date.getToday() < eventDate
else -> false
}
val eventObject = Event(
profileId,
id,
eventDate,
startTime,
"$topic\n$description",
-1,
Event.TYPE_HOMEWORK,
false,
teacherId,
subjectId,
data.teamClass?.id ?: -1
)
data.eventList.add(eventObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_HOMEWORK,
id,
notified,
notified,
addedDate
))
}
}
// because this requires a synergia login (2 more requests) sync this every two hours or if explicit :D
data.setSyncNext(ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK, 2 * HOUR, DRAWER_ITEM_HOMEWORK)
onSuccess()
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-23
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.synergia
import org.jsoup.Jsoup
import pl.szczodrzynski.edziennik.MONTH
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_SYNERGIA_INFO
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusSynergia
class LibrusSynergiaInfo(override val data: DataLibrus, val onSuccess: () -> Unit) : LibrusSynergia(data) {
companion object {
const val TAG = "LibrusSynergiaInfo"
}
init {
synergiaGet(TAG, "informacja") { text ->
val doc = Jsoup.parse(text)
doc.select("table.form tbody").firstOrNull()?.children()?.also { info ->
val studentNumber = info[2].select("td").text().trim().toIntOrNull()
studentNumber?.also {
data.profile?.studentNumber = it
}
}
data.setSyncNext(ENDPOINT_LIBRUS_SYNERGIA_INFO, MONTH)
onSuccess()
}
}
}

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