From c111e43f1872dd930439b1ac201f6dfd5732c731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Mon, 11 Dec 2017 19:45:28 +0100 Subject: [PATCH] Add timetable to Wulkanowy (#38) * Add timetable entities, synchronization and UI * Add better code hub config * Update SDK tools * Change activity to ui * [API] fix lesson room, when division into groups * [API] Rewrite lesson parser * [API] Add support for new lesson type * [API] Fix for substitutions in the timetable --- .bettercodehub.yml | 5 + api/build.gradle | 5 +- .../github/wulkanowy/api/timetable/Day.java | 11 + .../wulkanowy/api/timetable/Lesson.java | 22 ++ .../wulkanowy/api/timetable/Timetable.java | 203 ++++++---- .../api/timetable/TimetableTest.java | 55 ++- .../api/timetable/PlanLekcji-full.html | 62 +++- app/build.gradle | 35 +- .../wulkanowy/dao/entities/DayTest.java | 19 + .../wulkanowy/dao/entities/LessonTest.java | 24 ++ .../wulkanowy/dao/entities/WeekTest.java | 21 ++ .../services/VulcanSynchronizationTest.java | 20 +- .../TimetableSynchronizationTest.java | 103 ++++++ app/src/main/AndroidManifest.xml | 10 +- .../lessonplan/LessonPlanFragment.java | 19 - .../wulkanowy/dao/entities/Account.java | 40 +- .../io/github/wulkanowy/dao/entities/Day.java | 214 +++++++++++ .../github/wulkanowy/dao/entities/Lesson.java | 349 ++++++++++++++++++ .../wulkanowy/dao/entities/Subject.java | 5 +- .../github/wulkanowy/dao/entities/Week.java | 148 ++++++++ .../io/github/wulkanowy/security/Safety.java | 2 +- .../github/wulkanowy/security/Scrambler.java | 8 +- .../services/VulcanSynchronization.java | 57 ++- .../jobs/{GradeJob.java => FullSyncJob.java} | 44 +-- .../wulkanowy/services/jobs/SubjectJob.java | 58 --- .../services/jobs/VulcanJobHelper.java | 9 +- .../services/jobs/VulcanService.java | 9 +- ...onHelper.java => NotificationBuilder.java} | 8 +- .../synchronisation/FirstAccountLogin.java | 2 +- .../TimetableSynchronization.java | 117 ++++++ .../{activity => ui}/WulkanowyApp.java | 11 +- .../{activity => ui}/login/LoginActivity.java | 2 +- .../{activity => ui}/login/LoginTask.java | 31 +- .../wulkanowy/ui/main/AbstractFragment.java | 189 ++++++++++ .../wulkanowy/ui/main/AsyncResponse.java | 14 + .../main}/DashboardActivity.java | 16 +- .../wulkanowy/ui/main/DatabaseQueryTask.java | 30 ++ .../github/wulkanowy/ui/main/RefreshTask.java | 52 +++ .../main}/attendance/AttendanceFragment.java | 2 +- .../main}/board/BoardFragment.java | 2 +- .../main}/grades/GradesAdapter.java | 46 ++- .../main}/grades/GradesDialogFragment.java | 56 +-- .../main}/grades/GradesFragment.java | 60 +-- .../main}/grades/SubjectWithGrades.java | 2 +- .../timetable/TimetableDialogFragment.java | 100 +++++ .../ui/main/timetable/TimetableFragment.java | 121 ++++++ .../main/timetable/TimetableFragmentTab.java | 161 ++++++++ .../main/timetable/TimetableHeaderItem.java | 86 +++++ .../main/timetable/TimetablePagerAdapter.java | 48 +++ .../ui/main/timetable/TimetableSubItem.java | 112 ++++++ .../splash/SplashActivity.java | 11 +- .../utilities/ConversionVulcanObject.java | 51 +++ .../wulkanowy/utilities/DateHelper.java | 42 --- .../wulkanowy/utilities/RootUtilities.java | 2 +- .../wulkanowy/utilities/TimeUtilities.java | 74 ++++ .../res/drawable/ic_generic_exclamation.xml | 8 + .../main/res/drawable/ic_timetable_swap.xml | 9 + .../main/res/layout/activity_dashboard.xml | 2 +- app/src/main/res/layout/activity_login.xml | 4 +- .../main/res/layout/fragment_attendance.xml | 5 +- app/src/main/res/layout/fragment_board.xml | 5 +- app/src/main/res/layout/fragment_grades.xml | 2 +- .../main/res/layout/fragment_lessonplan.xml | 35 -- .../main/res/layout/fragment_timetable.xml | 36 ++ .../res/layout/fragment_timetable_tab.xml | 32 ++ app/src/main/res/layout/grade_item.xml | 7 +- app/src/main/res/layout/grades_dialog.xml | 4 +- app/src/main/res/layout/subject_item.xml | 4 +- app/src/main/res/layout/timetable_dialog.xml | 200 ++++++++++ app/src/main/res/layout/timetable_header.xml | 48 +++ app/src/main/res/layout/timetable_subitem.xml | 95 +++++ app/src/main/res/values-pl/strings.xml | 17 +- app/src/main/res/values/strings.xml | 18 +- .../grades/GradesDialogFragmentTest.java | 4 +- .../main}/grades/SubjectWithGradesTest.java | 2 +- .../utilities/ConversionVulcanObjectTest.java | 34 ++ ...HelperTest.java => TimeUtilitiesTest.java} | 20 +- 77 files changed, 3125 insertions(+), 471 deletions(-) create mode 100644 .bettercodehub.yml create mode 100644 app/src/androidTest/java/io/github/wulkanowy/dao/entities/DayTest.java create mode 100644 app/src/androidTest/java/io/github/wulkanowy/dao/entities/LessonTest.java create mode 100644 app/src/androidTest/java/io/github/wulkanowy/dao/entities/WeekTest.java create mode 100644 app/src/androidTest/java/io/github/wulkanowy/services/synchronization/TimetableSynchronizationTest.java delete mode 100644 app/src/main/java/io/github/wulkanowy/activity/dashboard/lessonplan/LessonPlanFragment.java create mode 100644 app/src/main/java/io/github/wulkanowy/dao/entities/Day.java create mode 100644 app/src/main/java/io/github/wulkanowy/dao/entities/Lesson.java create mode 100644 app/src/main/java/io/github/wulkanowy/dao/entities/Week.java rename app/src/main/java/io/github/wulkanowy/services/jobs/{GradeJob.java => FullSyncJob.java} (61%) delete mode 100644 app/src/main/java/io/github/wulkanowy/services/jobs/SubjectJob.java rename app/src/main/java/io/github/wulkanowy/services/notifications/{NotificationHelper.java => NotificationBuilder.java} (95%) create mode 100644 app/src/main/java/io/github/wulkanowy/services/synchronisation/TimetableSynchronization.java rename app/src/main/java/io/github/wulkanowy/{activity => ui}/WulkanowyApp.java (80%) rename app/src/main/java/io/github/wulkanowy/{activity => ui}/login/LoginActivity.java (99%) rename app/src/main/java/io/github/wulkanowy/{activity => ui}/login/LoginTask.java (89%) create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/AbstractFragment.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/AsyncResponse.java rename app/src/main/java/io/github/wulkanowy/{activity/dashboard => ui/main}/DashboardActivity.java (87%) create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/DatabaseQueryTask.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/RefreshTask.java rename app/src/main/java/io/github/wulkanowy/{activity/dashboard => ui/main}/attendance/AttendanceFragment.java (90%) rename app/src/main/java/io/github/wulkanowy/{activity/dashboard => ui/main}/board/BoardFragment.java (90%) rename app/src/main/java/io/github/wulkanowy/{activity/dashboard => ui/main}/grades/GradesAdapter.java (76%) rename app/src/main/java/io/github/wulkanowy/{activity/dashboard => ui/main}/grades/GradesDialogFragment.java (90%) rename app/src/main/java/io/github/wulkanowy/{activity/dashboard => ui/main}/grades/GradesFragment.java (96%) rename app/src/main/java/io/github/wulkanowy/{activity/dashboard => ui/main}/grades/SubjectWithGrades.java (84%) create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialogFragment.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragmentTab.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableHeaderItem.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePagerAdapter.java create mode 100644 app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableSubItem.java rename app/src/main/java/io/github/wulkanowy/{activity => ui}/splash/SplashActivity.java (79%) delete mode 100644 app/src/main/java/io/github/wulkanowy/utilities/DateHelper.java create mode 100644 app/src/main/java/io/github/wulkanowy/utilities/TimeUtilities.java create mode 100644 app/src/main/res/drawable/ic_generic_exclamation.xml create mode 100644 app/src/main/res/drawable/ic_timetable_swap.xml delete mode 100644 app/src/main/res/layout/fragment_lessonplan.xml create mode 100644 app/src/main/res/layout/fragment_timetable.xml create mode 100644 app/src/main/res/layout/fragment_timetable_tab.xml create mode 100644 app/src/main/res/layout/timetable_dialog.xml create mode 100644 app/src/main/res/layout/timetable_header.xml create mode 100644 app/src/main/res/layout/timetable_subitem.xml rename app/src/test/java/io/github/wulkanowy/{activity/dashboard => ui/main}/grades/GradesDialogFragmentTest.java (79%) rename app/src/test/java/io/github/wulkanowy/{activity/dashboard => ui/main}/grades/SubjectWithGradesTest.java (95%) rename app/src/test/java/io/github/wulkanowy/utilities/{DateHelperTest.java => TimeUtilitiesTest.java} (59%) diff --git a/.bettercodehub.yml b/.bettercodehub.yml new file mode 100644 index 00000000..96055d7d --- /dev/null +++ b/.bettercodehub.yml @@ -0,0 +1,5 @@ +exclude: +- /app/src/main/java/io/github/wulkanowy/dao/entities/.* +component_depth: 1 +languages: +- java diff --git a/api/build.gradle b/api/build.gradle index 70b32dd2..e376b94a 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -4,8 +4,9 @@ apply plugin: 'jacoco' apply plugin: 'com.jfrog.bintray' apply plugin: 'com.github.dcendents.android-maven' -sourceCompatibility = "1.7" -targetCompatibility = "1.7" +compileJava.options.encoding = "UTF-8" +compileTestJava.options.encoding = "UTF-8" + ext { PUBLISH_GROUP_ID = GROUP_ID diff --git a/api/src/main/java/io/github/wulkanowy/api/timetable/Day.java b/api/src/main/java/io/github/wulkanowy/api/timetable/Day.java index 9b44647f..1192de88 100644 --- a/api/src/main/java/io/github/wulkanowy/api/timetable/Day.java +++ b/api/src/main/java/io/github/wulkanowy/api/timetable/Day.java @@ -9,6 +9,8 @@ public class Day { private String date = ""; + private String dayName = ""; + private boolean isFreeDay = false; private String freeDayName = ""; @@ -35,6 +37,15 @@ public class Day { return this; } + public String getDayName() { + return dayName; + } + + public Day setDayName(String dayName) { + this.dayName = dayName; + return this; + } + public boolean isFreeDay() { return isFreeDay; } diff --git a/api/src/main/java/io/github/wulkanowy/api/timetable/Lesson.java b/api/src/main/java/io/github/wulkanowy/api/timetable/Lesson.java index f15e2928..4b634438 100644 --- a/api/src/main/java/io/github/wulkanowy/api/timetable/Lesson.java +++ b/api/src/main/java/io/github/wulkanowy/api/timetable/Lesson.java @@ -10,6 +10,8 @@ public class Lesson { public static final String CLASS_NEW_MOVED_IN_OR_CHANGED = "x-treelabel-zas"; + private String number = ""; + private String subject = ""; private String teacher = ""; @@ -24,6 +26,8 @@ public class Lesson { private String endTime = ""; + private String date = ""; + private boolean isEmpty = false; private boolean isDivisionIntoGroups = false; @@ -36,6 +40,15 @@ public class Lesson { private boolean isNewMovedInOrChanged = false; + public String getNumber() { + return number; + } + + public Lesson setNumber(String number) { + this.number = number; + return this; + } + public String getSubject() { return subject; } @@ -99,6 +112,15 @@ public class Lesson { return this; } + public String getDate() { + return date; + } + + public Lesson setDate(String date) { + this.date = date; + return this; + } + public boolean isEmpty() { return isEmpty; } diff --git a/api/src/main/java/io/github/wulkanowy/api/timetable/Timetable.java b/api/src/main/java/io/github/wulkanowy/api/timetable/Timetable.java index 87750a67..efff1780 100644 --- a/api/src/main/java/io/github/wulkanowy/api/timetable/Timetable.java +++ b/api/src/main/java/io/github/wulkanowy/api/timetable/Timetable.java @@ -5,8 +5,12 @@ import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; import java.util.List; +import java.util.Locale; import io.github.wulkanowy.api.StudentAndParent; @@ -20,125 +24,178 @@ public class Timetable { this.snp = snp; } - public Week getWeekTable() throws IOException { + public Week getWeekTable() throws IOException, ParseException { return getWeekTable(""); } - public Week getWeekTable(String tick) throws IOException { + public Week getWeekTable(final String tick) throws IOException, ParseException { Element table = snp.getSnPPageDocument(TIMETABLE_PAGE_URL + tick) .select(".mainContainer .presentData").first(); - Elements tableHeaderCells = table.select("thead th"); + List days = getDays(table.select("thead th")); + + setLessonToDays(table, days); + + return new Week() + .setStartDayDate(days.get(0).getDate()) + .setDays(days); + } + + private List getDays(Elements tableHeaderCells) throws ParseException { List days = new ArrayList<>(); for (int i = 2; i < 7; i++) { String[] dayHeaderCell = tableHeaderCells.get(i).html().split("
"); - boolean isFreeDay = tableHeaderCells.get(i).hasClass("free-day"); + + SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy", Locale.ROOT); + Date d = sdf.parse(dayHeaderCell[1].trim()); + sdf.applyPattern("yyyy-MM-dd"); Day day = new Day(); - day.setDate(dayHeaderCell[1]); + day.setDayName(dayHeaderCell[0]); + day.setDate(sdf.format(d)); - if (isFreeDay) { - day.setFreeDay(isFreeDay); + if (tableHeaderCells.get(i).hasClass("free-day")) { + day.setFreeDay(true); day.setFreeDayName(dayHeaderCell[2]); } days.add(day); } - Elements hoursInDays = table.select("tbody tr"); + return days; + } - // fill days in week with lessons - for (Element row : hoursInDays) { + private void setLessonToDays(Element table, List days) { + for (Element row : table.select("tbody tr")) { Elements hours = row.select("td"); // fill hours in day for (int i = 2; i < hours.size(); i++) { Lesson lesson = new Lesson(); - Elements e = hours.get(i).select("div"); - switch (e.size()) { - case 1: - lesson = getLessonFromElement(e.first()); - break; - case 3: - lesson = getLessonFromElement(e.get(1)); - break; - default: - lesson.setEmpty(true); - break; - } - String[] startEndEnd = hours.get(1).text().split(" "); lesson.setStartTime(startEndEnd[0]); lesson.setEndTime(startEndEnd[1]); + lesson.setDate(days.get(i - 2).getDate()); + lesson.setNumber(hours.get(0).text()); + + addLessonDetails(lesson, hours.get(i).select("div")); days.get(i - 2).setLesson(lesson); } } - - Element startDayCellHeader = tableHeaderCells.get(2); - String[] dayDescription = startDayCellHeader.html().split("
"); - - return new Week() - .setStartDayDate(dayDescription[1]) - .setDays(days); } - private Lesson getLessonFromElement(Element e) { - Lesson lesson = new Lesson(); + private void addLessonDetails(Lesson lesson, Elements e) { + switch (e.size()) { + case 1: + addLessonInfoFromElement(lesson, e.first()); + break; + case 2: + addLessonInfoFromElement(lesson, e.last()); + break; + case 3: + addLessonInfoFromElement(lesson, e.get(1)); + break; + default: + lesson.setEmpty(true); + break; + } + } + + private void addLessonInfoFromElement(Lesson lesson, Element e) { Elements spans = e.select("span"); - lesson.setSubject(spans.get(0).text()); - lesson.setTeacher(spans.get(1).text()); - lesson.setRoom(spans.get(2).text()); - - // okienko dla uczniów - if (5 == spans.size()) { - lesson.setTeacher(spans.get(2).text()); - lesson.setRoom(spans.get(3).text()); - } - - addGroupDivisionInfo(lesson, spans); - adTypeInfo(lesson, spans); - addDescriptionInfo(lesson, spans); - - return lesson; + addTypeInfo(lesson, spans); + addNormalLessonInfo(lesson, spans); + addChangesInfo(lesson, spans); + addGroupLessonInfo(lesson, spans); } - private void addGroupDivisionInfo(Lesson lesson, Elements e) { - if ((4 == e.size() && (e.first().attr("class").equals("")) || - (5 == e.size() && e.first().hasClass(Lesson.CLASS_NEW_MOVED_IN_OR_CHANGED)))) { - lesson.setDivisionIntoGroups(true); - String[] subjectNameArray = lesson.getSubject().split(" "); - String groupName = subjectNameArray[subjectNameArray.length - 1]; - lesson.setSubject(lesson.getSubject().replace(" " + groupName, "")); - lesson.setGroupName(StringUtils.substringBetween(groupName, "[", "]")); - lesson.setTeacher(e.get(2).text()); - lesson.setRoom(e.get(3).text()); - } - } - - private void adTypeInfo(Lesson lesson, Elements e) { - if (e.first().hasClass(Lesson.CLASS_MOVED_OR_CANCELED)) { - lesson.setMovedOrCanceled(true); - } else if (e.first().hasClass(Lesson.CLASS_NEW_MOVED_IN_OR_CHANGED)) { - lesson.setNewMovedInOrChanged(true); - } else if (e.first().hasClass(Lesson.CLASS_PLANNING)) { + private void addTypeInfo(Lesson lesson, Elements spans) { + if (spans.first().hasClass(Lesson.CLASS_PLANNING)) { lesson.setPlanning(true); } - if (e.last().hasClass(Lesson.CLASS_REALIZED) - || e.first().attr("class").equals("")) { + if (spans.first().hasClass(Lesson.CLASS_MOVED_OR_CANCELED)) { + lesson.setMovedOrCanceled(true); + } + + if (spans.first().hasClass(Lesson.CLASS_NEW_MOVED_IN_OR_CHANGED)) { + lesson.setNewMovedInOrChanged(true); + } + + if (spans.last().hasClass(Lesson.CLASS_REALIZED) || "".equals(spans.first().attr("class"))) { lesson.setRealized(true); } } - private void addDescriptionInfo(Lesson lesson, Elements e) { - if ((4 == e.size() || 5 == e.size()) - && (e.first().hasClass(Lesson.CLASS_MOVED_OR_CANCELED) - || e.first().hasClass(Lesson.CLASS_NEW_MOVED_IN_OR_CHANGED))) { - lesson.setDescription(StringUtils.substringBetween(e.last().text(), "(", ")")); + private void addNormalLessonInfo(Lesson lesson, Elements spans) { + if (3 == spans.size()) { + lesson.setSubject(spans.get(0).text()); + lesson.setTeacher(spans.get(1).text()); + lesson.setRoom(spans.get(2).text()); } } + + private void addChangesInfo(Lesson lesson, Elements spans) { + if (!spans.last().hasClass(Lesson.CLASS_REALIZED)) { + return; + } + + if (7 == spans.size()) { + lesson.setSubject(spans.get(3).text()); + lesson.setTeacher(spans.get(4).text()); + lesson.setRoom(spans.get(5).text()); + lesson.setMovedOrCanceled(false); + lesson.setNewMovedInOrChanged(true); + lesson.setDescription(StringUtils.substringBetween(spans.last().text(), "(", ")") + + " (poprzednio: " + spans.get(0).text() + ")"); + } else if (9 == spans.size()) { + String[] subjectAndGroupInfo = getLessonAndGroupInfoFromSpan(spans.get(4)); + lesson.setSubject(subjectAndGroupInfo[0]); + lesson.setGroupName(subjectAndGroupInfo[1]); + lesson.setTeacher(spans.get(6).text()); + lesson.setRoom(spans.get(7).text()); + lesson.setMovedOrCanceled(false); + lesson.setNewMovedInOrChanged(true); + lesson.setDivisionIntoGroups(true); + lesson.setDescription(StringUtils.substringBetween(spans.last().text(), "(", ")") + + " (poprzednio: " + getLessonAndGroupInfoFromSpan(spans.get(0))[0] + ")"); + } else if (4 <= spans.size()) { + lesson.setSubject(spans.get(0).text()); + lesson.setTeacher(spans.get(1).text()); + lesson.setRoom(spans.get(2).text()); + lesson.setDescription(StringUtils.substringBetween(spans.last().text(), "(", ")")); + } + } + + private void addGroupLessonInfo(Lesson lesson, Elements spans) { + if (4 == spans.size() && !spans.last().hasClass(Lesson.CLASS_REALIZED)) { + lesson.setRoom(spans.last().text()); + } + + if ((4 == spans.size() && !spans.last().hasClass(Lesson.CLASS_REALIZED) || 5 == spans.size())) { + String[] subjectAndGroupInfo = getLessonAndGroupInfoFromSpan(spans.get(0)); + lesson.setSubject(subjectAndGroupInfo[0]); + lesson.setGroupName(subjectAndGroupInfo[1]); + lesson.setTeacher(spans.get(2).text()); + lesson.setDivisionIntoGroups(true); + } + + if (5 == spans.size()) { + lesson.setRoom(spans.get(3).text()); + } + } + + private String[] getLessonAndGroupInfoFromSpan(Element span) { + String[] subjectNameArray = span.text().split(" "); + String groupName = subjectNameArray[subjectNameArray.length - 1]; + + return new String[]{ + span.text().replace(" " + groupName, ""), + StringUtils.substringBetween(groupName, "[", "]") + }; + } } diff --git a/api/src/test/java/io/github/wulkanowy/api/timetable/TimetableTest.java b/api/src/test/java/io/github/wulkanowy/api/timetable/TimetableTest.java index 5eb9487c..2ad08b2b 100644 --- a/api/src/test/java/io/github/wulkanowy/api/timetable/TimetableTest.java +++ b/api/src/test/java/io/github/wulkanowy/api/timetable/TimetableTest.java @@ -32,20 +32,29 @@ public class TimetableTest extends StudentAndParentTestCase { @Test public void getStartDayDateTest() throws Exception { - Assert.assertEquals("19.06.2017", std.getWeekTable().getStartDayDate()); - Assert.assertEquals("19.06.2017", full.getWeekTable().getStartDayDate()); - Assert.assertEquals("31.07.2017", holidays.getWeekTable().getStartDayDate()); + Assert.assertEquals("2017-06-19", std.getWeekTable().getStartDayDate()); + Assert.assertEquals("2017-06-19", full.getWeekTable().getStartDayDate()); + Assert.assertEquals("2017-07-31", holidays.getWeekTable().getStartDayDate()); } // Day + @Test + public void getDayNameTest() throws Exception { + Assert.assertEquals("poniedziałek", std.getWeekTable().getDay(0).getDayName()); + Assert.assertEquals("piątek", std.getWeekTable().getDay(4).getDayName()); + Assert.assertEquals("wtorek", full.getWeekTable().getDay(1).getDayName()); + Assert.assertEquals("czwartek", full.getWeekTable().getDay(3).getDayName()); + Assert.assertEquals("środa", holidays.getWeekTable().getDay(2).getDayName()); + } + @Test public void getDayDateTest() throws Exception { - Assert.assertEquals("19.06.2017", std.getWeekTable().getDay(0).getDate()); - Assert.assertEquals("23.06.2017", std.getWeekTable().getDay(4).getDate()); - Assert.assertEquals("20.06.2017", full.getWeekTable().getDay(1).getDate()); - Assert.assertEquals("22.06.2017", full.getWeekTable().getDay(3).getDate()); - Assert.assertEquals("02.08.2017", holidays.getWeekTable().getDay(2).getDate()); + Assert.assertEquals("2017-06-19", std.getWeekTable().getDay(0).getDate()); + Assert.assertEquals("2017-06-23", std.getWeekTable().getDay(4).getDate()); + Assert.assertEquals("2017-06-20", full.getWeekTable().getDay(1).getDate()); + Assert.assertEquals("2017-06-22", full.getWeekTable().getDay(3).getDate()); + Assert.assertEquals("2017-08-02", holidays.getWeekTable().getDay(2).getDate()); } @Test @@ -72,12 +81,26 @@ public class TimetableTest extends StudentAndParentTestCase { // Lesson + @Test + public void getLessonNumberTest() throws Exception { + Assert.assertEquals("2", std.getWeekTable().getDay(0).getLesson(1).getNumber()); + Assert.assertEquals("5", std.getWeekTable().getDay(2).getLesson(4).getNumber()); + Assert.assertEquals("0", full.getWeekTable().getDay(0).getLesson(0).getNumber()); + Assert.assertEquals("13", full.getWeekTable().getDay(4).getLesson(13).getNumber()); + Assert.assertEquals("3", holidays.getWeekTable().getDay(3).getLesson(3).getNumber()); + } + @Test public void getLessonSubjectTest() throws Exception { Assert.assertEquals("Historia", std.getWeekTable().getDay(0).getLesson(1).getSubject()); Assert.assertEquals("Zajęcia techniczne", std.getWeekTable().getDay(2).getLesson(4).getSubject()); + Assert.assertEquals("Wychowanie fizyczne", std.getWeekTable().getDay(1).getLesson(1).getSubject()); Assert.assertEquals("Język angielski", full.getWeekTable().getDay(0).getLesson(1).getSubject()); + Assert.assertEquals("Wychowanie do życia w rodzinie", full.getWeekTable().getDay(2).getLesson(0).getSubject()); + Assert.assertEquals("Wychowanie fizyczne", full.getWeekTable().getDay(3).getLesson(1).getSubject()); Assert.assertEquals("Uroczyste zakończenie roku szkolnego", full.getWeekTable().getDay(4).getLesson(0).getSubject()); + Assert.assertEquals("Fizyka", full.getWeekTable().getDay(0).getLesson(0).getSubject()); + Assert.assertEquals("Metodologia programowania", full.getWeekTable().getDay(1).getLesson(0).getSubject()); Assert.assertEquals("", holidays.getWeekTable().getDay(3).getLesson(3).getSubject()); } @@ -87,6 +110,8 @@ public class TimetableTest extends StudentAndParentTestCase { Assert.assertEquals("Chlebowski Stanisław", std.getWeekTable().getDay(2).getLesson(4).getTeacher()); Assert.assertEquals("Kobczyk Iwona", full.getWeekTable().getDay(0).getLesson(1).getTeacher()); Assert.assertEquals("Bączek Grzegorz", full.getWeekTable().getDay(0).getLesson(7).getTeacher()); + Assert.assertEquals("Nowak Jadwiga", full.getWeekTable().getDay(2).getLesson(0).getTeacher()); + Assert.assertEquals("Nowicka Irena", full.getWeekTable().getDay(3).getLesson(1).getTeacher()); Assert.assertEquals("Baran Małgorzata", full.getWeekTable().getDay(4).getLesson(0).getTeacher()); Assert.assertEquals("", holidays.getWeekTable().getDay(3).getLesson(3).getTeacher()); } @@ -95,7 +120,11 @@ public class TimetableTest extends StudentAndParentTestCase { public void getLessonRoomTest() throws Exception { Assert.assertEquals("", std.getWeekTable().getDay(3).getLesson(3).getRoom()); Assert.assertEquals("33", full.getWeekTable().getDay(0).getLesson(7).getRoom()); + Assert.assertEquals("19", full.getWeekTable().getDay(0).getLesson(0).getRoom()); + Assert.assertEquals("32", full.getWeekTable().getDay(1).getLesson(0).getRoom()); Assert.assertEquals("32", full.getWeekTable().getDay(1).getLesson(8).getRoom()); + Assert.assertEquals("32", full.getWeekTable().getDay(2).getLesson(8).getRoom()); + Assert.assertEquals("G4", full.getWeekTable().getDay(3).getLesson(1).getRoom()); Assert.assertEquals("37", full.getWeekTable().getDay(4).getLesson(0).getRoom()); Assert.assertEquals("", holidays.getWeekTable().getDay(3).getLesson(3).getRoom()); } @@ -103,9 +132,12 @@ public class TimetableTest extends StudentAndParentTestCase { @Test public void getLessonDescriptionTest() throws Exception { Assert.assertEquals("", std.getWeekTable().getDay(3).getLesson(3).getDescription()); + Assert.assertEquals("przeniesiona z lekcji 7, 01.12.2017", full.getWeekTable().getDay(1).getLesson(1).getDescription()); Assert.assertEquals("okienko dla uczniów", full.getWeekTable().getDay(0).getLesson(7).getDescription()); Assert.assertEquals("przeniesiona z lekcji 7, 20.06.2017", full.getWeekTable().getDay(1).getLesson(2).getDescription()); Assert.assertEquals("przeniesiona z lekcji 4, 20.06.2017", full.getWeekTable().getDay(1).getLesson(3).getDescription()); + Assert.assertEquals("zastępstwo (poprzednio: Religia)", full.getWeekTable().getDay(2).getLesson(0).getDescription()); + Assert.assertEquals("zastępstwo (poprzednio: Wychowanie fizyczne)", full.getWeekTable().getDay(3).getLesson(1).getDescription()); Assert.assertEquals("", full.getWeekTable().getDay(4).getLesson(0).getDescription()); Assert.assertEquals("", holidays.getWeekTable().getDay(3).getLesson(3).getDescription()); } @@ -115,7 +147,9 @@ public class TimetableTest extends StudentAndParentTestCase { Assert.assertEquals("CH", std.getWeekTable().getDay(0).getLesson(2).getGroupName()); Assert.assertEquals("JNPW", std.getWeekTable().getDay(4).getLesson(0).getGroupName()); Assert.assertEquals("", full.getWeekTable().getDay(0).getLesson(7).getGroupName()); + Assert.assertEquals("zaw2", full.getWeekTable().getDay(1).getLesson(0).getGroupName()); Assert.assertEquals("wf2", full.getWeekTable().getDay(1).getLesson(3).getGroupName()); + Assert.assertEquals("zaw1", full.getWeekTable().getDay(3).getLesson(1).getGroupName()); Assert.assertEquals("", holidays.getWeekTable().getDay(3).getLesson(3).getGroupName()); } @@ -141,6 +175,7 @@ public class TimetableTest extends StudentAndParentTestCase { public void getLessonIsEmptyTest() throws Exception { Assert.assertFalse(std.getWeekTable().getDay(1).getLesson(4).isEmpty()); Assert.assertTrue(std.getWeekTable().getDay(3).getLesson(7).isEmpty()); + Assert.assertFalse(full.getWeekTable().getDay(1).getLesson(1).isEmpty()); Assert.assertFalse(full.getWeekTable().getDay(1).getLesson(2).isEmpty()); Assert.assertFalse(full.getWeekTable().getDay(0).getLesson(7).isEmpty()); Assert.assertTrue(full.getWeekTable().getDay(2).getLesson(9).isEmpty()); @@ -154,6 +189,7 @@ public class TimetableTest extends StudentAndParentTestCase { Assert.assertTrue(std.getWeekTable().getDay(4).getLesson(0).isDivisionIntoGroups()); Assert.assertFalse(full.getWeekTable().getDay(0).getLesson(7).isDivisionIntoGroups()); Assert.assertTrue(full.getWeekTable().getDay(1).getLesson(3).isDivisionIntoGroups()); + Assert.assertTrue(full.getWeekTable().getDay(3).getLesson(1).isDivisionIntoGroups()); Assert.assertFalse(holidays.getWeekTable().getDay(3).getLesson(3).isDivisionIntoGroups()); } @@ -161,7 +197,7 @@ public class TimetableTest extends StudentAndParentTestCase { public void getLessonIsPlanningTest() throws Exception { Assert.assertFalse(std.getWeekTable().getDay(4).getLesson(4).isPlanning()); Assert.assertFalse(full.getWeekTable().getDay(0).getLesson(1).isPlanning()); - Assert.assertFalse(full.getWeekTable().getDay(1).getLesson(3).isPlanning()); + Assert.assertTrue(full.getWeekTable().getDay(1).getLesson(3).isPlanning()); Assert.assertTrue(full.getWeekTable().getDay(4).getLesson(0).isPlanning()); Assert.assertFalse(holidays.getWeekTable().getDay(3).getLesson(3).isPlanning()); } @@ -190,6 +226,7 @@ public class TimetableTest extends StudentAndParentTestCase { Assert.assertFalse(full.getWeekTable().getDay(0).getLesson(1).isNewMovedInOrChanged()); Assert.assertTrue(full.getWeekTable().getDay(1).getLesson(2).isNewMovedInOrChanged()); Assert.assertTrue(full.getWeekTable().getDay(1).getLesson(3).isNewMovedInOrChanged()); + Assert.assertTrue(full.getWeekTable().getDay(3).getLesson(1).isNewMovedInOrChanged()); Assert.assertFalse(holidays.getWeekTable().getDay(3).getLesson(3).isNewMovedInOrChanged()); } } diff --git a/api/src/test/resources/io/github/wulkanowy/api/timetable/PlanLekcji-full.html b/api/src/test/resources/io/github/wulkanowy/api/timetable/PlanLekcji-full.html index 24c88963..f265067d 100644 --- a/api/src/test/resources/io/github/wulkanowy/api/timetable/PlanLekcji-full.html +++ b/api/src/test/resources/io/github/wulkanowy/api/timetable/PlanLekcji-full.html @@ -24,9 +24,34 @@ 0 07:10 07:55 - - - + +
+ Fizyka [zaw2] + + Bączek Grzegorz + 19 + (uczniowie zwolnieni do domu) +
+ + +
+ Metodologia programowania [zaw2] + + + 32 +
+ + +
+ Religia + Cyranka Krystian + 3 + Wychowanie do życia w rodzinie + Nowak Jadwiga + 3 + (zastępstwo) +
+
@@ -47,7 +72,22 @@
- + +
+ Metodologia programowania [zaw2] + + Baran Małgorzata + 36 + (zmiana organizacji zajęć) +
+
+ Wychowanie fizyczne [zaw2] + + + G3 + (przeniesiona z lekcji 7, 01.12.2017) +
+
Użytkowanie urządzeń peryferyjnych komputera [zaw2] @@ -56,7 +96,19 @@
- + +
+ Wychowanie fizyczne [zaw1] + + Jarocki Krzysztof + G4 + Wychowanie fizyczne [zaw1] + + Nowicka Irena + G4 + (zastępstwo) +
+ diff --git a/app/build.gradle b/app/build.gradle index 9e0cbf11..72f6ab7c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,7 +5,7 @@ apply from: '../android-sonarqube.gradle' android { compileSdkVersion 27 - buildToolsVersion "27.0.1" + buildToolsVersion "27.0.2" defaultConfig { applicationId "io.github.wulkanowy" testApplicationId "io.github.tests.wulkanowy" @@ -31,7 +31,7 @@ android { unitTests.all { testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" - outputs.upToDateWhen {false} + outputs.upToDateWhen { false } showStandardStreams = true } } @@ -39,25 +39,28 @@ android { } greendao { - schemaVersion 14 + schemaVersion 19 generateTests = true } dependencies { - implementation project(":api") - implementation 'com.android.support:appcompat-v7:27.0.1' - implementation 'com.android.support.constraint:constraint-layout:1.0.2' - implementation 'com.android.support:design:27.0.1' - implementation 'com.android.support:support-vector-drawable:27.0.1' - implementation 'com.android.support:support-v4:27.0.1' - implementation 'com.android.support:recyclerview-v7:27.0.1' - implementation 'com.android.support:cardview-v7:27.0.1' - implementation 'com.android.support:customtabs:27.0.1' - implementation 'com.firebase:firebase-jobdispatcher:0.8.4' + implementation project(':api') + implementation 'com.android.support:appcompat-v7:27.0.2' + implementation 'com.android.support:design:27.0.2' + implementation 'com.android.support:support-v4:27.0.2' + implementation 'com.android.support:recyclerview-v7:27.0.2' + implementation 'com.android.support:cardview-v7:27.0.2' + implementation 'com.android.support:customtabs:27.0.2' + implementation 'com.firebase:firebase-jobdispatcher:0.8.5' implementation 'com.thoughtbot:expandablerecyclerview:1.3' implementation 'org.apache.commons:commons-lang3:3.6' + implementation 'eu.davidea:flexible-adapter:5.0.0-rc3' implementation 'org.apache.commons:commons-collections4:4.1' implementation 'org.greenrobot:greendao:3.2.2' + implementation 'com.jakewharton:butterknife:8.8.1' + implementation 'joda-time:joda-time:2.9.9' + + annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' debugImplementation 'com.amitshekhar.android:debug-db:1.0.1' debugImplementation 'net.zetetic:android-database-sqlcipher:3.5.7@aar' @@ -65,12 +68,6 @@ dependencies { testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.11.0' - androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.1', { - exclude group: 'com.android.support', module: 'support-annotations' - }) - androidTestImplementation 'com.android.support:support-annotations:27.0.1' androidTestImplementation 'com.android.support.test:runner:1.0.1' - androidTestImplementation 'com.android.support.test:rules:1.0.1' - androidTestImplementation 'org.hamcrest:hamcrest-library:1.3' androidTestImplementation 'org.mockito:mockito-android:2.11.0' } diff --git a/app/src/androidTest/java/io/github/wulkanowy/dao/entities/DayTest.java b/app/src/androidTest/java/io/github/wulkanowy/dao/entities/DayTest.java new file mode 100644 index 00000000..4c978c57 --- /dev/null +++ b/app/src/androidTest/java/io/github/wulkanowy/dao/entities/DayTest.java @@ -0,0 +1,19 @@ +package io.github.wulkanowy.dao.entities; + +import org.greenrobot.greendao.test.AbstractDaoTestLongPk; + +public class DayTest extends AbstractDaoTestLongPk { + + public DayTest() { + super(DayDao.class); + } + + @Override + protected Day createEntity(Long key) { + Day entity = new Day(); + entity.setId(key); + entity.setIsFreeDay(false); + return entity; + } + +} diff --git a/app/src/androidTest/java/io/github/wulkanowy/dao/entities/LessonTest.java b/app/src/androidTest/java/io/github/wulkanowy/dao/entities/LessonTest.java new file mode 100644 index 00000000..96d50e23 --- /dev/null +++ b/app/src/androidTest/java/io/github/wulkanowy/dao/entities/LessonTest.java @@ -0,0 +1,24 @@ +package io.github.wulkanowy.dao.entities; + +import org.greenrobot.greendao.test.AbstractDaoTestLongPk; + +public class LessonTest extends AbstractDaoTestLongPk { + + public LessonTest() { + super(LessonDao.class); + } + + @Override + protected Lesson createEntity(Long key) { + Lesson entity = new Lesson(); + entity.setId(key); + entity.setIsEmpty(false); + entity.setIsDivisionIntoGroups(false); + entity.setIsPlanning(false); + entity.setIsRealized(false); + entity.setIsMovedOrCanceled(false); + entity.setIsNewMovedInOrChanged(false); + return entity; + } + +} diff --git a/app/src/androidTest/java/io/github/wulkanowy/dao/entities/WeekTest.java b/app/src/androidTest/java/io/github/wulkanowy/dao/entities/WeekTest.java new file mode 100644 index 00000000..1302989c --- /dev/null +++ b/app/src/androidTest/java/io/github/wulkanowy/dao/entities/WeekTest.java @@ -0,0 +1,21 @@ +package io.github.wulkanowy.dao.entities; + +import org.greenrobot.greendao.test.AbstractDaoTestLongPk; + +import io.github.wulkanowy.dao.entities.Week; +import io.github.wulkanowy.dao.entities.WeekDao; + +public class WeekTest extends AbstractDaoTestLongPk { + + public WeekTest() { + super(WeekDao.class); + } + + @Override + protected Week createEntity(Long key) { + Week entity = new Week(); + entity.setId(key); + return entity; + } + +} diff --git a/app/src/androidTest/java/io/github/wulkanowy/services/VulcanSynchronizationTest.java b/app/src/androidTest/java/io/github/wulkanowy/services/VulcanSynchronizationTest.java index 69ecbd77..afabf30a 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/services/VulcanSynchronizationTest.java +++ b/app/src/androidTest/java/io/github/wulkanowy/services/VulcanSynchronizationTest.java @@ -6,18 +6,26 @@ import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.IOException; + @RunWith(AndroidJUnit4.class) public class VulcanSynchronizationTest { - @Test - public void syncNoLoginSessionSubjectTest() { + @Test(expected = IOException.class) + public void syncNoLoginSessionSubjectTest() throws IOException { VulcanSynchronization vulcanSynchronization = new VulcanSynchronization(new LoginSession()); - Assert.assertFalse(vulcanSynchronization.syncSubjectsAndGrades()); + vulcanSynchronization.syncSubjectsAndGrades(); } - @Test - public void syncNoLoginSessionGradeTest() { + @Test(expected = IOException.class) + public void syncNoLoginSessionGradeTest() throws IOException { VulcanSynchronization vulcanSynchronization = new VulcanSynchronization(new LoginSession()); - Assert.assertFalse(vulcanSynchronization.syncGrades()); + vulcanSynchronization.syncGrades(); + } + + @Test(expected = IOException.class) + public void syncNoLoginSessionTimetableTest() throws IOException { + VulcanSynchronization vulcanSynchronization = new VulcanSynchronization(new LoginSession()); + vulcanSynchronization.syncTimetable(); } } diff --git a/app/src/androidTest/java/io/github/wulkanowy/services/synchronization/TimetableSynchronizationTest.java b/app/src/androidTest/java/io/github/wulkanowy/services/synchronization/TimetableSynchronizationTest.java new file mode 100644 index 00000000..1140a3d3 --- /dev/null +++ b/app/src/androidTest/java/io/github/wulkanowy/services/synchronization/TimetableSynchronizationTest.java @@ -0,0 +1,103 @@ +package io.github.wulkanowy.services.synchronization; + +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.greenrobot.greendao.database.Database; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.api.timetable.Day; +import io.github.wulkanowy.api.timetable.Lesson; +import io.github.wulkanowy.api.timetable.Timetable; +import io.github.wulkanowy.api.timetable.Week; +import io.github.wulkanowy.dao.entities.Account; +import io.github.wulkanowy.dao.entities.DaoMaster; +import io.github.wulkanowy.dao.entities.DaoSession; +import io.github.wulkanowy.services.LoginSession; +import io.github.wulkanowy.services.synchronisation.TimetableSynchronization; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +@RunWith(AndroidJUnit4.class) +public class TimetableSynchronizationTest { + + private static DaoSession daoSession; + + @BeforeClass + public static void setUpClass() { + DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(InstrumentationRegistry.getTargetContext(), "wulkanowyTest-database"); + Database database = devOpenHelper.getWritableDb(); + + daoSession = new DaoMaster(database).newSession(); + } + + @Test + public void syncTimetableEmptyDatabaseTest() throws Exception { + Long userId = daoSession.getAccountDao().insert(new Account().setEmail("TEST@TEST")); + + List dayList = new ArrayList<>(); + dayList.add(new Day() + .setDate("20.12.2012") + .setLesson(new Lesson().setSubject("Matematyka").setRoom("20"))); + Week week = new Week().setDays(dayList); + + List nextDayList = new ArrayList<>(); + dayList.add(new Day() + .setDate("24.11.2013") + .setLesson(new Lesson().setSubject("Matematyka").setRoom("22"))); + Week nextWeek = new Week().setDays(nextDayList); + + Timetable timetable = mock(Timetable.class); + doReturn(week).when(timetable).getWeekTable(); + doReturn(nextWeek).when(timetable).getWeekTable(anyString()); + + Vulcan vulcan = mock(Vulcan.class); + doReturn(timetable).when(vulcan).getTimetable(); + + LoginSession loginSession = mock(LoginSession.class); + doReturn(vulcan).when(loginSession).getVulcan(); + doReturn(daoSession).when(loginSession).getDaoSession(); + doReturn(userId).when(loginSession).getUserId(); + + TimetableSynchronization timetableSynchronization = new TimetableSynchronization(); + timetableSynchronization.sync(loginSession, null); + + List dayEntityList = daoSession.getDayDao().loadAll(); + List lessonEntityList = dayEntityList.get(0).getLessons(); + + Assert.assertNotNull(dayEntityList.get(0)); + Assert.assertEquals(userId, dayEntityList.get(0).getUserId()); + Assert.assertEquals(1L, lessonEntityList.get(0).getDayId().longValue()); + Assert.assertEquals("Matematyka", lessonEntityList.get(0).getSubject()); + Assert.assertEquals("20", lessonEntityList.get(0).getRoom()); + Assert.assertEquals("20.12.2012", dayEntityList.get(0).getDate()); + + } + + @Before + public void setUp() { + daoSession.getAccountDao().deleteAll(); + daoSession.getDayDao().deleteAll(); + daoSession.getLessonDao().deleteAll(); + daoSession.clear(); + } + + @AfterClass + public static void cleanUp() { + daoSession.getAccountDao().deleteAll(); + daoSession.getDayDao().deleteAll(); + daoSession.getLessonDao().deleteAll(); + daoSession.clear(); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 95b204cf..89cd9b7f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,7 +11,7 @@ @@ -30,17 +30,17 @@ diff --git a/app/src/main/java/io/github/wulkanowy/activity/dashboard/lessonplan/LessonPlanFragment.java b/app/src/main/java/io/github/wulkanowy/activity/dashboard/lessonplan/LessonPlanFragment.java deleted file mode 100644 index 7c8cd30e..00000000 --- a/app/src/main/java/io/github/wulkanowy/activity/dashboard/lessonplan/LessonPlanFragment.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.wulkanowy.activity.dashboard.lessonplan; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import io.github.wulkanowy.R; - -public class LessonPlanFragment extends Fragment { - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_lessonplan, container, false); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/dao/entities/Account.java b/app/src/main/java/io/github/wulkanowy/dao/entities/Account.java index 0102a8e8..42b636b7 100644 --- a/app/src/main/java/io/github/wulkanowy/dao/entities/Account.java +++ b/app/src/main/java/io/github/wulkanowy/dao/entities/Account.java @@ -9,7 +9,10 @@ import org.greenrobot.greendao.annotation.ToMany; import java.util.List; -@Entity(nameInDb = "Accounts") +@Entity( + nameInDb = "Accounts", + active = true +) public class Account { @Id(autoincrement = true) @@ -36,6 +39,9 @@ public class Account { @ToMany(referencedJoinProperty = "userId") private List gradeList; + @ToMany(referencedJoinProperty = "userId") + private List dayList; + /** * Used to resolve relations */ @@ -50,7 +56,7 @@ public class Account { @Generated(hash = 735765217) public Account(Long id, String name, String email, String password, String symbol, - String snpId) { + String snpId) { this.id = id; this.name = name; this.email = email; @@ -213,6 +219,36 @@ public class Account { myDao.update(this); } + /** + * To-many relationship, resolved on first access (and after reset). + * Changes to to-many relations are not persisted, make changes to the target entity. + */ + @Generated(hash = 300459794) + public List getDayList() { + if (dayList == null) { + final DaoSession daoSession = this.daoSession; + if (daoSession == null) { + throw new DaoException("Entity is detached from DAO context"); + } + DayDao targetDao = daoSession.getDayDao(); + List dayListNew = targetDao._queryAccount_DayList(id); + synchronized (this) { + if (dayList == null) { + dayList = dayListNew; + } + } + } + return dayList; + } + + /** + * Resets a to-many relationship, making the next get call to query for a fresh result. + */ + @Generated(hash = 1010399236) + public synchronized void resetDayList() { + dayList = null; + } + /** called by internal mechanisms, do not call yourself. */ @Generated(hash = 1812283172) public void __setDaoSession(DaoSession daoSession) { diff --git a/app/src/main/java/io/github/wulkanowy/dao/entities/Day.java b/app/src/main/java/io/github/wulkanowy/dao/entities/Day.java new file mode 100644 index 00000000..27aff06f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/dao/entities/Day.java @@ -0,0 +1,214 @@ +package io.github.wulkanowy.dao.entities; + +import org.greenrobot.greendao.DaoException; +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Generated; +import org.greenrobot.greendao.annotation.Id; +import org.greenrobot.greendao.annotation.Index; +import org.greenrobot.greendao.annotation.Property; +import org.greenrobot.greendao.annotation.ToMany; + +import java.util.List; + +@Entity( + nameInDb = "Days", + active = true, + indexes ={@Index(value = "userId,weekId,date", unique = true)} +) +public class Day { + + @Id(autoincrement = true) + private Long id; + + @Property(nameInDb = "USER_ID") + private Long userId; + + @Property(nameInDb = "WEEK_ID") + private Long weekId; + + @Property(nameInDb = "DATE") + private String date = ""; + + @Property(nameInDb = "DAY_NAME") + private String dayName = ""; + + @Property(nameInDb = "IS_FREE_DAY") + private boolean isFreeDay = false; + + @Property(nameInDb = "FREE_DAY_NAME") + private String freeDayName = ""; + + @ToMany(referencedJoinProperty = "dayId") + private List lessons; + + /** + * Used to resolve relations + */ + @Generated(hash = 2040040024) + private transient DaoSession daoSession; + + /** + * Used for active entity operations. + */ + @Generated(hash = 312167767) + private transient DayDao myDao; + + @Generated(hash = 723729681) + public Day(Long id, Long userId, Long weekId, String date, String dayName, + boolean isFreeDay, String freeDayName) { + this.id = id; + this.userId = userId; + this.weekId = weekId; + this.date = date; + this.dayName = dayName; + this.isFreeDay = isFreeDay; + this.freeDayName = freeDayName; + } + + @Generated(hash = 866989762) + public Day() { + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getUserId() { + return userId; + } + + public Long getWeekId() { + return weekId; + } + + public Day setWeekId(Long weekId) { + this.weekId = weekId; + return this; + } + + public Day setUserId(Long userId) { + this.userId = userId; + return this; + } + + public String getDate() { + return date; + } + + public Day setDate(String date) { + this.date = date; + return this; + } + + public String getDayName() { + return dayName; + } + + public Day setDayName(String dayName) { + this.dayName = dayName; + return this; + } + + public boolean isFreeDay() { + return isFreeDay; + } + + public Day setFreeDay(boolean freeDay) { + isFreeDay = freeDay; + return this; + } + + public String getFreeDayName() { + return freeDayName; + } + + public Day setFreeDayName(String freeDayName) { + this.freeDayName = freeDayName; + return this; + } + + public boolean getIsFreeDay() { + return this.isFreeDay; + } + + public void setIsFreeDay(boolean isFreeDay) { + this.isFreeDay = isFreeDay; + } + + /** + * To-many relationship, resolved on first access (and after reset). + * Changes to to-many relations are not persisted, make changes to the target entity. + */ + @Generated(hash = 1552857303) + public List getLessons() { + if (lessons == null) { + final DaoSession daoSession = this.daoSession; + if (daoSession == null) { + throw new DaoException("Entity is detached from DAO context"); + } + LessonDao targetDao = daoSession.getLessonDao(); + List lessonsNew = targetDao._queryDay_Lessons(id); + synchronized (this) { + if (lessons == null) { + lessons = lessonsNew; + } + } + } + return lessons; + } + + /** + * Resets a to-many relationship, making the next get call to query for a fresh result. + */ + @Generated(hash = 1769801440) + public synchronized void resetLessons() { + lessons = null; + } + + /** + * Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}. + * Entity must attached to an entity context. + */ + @Generated(hash = 128553479) + public void delete() { + if (myDao == null) { + throw new DaoException("Entity is detached from DAO context"); + } + myDao.delete(this); + } + + /** + * Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}. + * Entity must attached to an entity context. + */ + @Generated(hash = 1942392019) + public void refresh() { + if (myDao == null) { + throw new DaoException("Entity is detached from DAO context"); + } + myDao.refresh(this); + } + + /** + * Convenient call for {@link org.greenrobot.greendao.AbstractDao#update(Object)}. + * Entity must attached to an entity context. + */ + @Generated(hash = 713229351) + public void update() { + if (myDao == null) { + throw new DaoException("Entity is detached from DAO context"); + } + myDao.update(this); + } + + /** called by internal mechanisms, do not call yourself. */ + @Generated(hash = 1409317752) + public void __setDaoSession(DaoSession daoSession) { + this.daoSession = daoSession; + myDao = daoSession != null ? daoSession.getDayDao() : null; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/dao/entities/Lesson.java b/app/src/main/java/io/github/wulkanowy/dao/entities/Lesson.java new file mode 100644 index 00000000..5465cb05 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/dao/entities/Lesson.java @@ -0,0 +1,349 @@ +package io.github.wulkanowy.dao.entities; + +import org.greenrobot.greendao.DaoException; +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Generated; +import org.greenrobot.greendao.annotation.Id; +import org.greenrobot.greendao.annotation.Index; +import org.greenrobot.greendao.annotation.Property; + +@Entity( + nameInDb = "Lessons", + active = true, + indexes ={@Index(value = "dayId,date,startTime,endTime", unique = true)} +) +public class Lesson { + + @Id(autoincrement = true) + private Long id; + + @Property(nameInDb = "DAY_ID") + private Long dayId; + + @Property(nameInDb = "NUMBER_OF_LESSON") + private String number; + + @Property(nameInDb = "SUBJECT_NAME") + private String subject = ""; + + @Property(nameInDb = "TEACHER") + private String teacher = ""; + + @Property(nameInDb = "ROOM") + private String room = ""; + + @Property(nameInDb = "DESCRIPTION") + private String description = ""; + + @Property(nameInDb = "GROUP_NAME") + private String groupName = ""; + + @Property(nameInDb = "START_TIME") + private String startTime = ""; + + @Property(nameInDb = "END_TIME") + private String endTime = ""; + + @Property(nameInDb = "DATE") + private String date = ""; + + @Property(nameInDb = "IS_EMPTY") + private boolean isEmpty = false; + + @Property(nameInDb = "IS_DIVISION_INTO_GROUP") + private boolean isDivisionIntoGroups = false; + + @Property(nameInDb = "IS_PLANNING") + private boolean isPlanning = false; + + @Property(nameInDb = "IS_REALIZED") + private boolean isRealized = false; + + @Property(nameInDb = "IS_MOVED_CANCELED") + private boolean isMovedOrCanceled = false; + + @Property(nameInDb = "IS_NEW_MOVED_IN_CANCELED") + private boolean isNewMovedInOrChanged = false; + + /** + * Used to resolve relations + */ + @Generated(hash = 2040040024) + private transient DaoSession daoSession; + + /** + * Used for active entity operations. + */ + @Generated(hash = 610143130) + private transient LessonDao myDao; + + @Generated(hash = 140778287) + public Lesson(Long id, Long dayId, String number, String subject, String teacher, String room, + String description, String groupName, String startTime, String endTime, String date, + boolean isEmpty, boolean isDivisionIntoGroups, boolean isPlanning, boolean isRealized, + boolean isMovedOrCanceled, boolean isNewMovedInOrChanged) { + this.id = id; + this.dayId = dayId; + this.number = number; + this.subject = subject; + this.teacher = teacher; + this.room = room; + this.description = description; + this.groupName = groupName; + this.startTime = startTime; + this.endTime = endTime; + this.date = date; + this.isEmpty = isEmpty; + this.isDivisionIntoGroups = isDivisionIntoGroups; + this.isPlanning = isPlanning; + this.isRealized = isRealized; + this.isMovedOrCanceled = isMovedOrCanceled; + this.isNewMovedInOrChanged = isNewMovedInOrChanged; + } + + @Generated(hash = 1669664117) + public Lesson() { + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getDayId() { + return this.dayId; + } + + public void setDayId(Long dayId) { + this.dayId = dayId; + } + + public String getNumber() { + return number; + } + + public Lesson setNumber(String number) { + this.number = number; + return this; + } + + public String getSubject() { + return subject; + } + + public Lesson setSubject(String subject) { + this.subject = subject; + return this; + } + + public String getTeacher() { + return teacher; + } + + public Lesson setTeacher(String teacher) { + this.teacher = teacher; + return this; + } + + public String getRoom() { + return room; + } + + public Lesson setRoom(String room) { + this.room = room; + return this; + } + + public String getDescription() { + return description; + } + + public Lesson setDescription(String description) { + this.description = description; + return this; + } + + public String getGroupName() { + return groupName; + } + + public Lesson setGroupName(String groupName) { + this.groupName = groupName; + return this; + } + + public String getStartTime() { + return startTime; + } + + public Lesson setStartTime(String startTime) { + this.startTime = startTime; + return this; + } + + public String getEndTime() { + return endTime; + } + + public Lesson setEndTime(String endTime) { + this.endTime = endTime; + return this; + } + + public String getDate() { + return date; + } + + public Lesson setDate(String date) { + this.date = date; + return this; + } + + public boolean isEmpty() { + return isEmpty; + } + + public Lesson setEmpty(boolean empty) { + isEmpty = empty; + return this; + } + + public boolean isDivisionIntoGroups() { + return isDivisionIntoGroups; + } + + public Lesson setDivisionIntoGroups(boolean divisionIntoGroups) { + isDivisionIntoGroups = divisionIntoGroups; + return this; + } + + public boolean isPlanning() { + return isPlanning; + } + + public Lesson setPlanning(boolean planning) { + isPlanning = planning; + return this; + } + + public boolean isRealized() { + return isRealized; + } + + public Lesson setRealized(boolean realized) { + isRealized = realized; + return this; + } + + public boolean isMovedOrCanceled() { + return isMovedOrCanceled; + } + + public Lesson setMovedOrCanceled(boolean movedOrCanceled) { + isMovedOrCanceled = movedOrCanceled; + return this; + } + + public boolean isNewMovedInOrChanged() { + return isNewMovedInOrChanged; + } + + public Lesson setNewMovedInOrChanged(boolean newMovedInOrChanged) { + isNewMovedInOrChanged = newMovedInOrChanged; + return this; + } + + public boolean getIsEmpty() { + return this.isEmpty; + } + + public void setIsEmpty(boolean isEmpty) { + this.isEmpty = isEmpty; + } + + public boolean getIsDivisionIntoGroups() { + return this.isDivisionIntoGroups; + } + + public void setIsDivisionIntoGroups(boolean isDivisionIntoGroups) { + this.isDivisionIntoGroups = isDivisionIntoGroups; + } + + public boolean getIsPlanning() { + return this.isPlanning; + } + + public void setIsPlanning(boolean isPlanning) { + this.isPlanning = isPlanning; + } + + public boolean getIsRealized() { + return this.isRealized; + } + + public void setIsRealized(boolean isRealized) { + this.isRealized = isRealized; + } + + public boolean getIsMovedOrCanceled() { + return this.isMovedOrCanceled; + } + + public void setIsMovedOrCanceled(boolean isMovedOrCanceled) { + this.isMovedOrCanceled = isMovedOrCanceled; + } + + public boolean getIsNewMovedInOrChanged() { + return this.isNewMovedInOrChanged; + } + + public void setIsNewMovedInOrChanged(boolean isNewMovedInOrChanged) { + this.isNewMovedInOrChanged = isNewMovedInOrChanged; + } + + /** + * Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}. + * Entity must attached to an entity context. + */ + @Generated(hash = 128553479) + public void delete() { + if (myDao == null) { + throw new DaoException("Entity is detached from DAO context"); + } + myDao.delete(this); + } + + /** + * Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}. + * Entity must attached to an entity context. + */ + @Generated(hash = 1942392019) + public void refresh() { + if (myDao == null) { + throw new DaoException("Entity is detached from DAO context"); + } + myDao.refresh(this); + } + + /** + * Convenient call for {@link org.greenrobot.greendao.AbstractDao#update(Object)}. + * Entity must attached to an entity context. + */ + @Generated(hash = 713229351) + public void update() { + if (myDao == null) { + throw new DaoException("Entity is detached from DAO context"); + } + myDao.update(this); + } + + /** called by internal mechanisms, do not call yourself. */ + @Generated(hash = 2078826279) + public void __setDaoSession(DaoSession daoSession) { + this.daoSession = daoSession; + myDao = daoSession != null ? daoSession.getLessonDao() : null; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/dao/entities/Subject.java b/app/src/main/java/io/github/wulkanowy/dao/entities/Subject.java index 5f77e344..8e93a417 100644 --- a/app/src/main/java/io/github/wulkanowy/dao/entities/Subject.java +++ b/app/src/main/java/io/github/wulkanowy/dao/entities/Subject.java @@ -9,7 +9,10 @@ import org.greenrobot.greendao.annotation.ToMany; import java.util.List; -@Entity(nameInDb = "Subjects") +@Entity( + nameInDb = "Subjects", + active = true +) public class Subject { @Id(autoincrement = true) diff --git a/app/src/main/java/io/github/wulkanowy/dao/entities/Week.java b/app/src/main/java/io/github/wulkanowy/dao/entities/Week.java new file mode 100644 index 00000000..4e9855e5 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/dao/entities/Week.java @@ -0,0 +1,148 @@ +package io.github.wulkanowy.dao.entities; + +import org.greenrobot.greendao.DaoException; +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Generated; +import org.greenrobot.greendao.annotation.Id; +import org.greenrobot.greendao.annotation.Index; +import org.greenrobot.greendao.annotation.Property; +import org.greenrobot.greendao.annotation.ToMany; + +import java.util.List; + +@Entity( + nameInDb = "Weeks", + active = true, + indexes ={@Index(value = "userId,startDayDate", unique = true)} +) +public class Week { + + @Id(autoincrement = true) + private Long id; + + @Property(nameInDb = "USER_ID") + private Long userId; + + @Property(nameInDb = "START_DATE") + private String startDayDate = ""; + + @ToMany(referencedJoinProperty = "weekId") + private List dayList; + + /** Used to resolve relations */ + @Generated(hash = 2040040024) + private transient DaoSession daoSession; + + /** Used for active entity operations. */ + @Generated(hash = 1019310398) + private transient WeekDao myDao; + + @Generated(hash = 36829814) + public Week(Long id, Long userId, String startDayDate) { + this.id = id; + this.userId = userId; + this.startDayDate = startDayDate; + } + + @Generated(hash = 2135529658) + public Week() { + } + + public Long getId() { + return id; + } + + public Week setId(Long id) { + this.id = id; + return this; + } + + public Long getUserId() { + return userId; + } + + public Week setUserId(Long userId) { + this.userId = userId; + return this; + } + + public String getStartDayDate() { + return startDayDate; + } + + public Week setStartDayDate(String startDayDate) { + this.startDayDate = startDayDate; + return this; + } + + /** + * To-many relationship, resolved on first access (and after reset). + * Changes to to-many relations are not persisted, make changes to the target entity. + */ + @Generated(hash = 1562119145) + public List getDayList() { + if (dayList == null) { + final DaoSession daoSession = this.daoSession; + if (daoSession == null) { + throw new DaoException("Entity is detached from DAO context"); + } + DayDao targetDao = daoSession.getDayDao(); + List dayListNew = targetDao._queryWeek_DayList(id); + synchronized (this) { + if (dayList == null) { + dayList = dayListNew; + } + } + } + return dayList; + } + + /** Resets a to-many relationship, making the next get call to query for a fresh result. */ + @Generated(hash = 1010399236) + public synchronized void resetDayList() { + dayList = null; + } + + /** + * Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}. + * Entity must attached to an entity context. + */ + @Generated(hash = 128553479) + public void delete() { + if (myDao == null) { + throw new DaoException("Entity is detached from DAO context"); + } + myDao.delete(this); + } + + /** + * Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}. + * Entity must attached to an entity context. + */ + @Generated(hash = 1942392019) + public void refresh() { + if (myDao == null) { + throw new DaoException("Entity is detached from DAO context"); + } + myDao.refresh(this); + } + + /** + * Convenient call for {@link org.greenrobot.greendao.AbstractDao#update(Object)}. + * Entity must attached to an entity context. + */ + @Generated(hash = 713229351) + public void update() { + if (myDao == null) { + throw new DaoException("Entity is detached from DAO context"); + } + myDao.update(this); + } + + /** called by internal mechanisms, do not call yourself. */ + @Generated(hash = 665278367) + public void __setDaoSession(DaoSession daoSession) { + this.daoSession = daoSession; + myDao = daoSession != null ? daoSession.getWeekDao() : null; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/security/Safety.java b/app/src/main/java/io/github/wulkanowy/security/Safety.java index 1ae36dd6..f55f7518 100644 --- a/app/src/main/java/io/github/wulkanowy/security/Safety.java +++ b/app/src/main/java/io/github/wulkanowy/security/Safety.java @@ -15,7 +15,7 @@ public class Safety extends Scrambler { generateNewKey(email, context); return encryptString(email, plainText); } else { - if (!RootUtilities.isRooted()) { + if (RootUtilities.isRooted()) { return new String(Base64.encode(plainText.getBytes(), Base64.DEFAULT)); } else { throw new UnsupportedOperationException("Stored data in this devices isn't safe because android is rooted"); diff --git a/app/src/main/java/io/github/wulkanowy/security/Scrambler.java b/app/src/main/java/io/github/wulkanowy/security/Scrambler.java index 7e7ffb8c..1c32af29 100644 --- a/app/src/main/java/io/github/wulkanowy/security/Scrambler.java +++ b/app/src/main/java/io/github/wulkanowy/security/Scrambler.java @@ -29,11 +29,10 @@ import javax.security.auth.x500.X500Principal; public class Scrambler { + public static final String DEBUG_TAG = "WulkanowySecurity"; private static final String ANDROID_KEYSTORE = "AndroidKeyStore"; - public static final String DEBUG_TAG = "WulkanowySecurity"; - private KeyStore keyStore; protected void loadKeyStore() throws CryptoException { @@ -48,6 +47,7 @@ public class Scrambler { } + @SuppressWarnings("deprecation") @TargetApi(18) protected void generateNewKey(String alias, Context context) throws CryptoException { @@ -116,9 +116,9 @@ public class Scrambler { cipherOutputStream.write(text.getBytes("UTF-8")); cipherOutputStream.close(); - byte[] vals = outputStream.toByteArray(); + byte[] values = outputStream.toByteArray(); - return Base64.encodeToString(vals, Base64.DEFAULT); + return Base64.encodeToString(values, Base64.DEFAULT); } catch (Exception e) { Log.e(DEBUG_TAG, e.getMessage()); diff --git a/app/src/main/java/io/github/wulkanowy/services/VulcanSynchronization.java b/app/src/main/java/io/github/wulkanowy/services/VulcanSynchronization.java index 27f25ed0..07bf5462 100644 --- a/app/src/main/java/io/github/wulkanowy/services/VulcanSynchronization.java +++ b/app/src/main/java/io/github/wulkanowy/services/VulcanSynchronization.java @@ -1,6 +1,7 @@ package io.github.wulkanowy.services; import android.content.Context; +import android.support.annotation.Nullable; import android.util.Log; import java.io.IOException; @@ -19,6 +20,7 @@ import io.github.wulkanowy.services.synchronisation.CurrentAccountLogin; import io.github.wulkanowy.services.synchronisation.FirstAccountLogin; import io.github.wulkanowy.services.synchronisation.GradesSynchronisation; import io.github.wulkanowy.services.synchronisation.SubjectsSynchronisation; +import io.github.wulkanowy.services.synchronisation.TimetableSynchronization; public class VulcanSynchronization { @@ -32,6 +34,10 @@ public class VulcanSynchronization { this.loginSession = loginSession; } + public VulcanSynchronization() { + this.loginSession = new LoginSession(); + } + public void firstLoginConnectStep(String email, String password, String symbol) throws BadCredentialsException, IOException { firstAccountLogin = new FirstAccountLogin(new Login(new Cookies()), new Vulcan(), email, password, symbol); @@ -48,44 +54,71 @@ public class VulcanSynchronization { } } - public void loginCurrentUser(Context context, DaoSession daoSession, Vulcan vulcan) - throws CryptoException, BadCredentialsException, AccountPermissionException, LoginErrorException, IOException { - CurrentAccountLogin currentAccountLogin = new CurrentAccountLogin(context, daoSession, vulcan); - loginSession = currentAccountLogin.loginCurrentUser(); + public VulcanSynchronization loginCurrentUser(Context context, DaoSession daoSession) throws CryptoException, + BadCredentialsException, AccountPermissionException, LoginErrorException, IOException { + return loginCurrentUser(context, daoSession, new Vulcan()); } - public boolean syncGrades() { + public VulcanSynchronization loginCurrentUser(Context context, DaoSession daoSession, Vulcan vulcan) + throws CryptoException, BadCredentialsException, AccountPermissionException, + LoginErrorException, IOException { + CurrentAccountLogin currentAccountLogin = new CurrentAccountLogin(context, daoSession, vulcan); + loginSession = currentAccountLogin.loginCurrentUser(); + return this; + } + + public void syncAll() throws IOException { + syncSubjectsAndGrades(); + syncTimetable(); + } + + public void syncGrades() throws IOException { if (loginSession != null) { GradesSynchronisation gradesSynchronisation = new GradesSynchronisation(); try { gradesSynchronisation.sync(loginSession); - return true; } catch (Exception e) { Log.e(VulcanJobHelper.DEBUG_TAG, "Synchronisation of grades failed", e); - return false; + throw new IOException(e.getCause()); } } else { Log.e(VulcanJobHelper.DEBUG_TAG, "Before synchronization, should login user to log", new UnsupportedOperationException()); - return false; } } - public boolean syncSubjectsAndGrades() { + public void syncSubjectsAndGrades() throws IOException { if (loginSession != null) { SubjectsSynchronisation subjectsSynchronisation = new SubjectsSynchronisation(); try { subjectsSynchronisation.sync(loginSession); syncGrades(); - return true; } catch (Exception e) { Log.e(VulcanJobHelper.DEBUG_TAG, "Synchronisation of subjects failed", e); - return false; + throw new IOException(e.getCause()); + } + } else { + Log.e(VulcanJobHelper.DEBUG_TAG, "Before synchronization, should login user to log", + new UnsupportedOperationException()); + } + } + + public void syncTimetable() throws IOException { + syncTimetable(null); + } + + public void syncTimetable(@Nullable String date) throws IOException { + if (loginSession != null) { + TimetableSynchronization timetableSynchronization = new TimetableSynchronization(); + try { + timetableSynchronization.sync(loginSession, date); + } catch (Exception e) { + Log.e(VulcanJobHelper.DEBUG_TAG, "Synchronization of timetable failed", e); + throw new IOException(e.getCause()); } } else { Log.e(VulcanJobHelper.DEBUG_TAG, "Before synchronization, should login user to log", new UnsupportedOperationException()); - return false; } } } diff --git a/app/src/main/java/io/github/wulkanowy/services/jobs/GradeJob.java b/app/src/main/java/io/github/wulkanowy/services/jobs/FullSyncJob.java similarity index 61% rename from app/src/main/java/io/github/wulkanowy/services/jobs/GradeJob.java rename to app/src/main/java/io/github/wulkanowy/services/jobs/FullSyncJob.java index a813b2ec..1d4c7044 100644 --- a/app/src/main/java/io/github/wulkanowy/services/jobs/GradeJob.java +++ b/app/src/main/java/io/github/wulkanowy/services/jobs/FullSyncJob.java @@ -11,59 +11,49 @@ import com.firebase.jobdispatcher.Lifetime; import com.firebase.jobdispatcher.RetryStrategy; import com.firebase.jobdispatcher.Trigger; -import java.io.IOException; import java.util.List; import java.util.Random; import io.github.wulkanowy.R; -import io.github.wulkanowy.activity.WulkanowyApp; -import io.github.wulkanowy.activity.dashboard.DashboardActivity; -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.api.login.AccountPermissionException; -import io.github.wulkanowy.api.login.BadCredentialsException; -import io.github.wulkanowy.api.login.NotLoggedInErrorException; import io.github.wulkanowy.dao.DatabaseAccess; import io.github.wulkanowy.dao.entities.DaoSession; import io.github.wulkanowy.dao.entities.Grade; -import io.github.wulkanowy.security.CryptoException; -import io.github.wulkanowy.services.LoginSession; import io.github.wulkanowy.services.VulcanSynchronization; -import io.github.wulkanowy.services.notifications.NotificationHelper; +import io.github.wulkanowy.services.notifications.NotificationBuilder; +import io.github.wulkanowy.ui.WulkanowyApp; +import io.github.wulkanowy.ui.main.DashboardActivity; -public class GradeJob extends VulcanJobHelper { +public class FullSyncJob extends VulcanJobHelper { - private static final String UNIQUE_TAG = "GradesSync34512"; + private static final String UNIQUE_TAG = "FullSync"; private static final int DEFAULT_INTERVAL_START = 60 * 50; - private static final int DEFAULT_INTERVAL_END = DEFAULT_INTERVAL_START + (60 * 10); + private static final int DEFAULT_INTERVAL_END = DEFAULT_INTERVAL_START + (60 * 40); @Override protected Job createJob(FirebaseJobDispatcher dispatcher) { return dispatcher.newJobBuilder() .setLifetime(Lifetime.FOREVER) - .setService(GradeService.class) + .setService(SyncService.class) .setTag(UNIQUE_TAG) .setRecurring(true) .setTrigger(Trigger.executionWindow(DEFAULT_INTERVAL_START, DEFAULT_INTERVAL_END)) .setConstraints(Constraint.ON_ANY_NETWORK) - .setReplaceCurrent(true) + .setReplaceCurrent(false) .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) .build(); } - public static class GradeService extends VulcanService { + public static class SyncService extends VulcanService { @Override - public void workToBePerformed() throws CryptoException, BadCredentialsException, - NotLoggedInErrorException, AccountPermissionException, IOException { - + public void workToBePerformed() throws Exception { DaoSession daoSession = ((WulkanowyApp) getApplication()).getDaoSession(); - VulcanSynchronization vulcanSynchronization = new VulcanSynchronization(new LoginSession()); - vulcanSynchronization.loginCurrentUser(getApplicationContext(), daoSession, new Vulcan()); - vulcanSynchronization.syncGrades(); - + VulcanSynchronization synchronization = new VulcanSynchronization() + .loginCurrentUser(getApplicationContext(), daoSession); + synchronization.syncAll(); List newGradeList = new DatabaseAccess().getNewGrades(daoSession); if (newGradeList.size() == 1) { @@ -81,10 +71,10 @@ public class GradeJob extends VulcanJobHelper { PendingIntent pendingIntent = PendingIntent .getActivity(getApplicationContext(), 0, intent, 0); - NotificationHelper notificationHelper = new NotificationHelper(getApplicationContext()); - NotificationCompat.Builder builder = notificationHelper + NotificationBuilder notificationBuilder = new NotificationBuilder(getApplicationContext()); + NotificationCompat.Builder builder = notificationBuilder .getNotifications(title, bodyText, pendingIntent); - notificationHelper.getManager().notify(new Random().nextInt(10000), builder.build()); + notificationBuilder.getManager().notify(new Random().nextInt(10000), builder.build()); } } -} +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/services/jobs/SubjectJob.java b/app/src/main/java/io/github/wulkanowy/services/jobs/SubjectJob.java deleted file mode 100644 index 50515136..00000000 --- a/app/src/main/java/io/github/wulkanowy/services/jobs/SubjectJob.java +++ /dev/null @@ -1,58 +0,0 @@ -package io.github.wulkanowy.services.jobs; - -import com.firebase.jobdispatcher.Constraint; -import com.firebase.jobdispatcher.FirebaseJobDispatcher; -import com.firebase.jobdispatcher.Job; -import com.firebase.jobdispatcher.Lifetime; -import com.firebase.jobdispatcher.RetryStrategy; -import com.firebase.jobdispatcher.Trigger; - -import java.io.IOException; - -import io.github.wulkanowy.activity.WulkanowyApp; -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.api.login.AccountPermissionException; -import io.github.wulkanowy.api.login.BadCredentialsException; -import io.github.wulkanowy.api.login.NotLoggedInErrorException; -import io.github.wulkanowy.dao.entities.DaoSession; -import io.github.wulkanowy.security.CryptoException; -import io.github.wulkanowy.services.LoginSession; -import io.github.wulkanowy.services.VulcanSynchronization; - -public class SubjectJob extends VulcanJobHelper { - - private static final String UNIQUE_TAG = "SubjectsSync34512"; - - private static final int DEFAULT_INTERVAL_START = 0; - - private static final int DEFAULT_INTERVAL_END = DEFAULT_INTERVAL_START + 10; - - @Override - protected Job createJob(FirebaseJobDispatcher dispatcher) { - return dispatcher.newJobBuilder() - .setLifetime(Lifetime.UNTIL_NEXT_BOOT) - .setService(SubjectService.class) - .setTag(UNIQUE_TAG) - .setRecurring(false) - .setTrigger(Trigger.executionWindow(DEFAULT_INTERVAL_START, DEFAULT_INTERVAL_END)) - .setConstraints(Constraint.ON_ANY_NETWORK) - .setReplaceCurrent(true) - .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) - .build(); - } - - private class SubjectService extends VulcanService { - - @Override - public void workToBePerformed() throws CryptoException, BadCredentialsException, - NotLoggedInErrorException, AccountPermissionException, IOException { - - DaoSession daoSession = ((WulkanowyApp) getApplication()).getDaoSession(); - - VulcanSynchronization vulcanSynchronization = new VulcanSynchronization(new LoginSession()); - vulcanSynchronization.loginCurrentUser(getApplicationContext(), daoSession, new Vulcan()); - vulcanSynchronization.syncSubjectsAndGrades(); - - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanJobHelper.java b/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanJobHelper.java index 7d55f799..a2d21fcb 100644 --- a/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanJobHelper.java +++ b/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanJobHelper.java @@ -2,20 +2,23 @@ package io.github.wulkanowy.services.jobs; import android.content.Context; +import android.util.Log; import com.firebase.jobdispatcher.FirebaseJobDispatcher; import com.firebase.jobdispatcher.GooglePlayDriver; import com.firebase.jobdispatcher.Job; -public abstract class VulcanJobHelper { +public abstract class VulcanJobHelper { public static final String DEBUG_TAG = "SynchronizationService"; - public void scheduledJob(Context context) { + @SuppressWarnings("unchecked") + public final T scheduledJob(Context context) { FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context)); dispatcher.mustSchedule(createJob(dispatcher)); + Log.i(DEBUG_TAG, "Wulkanowy Job is initiation: " + this.toString()); + return (T) this; } protected abstract Job createJob(FirebaseJobDispatcher dispatcher); - } diff --git a/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanService.java b/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanService.java index ace37d6b..56c93e54 100644 --- a/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanService.java +++ b/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanService.java @@ -6,14 +6,8 @@ import android.util.Log; import com.firebase.jobdispatcher.JobParameters; import com.firebase.jobdispatcher.JobService; -import java.io.IOException; import java.lang.ref.WeakReference; -import io.github.wulkanowy.api.login.AccountPermissionException; -import io.github.wulkanowy.api.login.BadCredentialsException; -import io.github.wulkanowy.api.login.NotLoggedInErrorException; -import io.github.wulkanowy.security.CryptoException; - public abstract class VulcanService extends JobService { private SyncTask syncTask; @@ -35,8 +29,7 @@ public abstract class VulcanService extends JobService { return true; } - public abstract void workToBePerformed() throws CryptoException, BadCredentialsException, - NotLoggedInErrorException, AccountPermissionException, IOException; + public abstract void workToBePerformed() throws Exception; private static class SyncTask extends AsyncTask { diff --git a/app/src/main/java/io/github/wulkanowy/services/notifications/NotificationHelper.java b/app/src/main/java/io/github/wulkanowy/services/notifications/NotificationBuilder.java similarity index 95% rename from app/src/main/java/io/github/wulkanowy/services/notifications/NotificationHelper.java rename to app/src/main/java/io/github/wulkanowy/services/notifications/NotificationBuilder.java index 65eb470b..74333f7e 100644 --- a/app/src/main/java/io/github/wulkanowy/services/notifications/NotificationHelper.java +++ b/app/src/main/java/io/github/wulkanowy/services/notifications/NotificationBuilder.java @@ -13,15 +13,15 @@ import android.support.v4.app.NotificationCompat; import io.github.wulkanowy.R; -public class NotificationHelper extends ContextWrapper { - - private NotificationManager manager; +public class NotificationBuilder extends ContextWrapper { public static final String CHANNEL_ID = "Wulkanowy_New_Grade_Channel"; public static final String CHANNEL_NAME = "New Grade Channel"; - public NotificationHelper(Context context) { + private NotificationManager manager; + + public NotificationBuilder(Context context) { super(context); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { createChannel(); diff --git a/app/src/main/java/io/github/wulkanowy/services/synchronisation/FirstAccountLogin.java b/app/src/main/java/io/github/wulkanowy/services/synchronisation/FirstAccountLogin.java index 9df840d5..9b88de4a 100644 --- a/app/src/main/java/io/github/wulkanowy/services/synchronisation/FirstAccountLogin.java +++ b/app/src/main/java/io/github/wulkanowy/services/synchronisation/FirstAccountLogin.java @@ -44,7 +44,7 @@ public class FirstAccountLogin { } public LoginSession login(Context context, DaoSession daoSession, String certificate) - throws NotLoggedInErrorException, AccountPermissionException, IOException, CryptoException{ + throws NotLoggedInErrorException, AccountPermissionException, IOException, CryptoException { long userId; diff --git a/app/src/main/java/io/github/wulkanowy/services/synchronisation/TimetableSynchronization.java b/app/src/main/java/io/github/wulkanowy/services/synchronisation/TimetableSynchronization.java new file mode 100644 index 00000000..69205a46 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/synchronisation/TimetableSynchronization.java @@ -0,0 +1,117 @@ +package io.github.wulkanowy.services.synchronisation; + + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; + +import org.greenrobot.greendao.query.Query; + +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.api.timetable.Day; +import io.github.wulkanowy.api.timetable.Week; +import io.github.wulkanowy.dao.entities.DayDao; +import io.github.wulkanowy.dao.entities.Lesson; +import io.github.wulkanowy.dao.entities.LessonDao; +import io.github.wulkanowy.dao.entities.WeekDao; +import io.github.wulkanowy.services.LoginSession; +import io.github.wulkanowy.services.jobs.VulcanJobHelper; +import io.github.wulkanowy.utilities.ConversionVulcanObject; +import io.github.wulkanowy.utilities.TimeUtilities; + +public class TimetableSynchronization { + + public void sync(@NonNull LoginSession loginSession, @Nullable String dateOfMonday) throws NotLoggedInErrorException, + IOException, ParseException { + DayDao dayDao = loginSession.getDaoSession().getDayDao(); + LessonDao lessonDao = loginSession.getDaoSession().getLessonDao(); + WeekDao weekDao = loginSession.getDaoSession().getWeekDao(); + + Long weekId; + + Week week = dateOfMonday == null ? loginSession.getVulcan().getTimetable().getWeekTable() + : loginSession.getVulcan().getTimetable() + .getWeekTable(String.valueOf(TimeUtilities.getNetTicks(dateOfMonday, "yyyy-MM-dd"))); + + Query weekQuery = weekDao.queryBuilder() + .where(WeekDao.Properties.UserId.eq(loginSession.getUserId()), WeekDao.Properties.StartDayDate.eq(week.getStartDayDate())).build(); + + io.github.wulkanowy.dao.entities.Week week1 = weekQuery.unique(); + + if (week1 != null) { + weekId = week1.getId(); + } else { + weekId = weekDao.insert(ConversionVulcanObject.weekToWeekEntitie(week).setUserId(loginSession.getUserId())); + } + + List dayList = week.getDays(); + + dayDao.saveInTx(getPreparedDaysList(dayList, loginSession.getUserId(), weekId, dayDao)); + + Log.d(VulcanJobHelper.DEBUG_TAG, "Synchronization days (amount = " + dayList.size() + ")"); + + List lessonList = new ArrayList<>(); + lessonList.addAll(getPreparedLessonsList(dayList, dayDao, lessonDao)); + + lessonDao.saveInTx(lessonList); + + Log.d(VulcanJobHelper.DEBUG_TAG, "Synchronization lessons (amount = " + lessonList.size() + ")"); + } + + private List getPreparedLessonsList(List dayList, DayDao dayDao, LessonDao lessonDao) { + List allLessonsList = new ArrayList<>(); + + for (Day day : dayList) { + + Query dayQuery = dayDao.queryBuilder() + .where(DayDao.Properties.Date.eq(day.getDate())) + .build(); + + List lessonEntityList = ConversionVulcanObject.lessonsToLessonsEntities(day.getLessons()); + List updatedLessonEntityList = new ArrayList<>(); + + for (Lesson lesson : lessonEntityList) { + Query lessonQuery = lessonDao.queryBuilder().where(LessonDao.Properties.DayId.eq(dayQuery.uniqueOrThrow().getId()), LessonDao.Properties.Date.eq(lesson.getDate()), + LessonDao.Properties.StartTime.eq(lesson.getStartTime()), LessonDao.Properties.EndTime.eq(lesson.getEndTime())).build(); + Lesson lesson1 = lessonQuery.unique(); + + if (lesson1 != null) { + lesson.setId(lesson1.getId()); + } + + lesson.setDayId(dayQuery.uniqueOrThrow().getId()); + if (!"".equals(lesson.getSubject())) { + updatedLessonEntityList.add(lesson); + } + } + allLessonsList.addAll(updatedLessonEntityList); + } + return allLessonsList; + } + + private List getPreparedDaysList(List dayList, long userId, long weekId, DayDao dayDao) { + List updatedDayList = new ArrayList<>(); + List dayEntityList = ConversionVulcanObject + .daysToDaysEntities(dayList); + for (io.github.wulkanowy.dao.entities.Day day : dayEntityList) { + + Query dayQuery = dayDao.queryBuilder().where(DayDao.Properties.UserId.eq(userId), DayDao.Properties.WeekId.eq(weekId), DayDao.Properties.Date.eq(day.getDate())).build(); + + io.github.wulkanowy.dao.entities.Day day1 = dayQuery.unique(); + + if (day1 != null) { + day.setId(day1.getId()); + } + + day.setUserId(userId); + day.setWeekId(weekId); + updatedDayList.add(day); + } + return updatedDayList; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/activity/WulkanowyApp.java b/app/src/main/java/io/github/wulkanowy/ui/WulkanowyApp.java similarity index 80% rename from app/src/main/java/io/github/wulkanowy/activity/WulkanowyApp.java rename to app/src/main/java/io/github/wulkanowy/ui/WulkanowyApp.java index 83164c1f..edf8c826 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/WulkanowyApp.java +++ b/app/src/main/java/io/github/wulkanowy/ui/WulkanowyApp.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.activity; +package io.github.wulkanowy.ui; import android.app.Application; import android.content.Context; @@ -7,17 +7,22 @@ import android.content.SharedPreferences; import org.greenrobot.greendao.database.Database; import org.greenrobot.greendao.query.QueryBuilder; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.utils.Log; import io.github.wulkanowy.dao.entities.DaoMaster; import io.github.wulkanowy.dao.entities.DaoSession; public class WulkanowyApp extends Application { + public static final String DEBUG_TAG = "WulaknowyActivity"; + private DaoSession daoSession; @Override public void onCreate() { super.onCreate(); + enableDebugLog(); DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(this, "wulkanowy-database"); Database database = devOpenHelper.getWritableDb(); @@ -34,7 +39,11 @@ public class WulkanowyApp extends Application { editor.apply(); } + } + + private void enableDebugLog() { QueryBuilder.LOG_VALUES = true; + FlexibleAdapter.enableLogs(Log.Level.DEBUG); } public DaoSession getDaoSession() { diff --git a/app/src/main/java/io/github/wulkanowy/activity/login/LoginActivity.java b/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.java similarity index 99% rename from app/src/main/java/io/github/wulkanowy/activity/login/LoginActivity.java rename to app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.java index c7063ee1..05df7d23 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/login/LoginActivity.java +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.activity.login; +package io.github.wulkanowy.ui.login; import android.app.Activity; import android.content.Context; diff --git a/app/src/main/java/io/github/wulkanowy/activity/login/LoginTask.java b/app/src/main/java/io/github/wulkanowy/ui/login/LoginTask.java similarity index 89% rename from app/src/main/java/io/github/wulkanowy/activity/login/LoginTask.java rename to app/src/main/java/io/github/wulkanowy/ui/login/LoginTask.java index ce72fad9..dc51516b 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/login/LoginTask.java +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginTask.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.activity.login; +package io.github.wulkanowy.ui.login; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -17,10 +17,10 @@ import android.widget.TextView; import java.io.IOException; import java.lang.ref.WeakReference; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; import io.github.wulkanowy.R; -import io.github.wulkanowy.activity.WulkanowyApp; -import io.github.wulkanowy.activity.dashboard.DashboardActivity; import io.github.wulkanowy.api.login.AccountPermissionException; import io.github.wulkanowy.api.login.BadCredentialsException; import io.github.wulkanowy.api.login.NotLoggedInErrorException; @@ -28,7 +28,9 @@ import io.github.wulkanowy.dao.entities.DaoSession; import io.github.wulkanowy.security.CryptoException; import io.github.wulkanowy.services.LoginSession; import io.github.wulkanowy.services.VulcanSynchronization; -import io.github.wulkanowy.services.jobs.GradeJob; +import io.github.wulkanowy.services.jobs.FullSyncJob; +import io.github.wulkanowy.ui.WulkanowyApp; +import io.github.wulkanowy.ui.main.DashboardActivity; import io.github.wulkanowy.utilities.ConnectionUtilities; /** @@ -78,7 +80,7 @@ public class LoginTask extends AsyncTask { vulcanSynchronization.firstLoginSignInStep(activity.get(), daoSession); publishProgress("3", activity.get().getResources().getString(R.string.step_synchronization)); - vulcanSynchronization.syncSubjectsAndGrades(); + vulcanSynchronization.syncAll(); } catch (BadCredentialsException e) { return R.string.login_bad_credentials_text; @@ -86,14 +88,17 @@ public class LoginTask extends AsyncTask { return R.string.error_bad_account_permission; } catch (CryptoException e) { return R.string.encrypt_failed_text; + } catch (UnknownHostException e) { + return R.string.noInternet_text; + } catch (SocketTimeoutException e) { + return R.string.generic_timeout_error; } catch (NotLoggedInErrorException | IOException e) { return R.string.login_denied_text; } catch (UnsupportedOperationException e) { return -1; } - GradeJob gradeJob = new GradeJob(); - gradeJob.scheduledJob(activity.get()); + new FullSyncJob().scheduledJob(activity.get()); return R.string.login_accepted_text; @@ -145,12 +150,12 @@ public class LoginTask extends AsyncTask { .setIcon(android.R.drawable.ic_dialog_alert) .setTitle(R.string.alert_dialog_blocked_app) .setMessage(R.string.alert_dialog_blocked_app_message) - .setPositiveButton(R.string.dialog_close, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dialogInterface.dismiss(); - } - }); + .setPositiveButton(R.string.generic_dialog_close, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + } + }); alertDialog.show(); break; diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/AbstractFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/AbstractFragment.java new file mode 100644 index 00000000..161fd735 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/AbstractFragment.java @@ -0,0 +1,189 @@ +package io.github.wulkanowy.ui.main; + + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager; +import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem; +import io.github.wulkanowy.R; +import io.github.wulkanowy.dao.entities.DaoSession; +import io.github.wulkanowy.ui.WulkanowyApp; +import io.github.wulkanowy.utilities.ConnectionUtilities; + +public abstract class AbstractFragment extends Fragment + implements AsyncResponse { + + private FlexibleAdapter flexibleAdapter; + + private List itemList = new ArrayList<>(); + + private WeakReference activityWeakReference; + + private SwipeRefreshLayout swipeRefreshLayout; + + private RecyclerView recyclerViewLayout; + + private DaoSession daoSession; + + private long userId; + + public AbstractFragment() { + //empty constructor for fragments + } + + public long getUserId() { + return userId; + } + + public SwipeRefreshLayout getRefreshLayoutView() { + return swipeRefreshLayout; + } + + public DaoSession getDaoSession() { + return daoSession; + } + + public Activity getActivityWeakReference() { + return activityWeakReference.get(); + } + + public abstract int getLayoutId(); + + public abstract int getRecyclerViewId(); + + public abstract int getLoadingBarId(); + + public abstract int getRefreshLayoutId(); + + public abstract List getItems() throws Exception; + + public abstract void onRefresh() throws Exception; + + public abstract void onPostRefresh(int stringResult); + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(getLayoutId(), container, false); + recyclerViewLayout = view.findViewById(getRecyclerViewId()); + swipeRefreshLayout = view.findViewById(getRefreshLayoutId()); + setUpRefreshLayout(swipeRefreshLayout); + return view; + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + if (getActivity() != null && getView() != null) { + activityWeakReference = new WeakReference(getActivity()); + daoSession = ((WulkanowyApp) getActivity().getApplication()).getDaoSession(); + userId = getActivity().getSharedPreferences("LoginData", Context.MODE_PRIVATE) + .getLong("userId", 0); + + if (itemList != null) + if (itemList.isEmpty()) { + flexibleAdapter = getFlexibleAdapter(itemList); + setAdapterOnRecyclerView(recyclerViewLayout); + if (getUserVisibleHint()) { + new DatabaseQueryTask(this).execute(); + } + } else { + setAdapterOnRecyclerView(recyclerViewLayout); + setLoadingBarInvisible(getView()); + } + } + } + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + if (isResumed() && isVisibleToUser && flexibleAdapter.getItemCount() == 0) { + new DatabaseQueryTask(this).execute(); + } + } + + @Override + public void onQuarryProcessFinish(@NonNull List resultItemList) { + itemList = resultItemList; + flexibleAdapter = getFlexibleAdapter(itemList); + setAdapterOnRecyclerView(recyclerViewLayout); + if (getView() != null) { + setLoadingBarInvisible(getView()); + } + } + + @Override + public void onRefreshProcessFinish(@Nullable List resultItemList, int stringEventId) { + if (resultItemList != null) { + itemList = resultItemList; + updateDataInRecyclerView(); + } + onPostRefresh(stringEventId); + getRefreshLayoutView().setRefreshing(false); + } + + @NonNull + protected FlexibleAdapter getFlexibleAdapter(@NonNull List itemList) { + return new FlexibleAdapter<>(itemList) + .setAutoCollapseOnExpand(true) + .setAutoScrollOnExpand(true) + .expandItemsAtStartUp(); + } + + @NonNull + private SwipeRefreshLayout.OnRefreshListener getDefaultRefreshListener() { + return new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + if (ConnectionUtilities.isOnline(getContext())) { + new RefreshTask(AbstractFragment.this).execute(); + } else { + Toast.makeText(getContext(), R.string.noInternet_text, Toast.LENGTH_SHORT).show(); + swipeRefreshLayout.setRefreshing(false); + } + } + }; + } + + private void updateDataInRecyclerView() { + flexibleAdapter.updateDataSet(itemList); + setAdapterOnRecyclerView(recyclerViewLayout); + } + + protected void setUpRefreshLayout(@NonNull SwipeRefreshLayout swipeRefreshLayout) { + swipeRefreshLayout.setColorSchemeResources(android.R.color.black); + swipeRefreshLayout.setOnRefreshListener(getDefaultRefreshListener()); + } + + protected final void setLoadingBarInvisible(@NonNull View mainView) { + mainView.findViewById(getLoadingBarId()).setVisibility(View.INVISIBLE); + } + + protected void setAdapterOnRecyclerView(@NonNull RecyclerView recyclerView) { + recyclerView.setLayoutManager(new SmoothScrollLinearLayoutManager(getActivityWeakReference())); + recyclerView.setAdapter(flexibleAdapter); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/AsyncResponse.java b/app/src/main/java/io/github/wulkanowy/ui/main/AsyncResponse.java new file mode 100644 index 00000000..dc95e5a2 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/AsyncResponse.java @@ -0,0 +1,14 @@ +package io.github.wulkanowy.ui.main; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.List; + +import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem; + +public interface AsyncResponse { + void onQuarryProcessFinish(@NonNull List resultItemList); + + void onRefreshProcessFinish(@Nullable List resultItemList, int stringErrorId); +} diff --git a/app/src/main/java/io/github/wulkanowy/activity/dashboard/DashboardActivity.java b/app/src/main/java/io/github/wulkanowy/ui/main/DashboardActivity.java similarity index 87% rename from app/src/main/java/io/github/wulkanowy/activity/dashboard/DashboardActivity.java rename to app/src/main/java/io/github/wulkanowy/ui/main/DashboardActivity.java index d1243e29..66015bbe 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/dashboard/DashboardActivity.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/DashboardActivity.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.activity.dashboard; +package io.github.wulkanowy.ui.main; import android.os.Bundle; import android.support.annotation.NonNull; @@ -9,10 +9,10 @@ import android.support.v7.app.AppCompatActivity; import android.view.MenuItem; import io.github.wulkanowy.R; -import io.github.wulkanowy.activity.dashboard.attendance.AttendanceFragment; -import io.github.wulkanowy.activity.dashboard.board.BoardFragment; -import io.github.wulkanowy.activity.dashboard.grades.GradesFragment; -import io.github.wulkanowy.activity.dashboard.lessonplan.LessonPlanFragment; +import io.github.wulkanowy.ui.main.attendance.AttendanceFragment; +import io.github.wulkanowy.ui.main.board.BoardFragment; +import io.github.wulkanowy.ui.main.grades.GradesFragment; +import io.github.wulkanowy.ui.main.timetable.TimetableFragment; public class DashboardActivity extends AppCompatActivity { @@ -24,7 +24,7 @@ public class DashboardActivity extends AppCompatActivity { private BoardFragment boardFragment = new BoardFragment(); - private LessonPlanFragment lessonPlanFragment = new LessonPlanFragment(); + private TimetableFragment timetableFragment = new TimetableFragment(); private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener = new BottomNavigationView.OnNavigationItemSelectedListener() { @@ -44,7 +44,7 @@ public class DashboardActivity extends AppCompatActivity { case R.id.navigation_lessonplan: setTitle(R.string.lessonplan_text); - currentFragment = lessonPlanFragment; + currentFragment = timetableFragment; break; case R.id.navigation_dashboard: @@ -74,7 +74,7 @@ public class DashboardActivity extends AppCompatActivity { setTitle(savedInstanceState.getString("activityTitle")); } else { currentFragment = gradesFragment; - setTitle(R.string.dashboard_text); + setTitle(R.string.grades_text); } int cardID = getIntent().getIntExtra("cardID", 0); diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/DatabaseQueryTask.java b/app/src/main/java/io/github/wulkanowy/ui/main/DatabaseQueryTask.java new file mode 100644 index 00000000..7ce4b4fb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/DatabaseQueryTask.java @@ -0,0 +1,30 @@ +package io.github.wulkanowy.ui.main; + +import android.os.AsyncTask; + +import java.util.List; + +public class DatabaseQueryTask extends AsyncTask> { + + private AbstractFragment abstractFragment; + + public DatabaseQueryTask(AbstractFragment abstractFragment) { + this.abstractFragment = abstractFragment; + } + + @Override + protected List doInBackground(Void... voids) { + try { + return abstractFragment.getItems(); + } catch (Exception e) { + return null; + } + } + + @SuppressWarnings("unchecked") + @Override + protected void onPostExecute(List objects) { + super.onPostExecute(objects); + abstractFragment.onQuarryProcessFinish(objects); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/RefreshTask.java b/app/src/main/java/io/github/wulkanowy/ui/main/RefreshTask.java new file mode 100644 index 00000000..46e661dd --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/RefreshTask.java @@ -0,0 +1,52 @@ +package io.github.wulkanowy.ui.main; + +import android.os.AsyncTask; +import android.util.Log; + +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.util.List; + +import io.github.wulkanowy.R; + +public class RefreshTask extends AsyncTask> { + + public static final String DEBUG_TAG = "RefreshTask"; + + private int stringEventId = 0; + + private AbstractFragment abstractFragment; + + public RefreshTask(AbstractFragment abstractFragment) { + this.abstractFragment = abstractFragment; + } + + @Override + protected List doInBackground(Void... voids) { + try { + abstractFragment.onRefresh(); + return abstractFragment.getItems(); + } catch (UnknownHostException e) { + stringEventId = R.string.noInternet_text; + Log.i(DEBUG_TAG, "Synchronization is failed because occur problem with internet", + new IOException()); + return null; + } catch (SocketTimeoutException e) { + stringEventId = R.string.generic_timeout_error; + Log.i(DEBUG_TAG, "Too long wait for connection with internet", e); + return null; + } catch (Exception e) { + stringEventId = R.string.refresh_error_text; + Log.e(DEBUG_TAG, "There was a synchronization problem", e); + return null; + } + } + + @SuppressWarnings("unchecked") + @Override + protected void onPostExecute(List objects) { + super.onPostExecute(objects); + abstractFragment.onRefreshProcessFinish(objects, stringEventId); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/activity/dashboard/attendance/AttendanceFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.java similarity index 90% rename from app/src/main/java/io/github/wulkanowy/activity/dashboard/attendance/AttendanceFragment.java rename to app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.java index f410e75c..0da2e99f 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/dashboard/attendance/AttendanceFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.activity.dashboard.attendance; +package io.github.wulkanowy.ui.main.attendance; import android.os.Bundle; import android.support.annotation.NonNull; diff --git a/app/src/main/java/io/github/wulkanowy/activity/dashboard/board/BoardFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/board/BoardFragment.java similarity index 90% rename from app/src/main/java/io/github/wulkanowy/activity/dashboard/board/BoardFragment.java rename to app/src/main/java/io/github/wulkanowy/ui/main/board/BoardFragment.java index bc808145..f6b12c68 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/dashboard/board/BoardFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/board/BoardFragment.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.activity.dashboard.board; +package io.github.wulkanowy.ui.main.board; import android.os.Bundle; import android.support.annotation.NonNull; diff --git a/app/src/main/java/io/github/wulkanowy/activity/dashboard/grades/GradesAdapter.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesAdapter.java similarity index 76% rename from app/src/main/java/io/github/wulkanowy/activity/dashboard/grades/GradesAdapter.java rename to app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesAdapter.java index 00586211..a50c1527 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/dashboard/grades/GradesAdapter.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesAdapter.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.activity.dashboard.grades; +package io.github.wulkanowy.ui.main.grades; import android.app.Activity; @@ -14,6 +14,7 @@ import com.thoughtbot.expandablerecyclerview.models.ExpandableGroup; import com.thoughtbot.expandablerecyclerview.viewholders.ChildViewHolder; import com.thoughtbot.expandablerecyclerview.viewholders.GroupViewHolder; +import java.lang.ref.WeakReference; import java.util.List; import io.github.wulkanowy.R; @@ -22,9 +23,9 @@ import io.github.wulkanowy.utilities.AverageCalculator; public class GradesAdapter extends ExpandableRecyclerViewAdapter { - private Activity activity; + private static int numberOfNotReadGrade; - private int numberOfNotReadGrade; + private Activity activity; public GradesAdapter(List groups, Activity activity) { super(groups); @@ -34,13 +35,13 @@ public class GradesAdapter extends ExpandableRecyclerViewAdapter activity; private TextView subjectName; @@ -63,16 +66,17 @@ public class GradesAdapter extends ExpandableRecyclerViewAdapter(activity); + subjectName = itemView.findViewById(R.id.subject_text); numberOfGrades = itemView.findViewById(R.id.subject_number_of_grades); subjectAlertNewGrades = itemView.findViewById(R.id.subject_new_grades_alert); averageGrades = itemView.findViewById(R.id.subject_grades_average); - - subjectAlertNewGrades.setVisibility(View.INVISIBLE); } + @SuppressWarnings("unchecked") public void bind(ExpandableGroup group) { int volumeGrades = group.getItemCount(); List gradeList = group.getItems(); @@ -83,20 +87,24 @@ public class GradesAdapter extends ExpandableRecyclerViewAdapter activity; private TextView gradeValue; @@ -110,9 +118,11 @@ public class GradesAdapter extends ExpandableRecyclerViewAdapter(activity); + gradeValue = itemView.findViewById(R.id.grade_text); descriptionGrade = itemView.findViewById(R.id.description_grade_text); dateGrade = itemView.findViewById(R.id.grade_date_text); @@ -147,16 +157,18 @@ public class GradesAdapter extends ExpandableRecyclerViewAdapter(); + + AccountDao accountDao = daoSession.getAccountDao(); + Account account = accountDao.load(userId); + + for (Subject subject : account.getSubjectList()) { + List gradeList = subject.getGradeList(); + if (!gradeList.isEmpty()) { + SubjectWithGrades subjectWithGrades = new SubjectWithGrades(subject.getName(), gradeList); + subjectWithGradesList.add(subjectWithGrades); + } + } + } + @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - DaoSession daoSession; - View view = inflater.inflate(R.layout.fragment_grades, container, false); view.findViewById(R.id.fragment_no_grades).setVisibility(View.GONE); if (getActivity() != null) { - daoSession = ((WulkanowyApp) getActivity().getApplication()).getDaoSession(); + DaoSession daoSession = ((WulkanowyApp) getActivity().getApplication()).getDaoSession(); userId = getActivity().getSharedPreferences("LoginData", Context.MODE_PRIVATE) .getLong("userId", 0); @@ -89,30 +115,6 @@ public class GradesFragment extends Fragment { }); } - private static void createExpList(View mainView, Activity activity) { - - RecyclerView recyclerView = mainView.findViewById(R.id.subject_grade_recycler); - recyclerView.setLayoutManager(new LinearLayoutManager(activity)); - GradesAdapter gradesAdapter = new GradesAdapter(subjectWithGradesList, activity); - recyclerView.setAdapter(gradesAdapter); - } - - private static void downloadGradesFormDatabase(DaoSession daoSession) { - - subjectWithGradesList = new ArrayList<>(); - - AccountDao accountDao = daoSession.getAccountDao(); - Account account = accountDao.load(userId); - - for (Subject subject : account.getSubjectList()) { - List gradeList = subject.getGradeList(); - if (!gradeList.isEmpty()) { - SubjectWithGrades subjectWithGrades = new SubjectWithGrades(subject.getName(), gradeList); - subjectWithGradesList.add(subjectWithGrades); - } - } - } - private static class GenerateListTask extends AsyncTask { private WeakReference mainView; diff --git a/app/src/main/java/io/github/wulkanowy/activity/dashboard/grades/SubjectWithGrades.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/SubjectWithGrades.java similarity index 84% rename from app/src/main/java/io/github/wulkanowy/activity/dashboard/grades/SubjectWithGrades.java rename to app/src/main/java/io/github/wulkanowy/ui/main/grades/SubjectWithGrades.java index 3938a3fd..38d630e4 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/dashboard/grades/SubjectWithGrades.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/SubjectWithGrades.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.activity.dashboard.grades; +package io.github.wulkanowy.ui.main.grades; import com.thoughtbot.expandablerecyclerview.models.ExpandableGroup; diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialogFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialogFragment.java new file mode 100644 index 00000000..54380b8f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialogFragment.java @@ -0,0 +1,100 @@ +package io.github.wulkanowy.ui.main.timetable; + +import android.app.Dialog; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.DialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import org.apache.commons.lang3.StringUtils; + +import io.github.wulkanowy.R; +import io.github.wulkanowy.dao.entities.Lesson; + +public class TimetableDialogFragment extends DialogFragment { + + private Lesson lesson; + + public TimetableDialogFragment() { + //empty constructor for fragment + } + + public static TimetableDialogFragment newInstance(Lesson lesson) { + return new TimetableDialogFragment().setLesson(lesson); + } + + private TimetableDialogFragment setLesson(Lesson lesson) { + this.lesson = lesson; + return this; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.timetable_dialog, container, false); + setRetainInstance(true); + + TextView lessonText = view.findViewById(R.id.timetable_dialog_lesson_value); + TextView teacherText = view.findViewById(R.id.timetable_dialog_teacher_value); + TextView groupText = view.findViewById(R.id.timetable_dialog_group_value); + TextView roomText = view.findViewById(R.id.timetable_dialog_room_value); + TextView timeText = view.findViewById(R.id.timetable_dialog_time_value); + TextView descriptionText = view.findViewById(R.id.timetable_dialog_description_value); + Button closeButton = view.findViewById(R.id.timetable_dialog_close); + + if (!lesson.getSubject().isEmpty()) { + lessonText.setText(lesson.getSubject()); + } + + if (!lesson.getTeacher().isEmpty()) { + teacherText.setText(lesson.getTeacher()); + } else { + teacherText.setVisibility(View.GONE); + view.findViewById(R.id.timetable_dialog_teacher).setVisibility(View.GONE); + } + + if (!lesson.getGroupName().isEmpty()) { + groupText.setText(lesson.getGroupName()); + } else { + groupText.setVisibility(View.GONE); + view.findViewById(R.id.timetable_dialog_group).setVisibility(View.GONE); + } + + if (!lesson.getRoom().isEmpty()) { + roomText.setText(lesson.getRoom()); + } + + if (!lesson.getEndTime().isEmpty() && !lesson.getStartTime().isEmpty()) { + timeText.setText(String.format("%1$s - %2$s", lesson.getStartTime(), lesson.getEndTime())); + } + + if (!lesson.getDescription().isEmpty()) { + descriptionText.setText(StringUtils.capitalize(lesson.getDescription())); + } else { + descriptionText.setVisibility(View.GONE); + view.findViewById(R.id.timetable_dialog_description).setVisibility(View.GONE); + } + + closeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + dismiss(); + } + }); + return view; + } + + @Override + public void onDestroyView() { + Dialog dialog = getDialog(); + if (dialog != null && getRetainInstance()) { + dialog.setDismissMessage(null); + } + super.onDestroyView(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.java new file mode 100644 index 00000000..3801e9ac --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.java @@ -0,0 +1,121 @@ +package io.github.wulkanowy.ui.main.timetable; + +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.view.ViewPager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeConstants; + +import java.util.ArrayList; +import java.util.List; + +import io.github.wulkanowy.R; +import io.github.wulkanowy.utilities.TimeUtilities; + +public class TimetableFragment extends Fragment { + + private final String DATE_PATTERN = "yyyy-MM-dd"; + + private List dateStringList = new ArrayList<>(); + + private TimetablePagerAdapter pagerAdapter; + + private ViewPager viewPager; + + private TabLayout tabLayout; + + public TimetableFragment() { + //empty constructor for fragment + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_timetable, container, false); + viewPager = view.findViewById(R.id.timetable_fragment_viewpager); + tabLayout = view.findViewById(R.id.timetable_fragment_tab); + new CreateTabTask(this).execute(); + return view; + } + + private TimetablePagerAdapter getPagerAdapter() { + TimetablePagerAdapter pagerAdapter = new TimetablePagerAdapter(getChildFragmentManager()); + for (String date : dateStringList) { + pagerAdapter.addFragment(TimetableFragmentTab.newInstance(date), date); + } + return pagerAdapter; + } + + private String getDateOfCurrentMonday() { + DateTime currentDate = new DateTime(); + + if (currentDate.getDayOfWeek() == DateTimeConstants.SATURDAY) { + currentDate = currentDate.plusDays(2); + } else if (currentDate.getDayOfWeek() == DateTimeConstants.SUNDAY) { + currentDate = currentDate.plusDays(1); + } else { + currentDate = currentDate.withDayOfWeek(DateTimeConstants.MONDAY); + } + return currentDate.toString(DATE_PATTERN); + } + + private void setAdapterOnViewPager() { + viewPager.setAdapter(pagerAdapter); + viewPager.setCurrentItem(dateStringList.indexOf(getDateOfCurrentMonday())); + } + + private void setDateStringList() { + if (dateStringList.isEmpty()) { + dateStringList = TimeUtilities.getMondaysFromCurrentSchoolYear(DATE_PATTERN); + } + } + + private void setViewPagerOnTabLayout() { + tabLayout.setupWithViewPager(viewPager); + } + + protected final void setLoadingBarInvisible() { + if (getView() != null) { + getView().findViewById(R.id.timetable_tab_progress_bar).setVisibility(View.GONE); + } + } + + private static class CreateTabTask extends AsyncTask { + + private TimetableFragment fragment; + + public CreateTabTask(TimetableFragment fragment) { + this.fragment = fragment; + } + + @Override + protected Void doInBackground(Void... voids) { + fragment.setDateStringList(); + fragment.pagerAdapter = fragment.getPagerAdapter(); + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + super.onPostExecute(aVoid); + fragment.setAdapterOnViewPager(); + fragment.setViewPagerOnTabLayout(); + fragment.setLoadingBarInvisible(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragmentTab.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragmentTab.java new file mode 100644 index 00000000..b77394b7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragmentTab.java @@ -0,0 +1,161 @@ +package io.github.wulkanowy.ui.main.timetable; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v7.widget.RecyclerView; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeComparator; +import org.joda.time.DateTimeConstants; +import org.joda.time.LocalDate; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +import java.util.ArrayList; +import java.util.List; + +import io.github.wulkanowy.R; +import io.github.wulkanowy.dao.entities.Day; +import io.github.wulkanowy.dao.entities.Lesson; +import io.github.wulkanowy.dao.entities.Week; +import io.github.wulkanowy.dao.entities.WeekDao; +import io.github.wulkanowy.services.VulcanSynchronization; +import io.github.wulkanowy.ui.main.AbstractFragment; + +public class TimetableFragmentTab extends AbstractFragment { + + private final String DATE_PATTERN = "yyyy-MM-dd"; + + private int positionToScroll; + + private String date; + + public static TimetableFragmentTab newInstance(String date) { + TimetableFragmentTab fragmentTab = new TimetableFragmentTab(); + + Bundle argument = new Bundle(); + argument.putString("date", date); + fragmentTab.setArguments(argument); + + return fragmentTab; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + date = getArguments().getString("date"); + } + } + + @Override + public int getLayoutId() { + return R.layout.fragment_timetable_tab; + } + + @Override + public int getRecyclerViewId() { + return R.id.timetable_recycler; + } + + @Override + public int getRefreshLayoutId() { + return R.id.timetable_refresh_layout; + } + + @Override + public int getLoadingBarId() { + return R.id.timetable_progress_bar; + } + + @NonNull + @Override + public List getItems() throws Exception { + Week week = getWeek(); + + if (week == null) { + onRefresh(); + return getItems(); + } + + List dayEntityList = week.getDayList(); + + List dayList = new ArrayList<>(); + + int iterator = -1; + + for (Day day : dayEntityList) { + List timetableSubItems = new ArrayList<>(); + + TimetableHeaderItem headerItem = new TimetableHeaderItem(day); + + for (Lesson lesson : day.getLessons()) { + TimetableSubItem subItem = new TimetableSubItem(headerItem, lesson, getFragmentManager()); + timetableSubItems.add(subItem); + } + + iterator++; + + boolean isExpanded = getExpanded(day.getDate()); + + if (isExpanded) { + positionToScroll = iterator; + } + + headerItem.setExpanded(isExpanded); + headerItem.setSubItems(timetableSubItems); + dayList.add(headerItem); + } + return dayList; + } + + @Override + protected void setAdapterOnRecyclerView(@NonNull RecyclerView recyclerView) { + super.setAdapterOnRecyclerView(recyclerView); + recyclerView.scrollToPosition(positionToScroll); + } + + @Override + public void onRefresh() throws Exception { + VulcanSynchronization synchronization = new VulcanSynchronization(); + synchronization.loginCurrentUser(getContext(), getDaoSession()); + synchronization.syncTimetable(date); + } + + @Override + public void onPostRefresh(int stringResult) { + if (stringResult == 0) { + stringResult = R.string.timetable_refresh_success; + } + Snackbar.make(getActivityWeakReference().findViewById(R.id.fragment_container), + stringResult, Snackbar.LENGTH_SHORT).show(); + } + + private boolean getExpanded(String dayDate) { + DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(DATE_PATTERN); + DateTime dayTime = dateTimeFormatter.parseDateTime(dayDate); + + DateTime currentDate = new DateTime(); + + if (currentDate.getDayOfWeek() == DateTimeConstants.SATURDAY) { + currentDate = currentDate.plusDays(2); + } else if (currentDate.getDayOfWeek() == DateTimeConstants.SUNDAY) { + currentDate = currentDate.plusDays(1); + } + + return DateTimeComparator.getDateOnlyInstance().compare(currentDate, dayTime) == 0; + } + + private Week getWeek() { + if (date == null) { + LocalDate currentMonday = new LocalDate().withDayOfWeek(DateTimeConstants.MONDAY); + date = currentMonday.toString(DATE_PATTERN); + } + return getDaoSession().getWeekDao().queryBuilder() + .where(WeekDao.Properties.StartDayDate.eq(date), + WeekDao.Properties.UserId.eq(getUserId())) + .unique(); + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableHeaderItem.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableHeaderItem.java new file mode 100644 index 00000000..6355c1cb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableHeaderItem.java @@ -0,0 +1,86 @@ +package io.github.wulkanowy.ui.main.timetable; + +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import org.apache.commons.lang3.StringUtils; + +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem; +import eu.davidea.viewholders.ExpandableViewHolder; +import io.github.wulkanowy.R; +import io.github.wulkanowy.dao.entities.Day; + +public class TimetableHeaderItem + extends AbstractExpandableHeaderItem { + + private Day day; + + public TimetableHeaderItem(Day day) { + this.day = day; + } + + @Override + public boolean equals(Object o) { + return this == o; + } + + @Override + public int getLayoutRes() { + return R.layout.timetable_header; + } + + @Override + public HeaderViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new HeaderViewHolder(view, adapter); + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, int position, List payloads) { + holder.dayName.setText(StringUtils.capitalize(day.getDayName())); + holder.date.setText(day.getDate()); + + boolean alertActive = false; + + for (TimetableSubItem subItem : getSubItems()) { + if (subItem.getLesson().getIsMovedOrCanceled() || + subItem.getLesson().getIsNewMovedInOrChanged()) { + alertActive = true; + } + } + + if (alertActive) { + holder.alert.setVisibility(View.VISIBLE); + } else { + holder.alert.setVisibility(View.GONE); + } + } + + public static class HeaderViewHolder extends ExpandableViewHolder { + + @BindView(R.id.timetable_header_dayName_text) + public TextView dayName; + + @BindView(R.id.timetable_header_date_text) + public TextView date; + + @BindView(R.id.timetable_header_alert_image) + public ImageView alert; + + public HeaderViewHolder(View view, FlexibleAdapter adapter) { + super(view, adapter); + view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + toggleExpansion(); + } + }); + ButterKnife.bind(this, view); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePagerAdapter.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePagerAdapter.java new file mode 100644 index 00000000..8e404417 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePagerAdapter.java @@ -0,0 +1,48 @@ +package io.github.wulkanowy.ui.main.timetable; + +import android.os.Parcelable; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentStatePagerAdapter; + +import java.util.ArrayList; +import java.util.List; + +public class TimetablePagerAdapter extends FragmentStatePagerAdapter { + + private List fragmentList = new ArrayList<>(); + + private List titleList = new ArrayList<>(); + + public TimetablePagerAdapter(FragmentManager fm) { + super(fm); + } + + public void addFragment(Fragment fragment, String title) { + fragmentList.add(fragment); + titleList.add(title); + } + + @Override + public Fragment getItem(int position) { + return fragmentList.get(position); + } + + + @Override + public int getCount() { + return fragmentList.size(); + } + + @Nullable + @Override + public CharSequence getPageTitle(int position) { + return titleList.get(position); + } + + @Override + public void restoreState(Parcelable state, ClassLoader loader) { + // do nothing + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableSubItem.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableSubItem.java new file mode 100644 index 00000000..cdbf9942 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableSubItem.java @@ -0,0 +1,112 @@ +package io.github.wulkanowy.ui.main.timetable; + +import android.graphics.Paint; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentManager; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.items.AbstractSectionableItem; +import eu.davidea.viewholders.FlexibleViewHolder; +import io.github.wulkanowy.R; +import io.github.wulkanowy.dao.entities.Lesson; + +public class TimetableSubItem extends AbstractSectionableItem { + + private Lesson lesson; + + private FragmentManager fragmentManager; + + public TimetableSubItem(TimetableHeaderItem header, Lesson lesson, FragmentManager fragmentManager) { + super(header); + this.lesson = lesson; + this.fragmentManager = fragmentManager; + } + + public Lesson getLesson() { + return lesson; + } + + @Override + public boolean equals(Object o) { + return this == o; + } + + @Override + public int getLayoutRes() { + return R.layout.timetable_subitem; + } + + @Override + public SubItemViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new SubItemViewHolder(view, adapter); + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, SubItemViewHolder holder, int position, List payloads) { + holder.lessonName.setText(lesson.getSubject()); + holder.lessonTime.setText(String.format("%1$s - %2$s", lesson.getStartTime(), lesson.getEndTime())); + holder.numberOfLesson.setText(lesson.getNumber()); + holder.room.setText(lesson.getRoom()); + + holder.setDialog(lesson, fragmentManager); + + if (lesson.getIsMovedOrCanceled() || lesson.getIsNewMovedInOrChanged()) { + holder.change.setVisibility(View.VISIBLE); + } else { + holder.change.setVisibility(View.GONE); + } + + if (lesson.getIsMovedOrCanceled()) { + holder.lessonName.setPaintFlags(holder.lessonName.getPaintFlags() + | Paint.STRIKE_THRU_TEXT_FLAG); + } else { + holder.lessonName.setPaintFlags(holder.lessonName.getPaintFlags() + & (~Paint.STRIKE_THRU_TEXT_FLAG)); + } + + if (!lesson.getRoom().isEmpty()) { + holder.room.setText(holder.getContentView().getContext().getString(R.string.timetable_subitem_room, lesson.getRoom())); + } + } + + public static class SubItemViewHolder extends FlexibleViewHolder { + + @BindView(R.id.timetable_subItem_lesson_text) + public TextView lessonName; + + @BindView(R.id.timetable_subItem_number_of_lesson) + public TextView numberOfLesson; + + @BindView(R.id.timetable_subItem_time) + public TextView lessonTime; + + @BindView(R.id.timetable_subItem_room) + public TextView room; + + @BindView(R.id.timetable_subItem_change_image) + public ImageView change; + + public SubItemViewHolder(View view, FlexibleAdapter adapter) { + super(view, adapter); + ButterKnife.bind(this, view); + } + + public void setDialog(final Lesson lesson, final FragmentManager fragmentManager) { + getContentView().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + TimetableDialogFragment dialogFragment = TimetableDialogFragment.newInstance(lesson); + dialogFragment.setStyle(DialogFragment.STYLE_NO_TITLE, 0); + dialogFragment.show(fragmentManager, lesson.toString()); + } + }); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/activity/splash/SplashActivity.java b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashActivity.java similarity index 79% rename from app/src/main/java/io/github/wulkanowy/activity/splash/SplashActivity.java rename to app/src/main/java/io/github/wulkanowy/ui/splash/SplashActivity.java index 12dbd60a..af8cd2f4 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/splash/SplashActivity.java +++ b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashActivity.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.activity.splash; +package io.github.wulkanowy.ui.splash; import android.content.Context; import android.content.Intent; @@ -9,9 +9,9 @@ import android.widget.TextView; import io.github.wulkanowy.BuildConfig; import io.github.wulkanowy.R; -import io.github.wulkanowy.activity.dashboard.DashboardActivity; -import io.github.wulkanowy.activity.login.LoginActivity; -import io.github.wulkanowy.services.jobs.GradeJob; +import io.github.wulkanowy.services.jobs.FullSyncJob; +import io.github.wulkanowy.ui.login.LoginActivity; +import io.github.wulkanowy.ui.main.DashboardActivity; public class SplashActivity extends AppCompatActivity { @@ -36,8 +36,7 @@ public class SplashActivity extends AppCompatActivity { Intent intent = new Intent(this, LoginActivity.class); startActivity(intent); } else { - GradeJob gradesSync = new GradeJob(); - gradesSync.scheduledJob(this); + new FullSyncJob().scheduledJob(getApplicationContext()); Intent intent = new Intent(this, DashboardActivity.class); startActivity(intent); diff --git a/app/src/main/java/io/github/wulkanowy/utilities/ConversionVulcanObject.java b/app/src/main/java/io/github/wulkanowy/utilities/ConversionVulcanObject.java index 342b9368..6cd456fb 100644 --- a/app/src/main/java/io/github/wulkanowy/utilities/ConversionVulcanObject.java +++ b/app/src/main/java/io/github/wulkanowy/utilities/ConversionVulcanObject.java @@ -4,8 +4,11 @@ package io.github.wulkanowy.utilities; import java.util.ArrayList; import java.util.List; +import io.github.wulkanowy.dao.entities.Day; import io.github.wulkanowy.dao.entities.Grade; +import io.github.wulkanowy.dao.entities.Lesson; import io.github.wulkanowy.dao.entities.Subject; +import io.github.wulkanowy.dao.entities.Week; public class ConversionVulcanObject { @@ -48,4 +51,52 @@ public class ConversionVulcanObject { } return gradeEntityList; } + + public static Week weekToWeekEntitie(io.github.wulkanowy.api.timetable.Week week) { + return new Week().setStartDayDate(week.getStartDayDate()); + } + + + public static List daysToDaysEntities(List dayList) { + + List dayEntityList = new ArrayList<>(); + + for (io.github.wulkanowy.api.timetable.Day day : dayList) { + Day dayEntity = new Day() + .setDate(day.getDate()) + .setDayName(day.getDayName()) + .setFreeDay(day.isFreeDay()) + .setFreeDayName(day.getFreeDayName()); + + dayEntityList.add(dayEntity); + } + return dayEntityList; + } + + public static List lessonsToLessonsEntities(List lessonList) { + + List lessonEntityList = new ArrayList<>(); + + for (io.github.wulkanowy.api.timetable.Lesson lesson : lessonList) { + Lesson lessonEntity = new Lesson() + .setNumber(lesson.getNumber()) + .setSubject(lesson.getSubject()) + .setTeacher(lesson.getTeacher()) + .setRoom(lesson.getRoom()) + .setDescription(lesson.getDescription()) + .setGroupName(lesson.getGroupName()) + .setStartTime(lesson.getStartTime()) + .setEndTime(lesson.getEndTime()) + .setDate(lesson.getDate()) + .setEmpty(lesson.isEmpty()) + .setDivisionIntoGroups(lesson.isDivisionIntoGroups()) + .setPlanning(lesson.isPlanning()) + .setRealized(lesson.isRealized()) + .setMovedOrCanceled(lesson.isMovedOrCanceled()) + .setNewMovedInOrChanged(lesson.isNewMovedInOrChanged()); + + lessonEntityList.add(lessonEntity); + } + return lessonEntityList; + } } diff --git a/app/src/main/java/io/github/wulkanowy/utilities/DateHelper.java b/app/src/main/java/io/github/wulkanowy/utilities/DateHelper.java deleted file mode 100644 index 52c4c22e..00000000 --- a/app/src/main/java/io/github/wulkanowy/utilities/DateHelper.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.wulkanowy.utilities; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; - -public class DateHelper { - - private static final long TICKS_AT_EPOCH = 621355968000000000L; - - private static final long TICKS_PER_MILLISECOND = 10000; - - private DateHelper() { - throw new IllegalStateException("Utility class"); - } - - public static long getTicks(Date date) { - Calendar calendar = Calendar.getInstance(); - calendar.setTime(date); - - return (calendar.getTimeInMillis() * TICKS_PER_MILLISECOND) + TICKS_AT_EPOCH; - } - - public static long getTics(String dateString) throws ParseException { - return getTics(dateString, "dd.MM.yyyy"); - } - - public static long getTics(String dateString, String dateFormat) throws ParseException { - SimpleDateFormat format = new SimpleDateFormat(dateFormat, Locale.ROOT); - format.setTimeZone(TimeZone.getTimeZone("UTC")); - Date dateObject = format.parse(dateString); - - return getTicks(dateObject); - } - - public static Date getDate(long ticks) { - return new Date((ticks - TICKS_AT_EPOCH) / TICKS_PER_MILLISECOND); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/utilities/RootUtilities.java b/app/src/main/java/io/github/wulkanowy/utilities/RootUtilities.java index 7bdf162f..b7320c14 100644 --- a/app/src/main/java/io/github/wulkanowy/utilities/RootUtilities.java +++ b/app/src/main/java/io/github/wulkanowy/utilities/RootUtilities.java @@ -21,7 +21,7 @@ public class RootUtilities { if (file.exists()) { return true; } - } catch (Exception e1) { + } catch (Exception e) { // ignore } return canExecuteCommand("/system/xbin/which su") diff --git a/app/src/main/java/io/github/wulkanowy/utilities/TimeUtilities.java b/app/src/main/java/io/github/wulkanowy/utilities/TimeUtilities.java new file mode 100644 index 00000000..1f308652 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utilities/TimeUtilities.java @@ -0,0 +1,74 @@ +package io.github.wulkanowy.utilities; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeConstants; +import org.joda.time.LocalDate; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; + +public class TimeUtilities { + + private static final long TICKS_AT_EPOCH = 621355968000000000L; + + private static final long TICKS_PER_MILLISECOND = 10000; + + private TimeUtilities() { + throw new IllegalStateException("Utility class"); + } + + public static long getNetTicks(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + + return (calendar.getTimeInMillis() * TICKS_PER_MILLISECOND) + TICKS_AT_EPOCH; + } + + public static long getNetTicks(String dateString) throws ParseException { + return getNetTicks(dateString, "dd.MM.yyyy"); + } + + public static long getNetTicks(String dateString, String dateFormat) throws ParseException { + SimpleDateFormat format = new SimpleDateFormat(dateFormat, Locale.ROOT); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + Date dateObject = format.parse(dateString); + + return getNetTicks(dateObject); + } + + public static Date getDate(long netTicks) { + return new Date((netTicks - TICKS_AT_EPOCH) / TICKS_PER_MILLISECOND); + } + + public static List getMondaysFromCurrentSchoolYear(String dateFormat) { + LocalDate startDate = new LocalDate(getCurrentSchoolYear(), 9, 1); + LocalDate endDate = new LocalDate(getCurrentSchoolYear() + 1, 8, 31); + + List dateList = new ArrayList<>(); + + LocalDate thisMonday = startDate.withDayOfWeek(DateTimeConstants.MONDAY); + + if (startDate.isAfter(thisMonday)) { + startDate = thisMonday.plusWeeks(1); + } else { + startDate = thisMonday; + } + + while (startDate.isBefore(endDate)) { + dateList.add(startDate.toString(dateFormat)); + startDate = startDate.plusWeeks(1); + } + return dateList; + } + + public static int getCurrentSchoolYear() { + DateTime dateTime = new DateTime(); + return dateTime.getMonthOfYear() <= 8 ? dateTime.getYear() - 1 : dateTime.getYear(); + } +} diff --git a/app/src/main/res/drawable/ic_generic_exclamation.xml b/app/src/main/res/drawable/ic_generic_exclamation.xml new file mode 100644 index 00000000..c7159e31 --- /dev/null +++ b/app/src/main/res/drawable/ic_generic_exclamation.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_timetable_swap.xml b/app/src/main/res/drawable/ic_timetable_swap.xml new file mode 100644 index 00000000..81455485 --- /dev/null +++ b/app/src/main/res/drawable/ic_timetable_swap.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_dashboard.xml b/app/src/main/res/layout/activity_dashboard.xml index a5988d03..0bb788bd 100644 --- a/app/src/main/res/layout/activity_dashboard.xml +++ b/app/src/main/res/layout/activity_dashboard.xml @@ -7,7 +7,7 @@ android:layout_height="match_parent" android:orientation="vertical" android:weightSum="1" - tools:context="io.github.wulkanowy.activity.dashboard.DashboardActivity"> + tools:context="io.github.wulkanowy.ui.main.DashboardActivity"> @@ -77,7 +77,6 @@ android:hint="@string/prompt_email" android:inputType="textEmailAddress" android:maxLines="1" - android:importantForAutofill="noExcludeDescendants" /> @@ -98,7 +97,6 @@ android:imeActionLabel="@string/action_sign_in" android:imeOptions="actionDone" android:fontFamily="sans-serif" - android:importantForAutofill="noExcludeDescendants" /> diff --git a/app/src/main/res/layout/fragment_attendance.xml b/app/src/main/res/layout/fragment_attendance.xml index 3a4c840a..dc270277 100644 --- a/app/src/main/res/layout/fragment_attendance.xml +++ b/app/src/main/res/layout/fragment_attendance.xml @@ -3,7 +3,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" - tools:context="io.github.wulkanowy.activity.dashboard.attendance.AttendanceFragment"> + tools:context="io.github.wulkanowy.ui.main.attendance.AttendanceFragment"> diff --git a/app/src/main/res/layout/fragment_board.xml b/app/src/main/res/layout/fragment_board.xml index 9fda7745..bf8944ac 100644 --- a/app/src/main/res/layout/fragment_board.xml +++ b/app/src/main/res/layout/fragment_board.xml @@ -3,7 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context="io.github.wulkanowy.activity.dashboard.board.BoardFragment"> + tools:context="io.github.wulkanowy.ui.main.board.BoardFragment"> diff --git a/app/src/main/res/layout/fragment_grades.xml b/app/src/main/res/layout/fragment_grades.xml index 74e089fd..5db025df 100644 --- a/app/src/main/res/layout/fragment_grades.xml +++ b/app/src/main/res/layout/fragment_grades.xml @@ -4,7 +4,7 @@ android:id="@+id/coordinator_grade" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context="io.github.wulkanowy.activity.dashboard.grades.GradesFragment"> + tools:context="io.github.wulkanowy.ui.main.grades.GradesFragment"> - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_timetable.xml b/app/src/main/res/layout/fragment_timetable.xml new file mode 100644 index 00000000..c23956c6 --- /dev/null +++ b/app/src/main/res/layout/fragment_timetable.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_timetable_tab.xml b/app/src/main/res/layout/fragment_timetable_tab.xml new file mode 100644 index 00000000..04da4040 --- /dev/null +++ b/app/src/main/res/layout/fragment_timetable_tab.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/grade_item.xml b/app/src/main/res/layout/grade_item.xml index bdaae6c3..152a7a75 100644 --- a/app/src/main/res/layout/grade_item.xml +++ b/app/src/main/res/layout/grade_item.xml @@ -1,6 +1,7 @@ + android:textSize="15sp" + tool:ignore="all"/> + app:srcCompat="@drawable/subject_alert_circle" + tool:ignore="contentDescription"/> \ No newline at end of file diff --git a/app/src/main/res/layout/grades_dialog.xml b/app/src/main/res/layout/grades_dialog.xml index 4b610000..a8e4ad53 100644 --- a/app/src/main/res/layout/grades_dialog.xml +++ b/app/src/main/res/layout/grades_dialog.xml @@ -8,7 +8,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="450dp" - android:minWidth="325dp" + android:minWidth="300dp" android:orientation="vertical"> @@ -176,7 +176,7 @@ android:layout_alignParentRight="true" android:background="?attr/selectableItemBackground" android:focusable="true" - android:text="@string/dialog_close" + android:text="@string/generic_dialog_close" android:textAllCaps="true" android:textSize="15sp" /> diff --git a/app/src/main/res/layout/subject_item.xml b/app/src/main/res/layout/subject_item.xml index 5ba243e5..9b47fd81 100644 --- a/app/src/main/res/layout/subject_item.xml +++ b/app/src/main/res/layout/subject_item.xml @@ -2,6 +2,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" + xmlns:tool="http://schemas.android.com/tools" android:background="@drawable/subject_border_1px" android:foreground="?attr/selectableItemBackgroundBorderless" android:padding="15dp"> @@ -48,5 +49,6 @@ android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:layout_marginTop="10dp" - app:srcCompat="@drawable/subject_alert_circle" /> + app:srcCompat="@drawable/subject_alert_circle" + tool:ignore="contentDescription"/> \ No newline at end of file diff --git a/app/src/main/res/layout/timetable_dialog.xml b/app/src/main/res/layout/timetable_dialog.xml new file mode 100644 index 00000000..017004ec --- /dev/null +++ b/app/src/main/res/layout/timetable_dialog.xml @@ -0,0 +1,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +