diff --git a/app/build.gradle b/app/build.gradle
index 73121d5a..24c91a3f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -166,6 +166,10 @@ dependencies {
implementation "androidx.work:work-runtime-ktx:${versions.work}"
implementation 'com.hypertrack:hyperlog:0.0.10'
+
+ implementation 'com.github.kuba2k2:RecyclerTabLayout:700f980584'
+
+ implementation 'com.linkedin.android.tachyon:tachyon:1.0.2'
}
repositories {
mavenCentral()
diff --git a/app/sampledata/check/ic_check.xml b/app/sampledata/check/ic_check.xml
new file mode 100644
index 00000000..f621023c
--- /dev/null
+++ b/app/sampledata/check/ic_check.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt
index 7ea38470..bb471068 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt
@@ -6,8 +6,13 @@ import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
+import android.text.*
+import android.text.style.ForegroundColorSpan
+import android.text.style.StrikethroughSpan
import android.util.LongSparseArray
import android.util.SparseArray
+import android.widget.TextView
+import androidx.annotation.StringRes
import androidx.core.app.ActivityCompat
import androidx.core.util.forEach
import com.google.gson.JsonArray
@@ -326,4 +331,64 @@ fun String.crc32(): Long {
return crc.value
}
-fun Long.formatDate(format: String = "yyyy-MM-dd HH:mm:ss"): String = SimpleDateFormat(format).format(this)
\ No newline at end of file
+fun Long.formatDate(format: String = "yyyy-MM-dd HH:mm:ss"): String = SimpleDateFormat(format).format(this)
+
+fun CharSequence?.asColoredSpannable(colorInt: Int): Spannable {
+ val spannable = SpannableString(this)
+ spannable.setSpan(ForegroundColorSpan(colorInt), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ return spannable
+}
+fun CharSequence?.asStrikethroughSpannable(): Spannable {
+ val spannable = SpannableString(this)
+ spannable.setSpan(StrikethroughSpan(), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ return spannable
+}
+
+/**
+ * Returns a new read-only list only of those given elements, that are not empty.
+ * Applies for CharSequence and descendants.
+ */
+fun listOfNotEmpty(vararg elements: T): List = elements.filterNot { it.isEmpty() }
+
+fun List.concat(delimiter: String? = null): CharSequence {
+ if (this.isEmpty()) {
+ return ""
+ }
+
+ if (this.size == 1) {
+ return this[0]
+ }
+
+ var spanned = false
+ for (piece in this) {
+ if (piece is Spanned) {
+ spanned = true
+ break
+ }
+ }
+
+ var first = true
+ if (spanned) {
+ val ssb = SpannableStringBuilder()
+ for (piece in this) {
+ if (!first && delimiter != null)
+ ssb.append(delimiter)
+ first = false
+ ssb.append(piece)
+ }
+ return SpannedString(ssb)
+ } else {
+ val sb = StringBuilder()
+ for (piece in this) {
+ if (!first && delimiter != null)
+ sb.append(delimiter)
+ first = false
+ sb.append(piece)
+ }
+ return sb.toString()
+ }
+}
+
+fun TextView.setText(@StringRes resid: Int, vararg formatArgs: Any) {
+ text = context.getString(resid, formatArgs)
+}
\ No newline at end of file
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt
index a2dbdeb0..e84f210f 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt
@@ -62,7 +62,7 @@ import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment
import pl.szczodrzynski.edziennik.ui.modules.notifications.NotificationsFragment
import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsNewFragment
-import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
+import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment
import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.Utils
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiTimetable.kt
index ce938865..3f529a7c 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiTimetable.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/data/api/MobidziennikApiTimetable.kt
@@ -5,15 +5,77 @@
package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
-import pl.szczodrzynski.edziennik.data.db.modules.lessons.Lesson
-import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
+import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.fixName
import pl.szczodrzynski.edziennik.singleOrNull
+import pl.szczodrzynski.edziennik.utils.models.Date
+import pl.szczodrzynski.edziennik.utils.models.Time
class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List) {
init {
- for (lessonStr in rows) {
+ val lessons = rows.filterNot { it.isEmpty() }.map { it.split("|") }
+
+ for (lesson in lessons) {
+ val date = Date.fromYmd(lesson[2])
+ val startTime = Time.fromYmdHm(lesson[3])
+ val endTime = Time.fromYmdHm(lesson[4])
+ val id = date.combineWith(startTime) / 1000L
+
+ val subjectId = data.subjectList.singleOrNull { it.longName == lesson[5] }?.id ?: -1
+ val teacherId = data.teacherList.singleOrNull { it.fullNameLastFirst == (lesson[7]+" "+lesson[6]).fixName() }?.id ?: -1
+ val teamId = data.teamList.singleOrNull { it.name == lesson[8]+lesson[9] }?.id ?: -1
+ val classroom = lesson[11]
+
+ Lesson(data.profileId, id).also {
+ when (lesson[1]) {
+ "plan_lekcji", "lekcja" -> {
+ it.type = Lesson.TYPE_NORMAL
+ it.date = date
+ it.startTime = startTime
+ it.endTime = endTime
+ it.subjectId = subjectId
+ it.teacherId = teacherId
+ it.teamId = teamId
+ it.classroom = classroom
+ }
+ "lekcja_odwolana" -> {
+ it.type = Lesson.TYPE_CANCELLED
+ it.date = date
+ it.startTime = startTime
+ it.endTime = endTime
+ it.oldSubjectId = subjectId
+ //it.oldTeacherId = teacherId
+ it.oldTeamId = teamId
+ //it.oldClassroom = classroom
+ }
+ "zastepstwo" -> {
+ it.type = Lesson.TYPE_CHANGE
+ it.date = date
+ it.startTime = startTime
+ it.endTime = endTime
+ it.subjectId = subjectId
+ it.teacherId = teacherId
+ it.teamId = teamId
+ it.classroom = classroom
+ }
+ }
+
+ if (it.type != Lesson.TYPE_NORMAL) {
+ data.metadataList.add(
+ Metadata(
+ data.profileId,
+ Metadata.TYPE_LESSON_CHANGE,
+ it.id,
+ data.profile?.empty ?: false,
+ data.profile?.empty ?: false,
+ System.currentTimeMillis()
+ ))
+ }
+ data.lessonNewList += it
+ }
+ }
+ /*for (lessonStr in rows) {
if (lessonStr.isNotEmpty()) {
val lesson = lessonStr.split("|")
@@ -76,9 +138,9 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List) {
if (originalLesson == null) {
// original lesson doesn't exist, save a new addition
// TODO
- /*if (!RegisterLessonChange.existsAddition(app.profile, registerLessonChange)) {
+ *//*if (!RegisterLessonChange.existsAddition(app.profile, registerLessonChange)) {
app.profile.timetable.addLessonAddition(registerLessonChange);
- }*/
+ }*//*
} else {
// original lesson exists, so we need to compare them
if (!lessonChange.matches(originalLesson)) {
@@ -108,6 +170,6 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List) {
}
}
}
- }
+ }*/
}
}
\ No newline at end of file
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/Data.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/Data.kt
index 394aaeaf..bac36618 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/Data.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/Data.kt
@@ -136,6 +136,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
var lessonsToRemove: DataRemoveModel? = null
val lessonList = mutableListOf()
val lessonChangeList = mutableListOf()
+ val lessonNewList = mutableListOf()
var gradesToRemove: DataRemoveModel? = null
val gradeList = mutableListOf()
@@ -195,6 +196,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
lessonList.clear()
lessonChangeList.clear()
+ lessonNewList.clear()
gradeList.clear()
noticeList.clear()
attendanceList.clear()
@@ -282,6 +284,10 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
}
if (lessonChangeList.isNotEmpty())
db.lessonChangeDao().addAll(lessonChangeList)
+ if (lessonNewList.isNotEmpty()) {
+ db.timetableDao().clear(profile.id)
+ db.timetableDao() += lessonNewList
+ }
if (gradeList.isNotEmpty()) {
db.gradeDao().addAll(gradeList)
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.java
index 800dcc69..046c78e8 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.java
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.java
@@ -72,6 +72,7 @@ import pl.szczodrzynski.edziennik.data.db.modules.teachers.TeacherAbsenceTypeDao
import pl.szczodrzynski.edziennik.data.db.modules.teachers.TeacherDao;
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team;
import pl.szczodrzynski.edziennik.data.db.modules.teams.TeamDao;
+import pl.szczodrzynski.edziennik.data.db.modules.timetable.TimetableDao;
import pl.szczodrzynski.edziennik.utils.models.Date;
@Database(entities = {
@@ -103,7 +104,8 @@ import pl.szczodrzynski.edziennik.utils.models.Date;
Classroom.class,
NoticeType.class,
AttendanceType.class,
- Metadata.class}, version = 63)
+ pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson.class,
+ Metadata.class}, version = 64)
@TypeConverters({
ConverterTime.class,
ConverterDate.class,
@@ -141,6 +143,7 @@ public abstract class AppDb extends RoomDatabase {
public abstract ClassroomDao classroomDao();
public abstract NoticeTypeDao noticeTypeDao();
public abstract AttendanceTypeDao attendanceTypeDao();
+ public abstract TimetableDao timetableDao();
public abstract MetadataDao metadataDao();
private static volatile AppDb INSTANCE;
@@ -729,6 +732,37 @@ public abstract class AppDb extends RoomDatabase {
database.execSQL("ALTER TABLE profiles ADD COLUMN studentSchoolYear TEXT DEFAULT NULL");
}
};
+ private static final Migration MIGRATION_63_64 = new Migration(63, 64) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ //database.execSQL("ALTER TABLE lessons RENAME TO lessonsOld;");
+ database.execSQL("CREATE TABLE timetable (" +
+ "profileId INTEGER NOT NULL," +
+ "id INTEGER NOT NULL," +
+ "type INTEGER NOT NULL," +
+
+ "date TEXT DEFAULT NULL," +
+ "lessonNumber INTEGER DEFAULT NULL," +
+ "startTime TEXT DEFAULT NULL," +
+ "endTime TEXT DEFAULT NULL," +
+ "subjectId INTEGER DEFAULT NULL," +
+ "teacherId INTEGER DEFAULT NULL," +
+ "teamId INTEGER DEFAULT NULL," +
+ "classroom TEXT DEFAULT NULL," +
+
+ "oldDate TEXT DEFAULT NULL," +
+ "oldLessonNumber INTEGER DEFAULT NULL," +
+ "oldStartTime TEXT DEFAULT NULL," +
+ "oldEndTime TEXT DEFAULT NULL," +
+ "oldSubjectId INTEGER DEFAULT NULL," +
+ "oldTeacherId INTEGER DEFAULT NULL," +
+ "oldTeamId INTEGER DEFAULT NULL," +
+ "oldClassroom TEXT DEFAULT NULL," +
+ "PRIMARY KEY(id));");
+ database.execSQL("CREATE INDEX index_lessons_profileId_type_date ON timetable (profileId, type, date);");
+ database.execSQL("CREATE INDEX index_lessons_profileId_type_oldDate ON timetable (profileId, type, oldDate);");
+ }
+ };
public static AppDb getDatabase(final Context context) {
@@ -789,7 +823,8 @@ public abstract class AppDb extends RoomDatabase {
MIGRATION_59_60,
MIGRATION_60_61,
MIGRATION_61_62,
- MIGRATION_62_63
+ MIGRATION_62_63,
+ MIGRATION_63_64
)
.allowMainThreadQueries()
//.fallbackToDestructiveMigration()
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/Lesson.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/Lesson.kt
index 0feaaa6f..abc75820 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/Lesson.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/Lesson.kt
@@ -4,11 +4,18 @@
package pl.szczodrzynski.edziennik.data.db.modules.timetable
-import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.Index
+import androidx.room.PrimaryKey
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
-open class Lesson(val profileId: Int) {
+@Entity(tableName = "timetable",
+ indices = [
+ Index(value = ["profileId", "type", "date"]),
+ Index(value = ["profileId", "type", "oldDate"])
+ ])
+open class Lesson(val profileId: Int, @PrimaryKey val id: Long) {
companion object {
const val TYPE_NORMAL = 0
const val TYPE_CANCELLED = 1
@@ -17,15 +24,14 @@ open class Lesson(val profileId: Int) {
const val TYPE_SHIFTED_TARGET = 4 /* target lesson */
}
- @ColumnInfo(name = "lessonType")
var type: Int = TYPE_NORMAL
var date: Date? = null
var lessonNumber: Int? = null
var startTime: Time? = null
var endTime: Time? = null
- var teacherId: Long? = null
var subjectId: Long? = null
+ var teacherId: Long? = null
var teamId: Long? = null
var classroom: String? = null
@@ -33,8 +39,58 @@ open class Lesson(val profileId: Int) {
var oldLessonNumber: Int? = null
var oldStartTime: Time? = null
var oldEndTime: Time? = null
- var oldTeacherId: Long? = null
var oldSubjectId: Long? = null
+ var oldTeacherId: Long? = null
var oldTeamId: Long? = null
var oldClassroom: String? = null
-}
\ No newline at end of file
+
+ override fun toString(): String {
+ return "Lesson(profileId=$profileId, " +
+ "id=$id, " +
+ "type=$type, " +
+ "date=$date, " +
+ "lessonNumber=$lessonNumber, " +
+ "startTime=$startTime, " +
+ "endTime=$endTime, " +
+ "subjectId=$subjectId, " +
+ "teacherId=$teacherId, " +
+ "teamId=$teamId, " +
+ "classroom=$classroom, " +
+ "oldDate=$oldDate, " +
+ "oldLessonNumber=$oldLessonNumber, " +
+ "oldStartTime=$oldStartTime, " +
+ "oldEndTime=$oldEndTime, " +
+ "oldSubjectId=$oldSubjectId, " +
+ "oldTeacherId=$oldTeacherId, " +
+ "oldTeamId=$oldTeamId, " +
+ "oldClassroom=$oldClassroom)"
+ }
+}
+/*
+DROP TABLE lessons;
+DROP TABLE lessonChanges;
+CREATE TABLE lessons (
+ profileId INTEGER NOT NULL,
+ type INTEGER NOT NULL,
+
+ date TEXT DEFAULT NULL,
+ lessonNumber INTEGER DEFAULT NULL,
+ startTime TEXT DEFAULT NULL,
+ endTime TEXT DEFAULT NULL,
+ teacherId INTEGER DEFAULT NULL,
+ subjectId INTEGER DEFAULT NULL,
+ teamId INTEGER DEFAULT NULL,
+ classroom TEXT DEFAULT NULL,
+
+ oldDate TEXT DEFAULT NULL,
+ oldLessonNumber INTEGER DEFAULT NULL,
+ oldStartTime TEXT DEFAULT NULL,
+ oldEndTime TEXT DEFAULT NULL,
+ oldTeacherId INTEGER DEFAULT NULL,
+ oldSubjectId INTEGER DEFAULT NULL,
+ oldTeamId INTEGER DEFAULT NULL,
+ oldClassroom TEXT DEFAULT NULL,
+
+ PRIMARY KEY(profileId)
+);
+*/
\ No newline at end of file
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/LessonFull.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/LessonFull.kt
new file mode 100644
index 00000000..ee573ac2
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/LessonFull.kt
@@ -0,0 +1,63 @@
+package pl.szczodrzynski.edziennik.data.db.modules.timetable
+
+import pl.szczodrzynski.edziennik.utils.models.Date
+import pl.szczodrzynski.edziennik.utils.models.Time
+
+class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) {
+ var subjectName: String? = null
+ var teacherName: String? = null
+ var teamName: String? = null
+ var oldSubjectName: String? = null
+ var oldTeacherName: String? = null
+ var oldTeamName: String? = null
+
+ val displayDate: Date?
+ get() {
+ if (type == TYPE_SHIFTED_SOURCE)
+ return oldDate
+ return date ?: oldDate
+ }
+ val displayStartTime: Time?
+ get() {
+ if (type == TYPE_SHIFTED_SOURCE)
+ return oldStartTime
+ return startTime ?: oldStartTime
+ }
+ val displayEndTime: Time?
+ get() {
+ if (type == TYPE_SHIFTED_SOURCE)
+ return oldEndTime
+ return endTime ?: oldEndTime
+ }
+
+ val displaySubjectName: String?
+ get() {
+ if (type == TYPE_SHIFTED_SOURCE)
+ return oldSubjectName
+ return subjectName ?: oldSubjectName
+ }
+ val displayTeacherName: String?
+ get() {
+ if (type == TYPE_SHIFTED_SOURCE)
+ return oldTeacherName
+ return teacherName ?: oldTeacherName
+ }
+ val displayTeamName: String?
+ get() {
+ if (type == TYPE_SHIFTED_SOURCE)
+ return oldTeamName
+ return teamName ?: oldTeamName
+ }
+
+ val displayClassroom: String?
+ get() {
+ if (type == TYPE_SHIFTED_SOURCE)
+ return oldClassroom
+ return classroom ?: oldClassroom
+ }
+
+ // metadata
+ var seen: Boolean = false
+ var notified: Boolean = false
+ var addedDate: Long = 0
+}
\ No newline at end of file
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/TimetableDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/TimetableDao.kt
new file mode 100644
index 00000000..ee565eff
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/modules/timetable/TimetableDao.kt
@@ -0,0 +1,41 @@
+package pl.szczodrzynski.edziennik.data.db.modules.timetable
+
+import androidx.lifecycle.LiveData
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
+import pl.szczodrzynski.edziennik.utils.models.Date
+
+@Dao
+interface TimetableDao {
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ operator fun plusAssign(lessonList: List)
+
+ @Query("DELETE FROM timetable WHERE profileId = :profileId")
+ fun clear(profileId: Int)
+
+ @Query("""
+ SELECT
+ timetable.*,
+ subjects.subjectLongName AS subjectName,
+ teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName,
+ teams.teamName AS teamName,
+ oldS.subjectLongName AS oldSubjectName,
+ oldT.teacherName ||" "|| oldT.teacherSurname AS oldTeacherName,
+ oldG.teamName AS oldTeamName,
+ metadata.seen, metadata.notified, metadata.addedDate
+ FROM timetable
+ LEFT JOIN subjects USING(profileId, subjectId)
+ LEFT JOIN teachers USING(profileId, teacherId)
+ LEFT JOIN teams USING(profileId, teamId)
+ LEFT JOIN subjects AS oldS ON timetable.profileId = oldS.profileId AND timetable.oldSubjectId = oldS.subjectId
+ LEFT JOIN teachers AS oldT ON timetable.profileId = oldT.profileId AND timetable.oldTeacherId = oldT.teacherId
+ LEFT JOIN teams AS oldG ON timetable.profileId = oldG.profileId AND timetable.oldTeamId = oldG.teamId
+ LEFT JOIN metadata ON id = thingId AND thingType = ${Metadata.TYPE_LESSON_CHANGE} AND metadata.profileId = timetable.profileId
+ WHERE timetable.profileId = :profileId AND (type != 3 AND date = :date) OR (type = 3 AND oldDate = :date)
+ """)
+ fun getForDate(profileId: Int, date: Date) : LiveData>
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/v2/TimetableFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/v2/TimetableFragment.kt
new file mode 100644
index 00000000..b29a7e72
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/v2/TimetableFragment.kt
@@ -0,0 +1,99 @@
+package pl.szczodrzynski.edziennik.ui.modules.timetable.v2
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.viewpager.widget.ViewPager
+import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
+import pl.szczodrzynski.edziennik.App
+import pl.szczodrzynski.edziennik.MainActivity
+import pl.szczodrzynski.edziennik.R
+import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding
+import pl.szczodrzynski.edziennik.utils.Themes
+import pl.szczodrzynski.edziennik.utils.models.Date
+
+class TimetableFragment : Fragment() {
+ companion object {
+ private const val TAG = "TimetableFragment"
+ }
+
+ private lateinit var app: App
+ private lateinit var activity: MainActivity
+ private lateinit var b: FragmentTimetableV2Binding
+ private var fabShown = false
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ activity = (getActivity() as MainActivity?) ?: return null
+ if (context == null)
+ return null
+ app = activity.application as App
+ context!!.theme.applyStyle(Themes.appTheme, true)
+ if (app.profile == null)
+ return inflater.inflate(R.layout.fragment_loading, container, false)
+ // activity, context and profile is valid
+ b = FragmentTimetableV2Binding.inflate(inflater)
+ return b.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ // TODO check if app, activity, b can be null
+ if (app.profile == null || !isAdded)
+ return
+
+ val items = mutableListOf()
+
+ val monthDayCount = listOf(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
+
+ val today = Date.getToday().value
+ val yearStart = app.profile.dateSemester1Start?.clone() ?: return
+ val yearEnd = app.profile.dateYearEnd ?: return
+ while (yearStart.value <= yearEnd.value) {
+ items += yearStart.clone()
+ var maxDays = monthDayCount[yearStart.month-1]
+ if (yearStart.month == 2 && yearStart.isLeap)
+ maxDays++
+ yearStart.day++
+ if (yearStart.day > maxDays) {
+ yearStart.day = 1
+ yearStart.month++
+ }
+ if (yearStart.month > 12) {
+ yearStart.month = 1
+ yearStart.year++
+ }
+ }
+
+ val pagerAdapter = TimetablePagerAdapter(fragmentManager ?: return, items)
+ b.viewPager.adapter = pagerAdapter
+ b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
+ override fun onPageScrollStateChanged(state: Int) {
+
+ }
+
+ override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
+
+ }
+
+ override fun onPageSelected(position: Int) {
+ activity.navView.bottomBar.fabEnable = items[position].value != today
+ if (activity.navView.bottomBar.fabEnable && !fabShown) {
+ activity.gainAttentionFAB()
+ fabShown = true
+ }
+ }
+
+ })
+
+ b.tabLayout.setUpWithViewPager(b.viewPager)
+ b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, false)
+
+ //activity.navView.bottomBar.fabEnable = true
+ activity.navView.bottomBar.fabExtendedText = getString(R.string.timetable_today)
+ activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon.cmd_calendar_today
+ activity.navView.setFabOnClickListener(View.OnClickListener {
+ b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, true)
+ })
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/v2/TimetablePagerAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/v2/TimetablePagerAdapter.kt
new file mode 100644
index 00000000..84d7a004
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/v2/TimetablePagerAdapter.kt
@@ -0,0 +1,29 @@
+package pl.szczodrzynski.edziennik.ui.modules.timetable.v2
+
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentStatePagerAdapter
+import pl.szczodrzynski.edziennik.utils.models.Date
+
+class TimetablePagerAdapter(val fragmentManager: FragmentManager, val items: List) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
+ companion object {
+ private const val TAG = "TimetablePagerAdapter"
+ }
+
+ override fun getItem(position: Int): Fragment {
+ return pl.szczodrzynski.edziennik.ui.modules.timetable.v2.day.TimetableDayFragment(items[position])
+ /*return TimetableDayFragment().apply {
+ arguments = Bundle().also {
+ it.putLong("date", items[position].value.toLong())
+ }
+ }*/
+ }
+
+ override fun getCount(): Int {
+ return items.size
+ }
+
+ override fun getPageTitle(position: Int): CharSequence? {
+ return items[position].formattedStringShort
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/v2/day/TimetableDayFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/v2/day/TimetableDayFragment.kt
new file mode 100644
index 00000000..92044328
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/v2/day/TimetableDayFragment.kt
@@ -0,0 +1,185 @@
+package pl.szczodrzynski.edziennik.ui.modules.timetable.v2.day
+
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Observer
+import com.linkedin.android.tachyon.DayView
+import pl.szczodrzynski.edziennik.*
+import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
+import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull
+import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2DayBinding
+import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding
+import pl.szczodrzynski.edziennik.utils.models.Date
+import pl.szczodrzynski.navlib.getColorFromAttr
+import java.util.*
+
+class TimetableDayFragment(val date: Date) : Fragment() {
+ companion object {
+ private const val TAG = "TimetableDayFragment"
+ }
+
+ private lateinit var app: App
+ private lateinit var activity: MainActivity
+ private lateinit var b: FragmentTimetableV2DayBinding
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ activity = (getActivity() as MainActivity?) ?: return null
+ if (context == null)
+ return null
+ app = activity.application as App
+ b = FragmentTimetableV2DayBinding.inflate(inflater)
+ Log.d(TAG, "onCreateView, date=$date")
+ return b.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ // TODO check if app, activity, b can be null
+ if (app.profile == null || !isAdded)
+ return
+
+ Log.d(TAG, "onViewCreated, date=$date")
+ b.date.text = date.formattedString
+
+ // Inflate a label view for each hour the day view will display
+ val hourLabelViews = ArrayList()
+ for (i in b.day.startHour..b.day.endHour) {
+ val hourLabelView = layoutInflater.inflate(R.layout.timetable_hour_label, b.day, false) as TextView
+ hourLabelView.text = "$i:00"
+ hourLabelViews.add(hourLabelView)
+ }
+ b.day.setHourLabelViews(hourLabelViews)
+
+ app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer> { lessons ->
+ buildLessonViews(lessons)
+ })
+ }
+
+ private fun buildLessonViews(lessons: List) {
+ val eventViews = mutableListOf()
+ val eventTimeRanges = mutableListOf()
+
+ // Reclaim all of the existing event views so we can reuse them if needed, this process
+ // can be useful if your day view is hosted in a recycler view for example
+ val recycled = b.day.removeEventViews()
+ var remaining = recycled?.size ?: 0
+
+ val arrowRight = " → "
+ val bullet = " • "
+ val colorSecondary = getColorFromAttr(activity, android.R.attr.textColorSecondary)
+
+ for (lesson in lessons) {
+ val startTime = lesson.displayStartTime ?: continue
+ val endTime = lesson.displayEndTime ?: continue
+
+ // Try to recycle an existing event view if there are enough left, otherwise inflate
+ // a new one
+ val eventView = (if (remaining > 0) recycled?.get(--remaining) else layoutInflater.inflate(R.layout.timetable_lesson, b.day, false))
+ ?: continue
+ val lb = TimetableLessonBinding.bind(eventView)
+ eventViews += eventView
+
+ eventView.tag = lesson
+
+ eventView.setOnClickListener {
+ Log.d(TAG, "Clicked ${it.tag}")
+ }
+
+
+ val timeRange = "${startTime.stringHM} - ${endTime.stringHM}".asColoredSpannable(colorSecondary)
+
+ // teacher
+ val teacherInfo = if (lesson.teacherId != null && lesson.teacherId == lesson.oldTeacherId)
+ lesson.teacherName ?: "?"
+ else
+ mutableListOf().apply {
+ lesson.oldTeacherName?.let { add(it.asStrikethroughSpannable()) }
+ lesson.teacherName?.let { add(it) }
+ }.concat(arrowRight)
+
+ // team
+ val teamInfo = if (lesson.teamId != null && lesson.teamId == lesson.oldTeamId)
+ lesson.teamName ?: "?"
+ else
+ mutableListOf().apply {
+ lesson.oldTeamName?.let { add(it.asStrikethroughSpannable()) }
+ lesson.teamName?.let { add(it) }
+ }.concat(arrowRight)
+
+ // classroom
+ val classroomInfo = if (lesson.classroom != null && lesson.classroom == lesson.oldClassroom)
+ lesson.classroom ?: "?"
+ else
+ mutableListOf().apply {
+ lesson.oldClassroom?.let { add(it.asStrikethroughSpannable()) }
+ lesson.classroom?.let { add(it) }
+ }.concat(arrowRight)
+
+
+ lb.subjectName.text = lesson.displaySubjectName?.let { if (lesson.type == Lesson.TYPE_CANCELLED) it.asStrikethroughSpannable().asColoredSpannable(colorSecondary) else it }
+ lb.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet)
+ lb.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet)
+
+ //lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD)
+ when (lesson.type) {
+ Lesson.TYPE_NORMAL -> {
+ lb.annotation.visibility = View.GONE
+ }
+ Lesson.TYPE_CANCELLED -> {
+ lb.annotation.visibility = View.VISIBLE
+ lb.annotation.setText(R.string.timetable_lesson_cancelled)
+ lb.annotation.background.colorFilter = PorterDuffColorFilter(
+ getColorFromAttr(activity, R.attr.timetable_lesson_cancelled_color),
+ PorterDuff.Mode.SRC_ATOP
+ )
+ //lb.subjectName.typeface = Typeface.DEFAULT
+ }
+ Lesson.TYPE_CHANGE -> {
+ lb.annotation.visibility = View.VISIBLE
+ if (lesson.subjectId != lesson.oldSubjectId && lesson.teacherId != lesson.oldTeacherId) {
+ lb.annotation.setText(
+ R.string.timetable_lesson_change_format,
+ "${lesson.oldSubjectName ?: "?"}, ${lesson.oldTeacherName ?: "?"}"
+ )
+ }
+ else if (lesson.subjectId != lesson.oldSubjectId) {
+ lb.annotation.setText(
+ R.string.timetable_lesson_change_format,
+ lesson.oldSubjectName ?: "?"
+ )
+ }
+ else if (lesson.teacherId != lesson.oldTeacherId) {
+ lb.annotation.setText(
+ R.string.timetable_lesson_change_format,
+ lesson.oldTeacherName ?: "?"
+ )
+ }
+ else {
+ lb.annotation.setText(R.string.timetable_lesson_change)
+ }
+
+ lb.annotation.background.colorFilter = PorterDuffColorFilter(
+ getColorFromAttr(activity, R.attr.timetable_lesson_cancelled_color),
+ PorterDuff.Mode.SRC_ATOP
+ )
+ }
+ }
+
+
+ // The day view needs the event time ranges in the start minute/end minute format,
+ // so calculate those here
+ val startMinute = 60 * (lesson.displayStartTime?.hour ?: 0) + (lesson.displayStartTime?.minute ?: 0)
+ val endMinute = startMinute + 45
+ eventTimeRanges.add(DayView.EventTimeRange(startMinute, endMinute))
+ }
+
+ b.day.setEventViews(eventViews, eventTimeRanges)
+ b.dayScroll.scrollTo(0, b.day.firstEventTop)
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java
index 3b69cdc9..a4ab40fe 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java
@@ -182,6 +182,10 @@ public class Date implements Comparable {
}
}
+ public boolean isLeap() {
+ return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);
+ }
+
public static Date getToday()
{
Calendar cal = Calendar.getInstance();
diff --git a/app/src/main/res/drawable-v21/bg_rounded_ripple_4dp.xml b/app/src/main/res/drawable-v21/bg_rounded_ripple_4dp.xml
new file mode 100644
index 00000000..662d846f
--- /dev/null
+++ b/app/src/main/res/drawable-v21/bg_rounded_ripple_4dp.xml
@@ -0,0 +1,10 @@
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_rounded_ripple_4dp.xml b/app/src/main/res/drawable/bg_rounded_ripple_4dp.xml
new file mode 100644
index 00000000..ca5a7911
--- /dev/null
+++ b/app/src/main/res/drawable/bg_rounded_ripple_4dp.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_rounded_ripple_4dp_pressed.xml b/app/src/main/res/drawable/bg_rounded_ripple_4dp_pressed.xml
new file mode 100644
index 00000000..aadf2255
--- /dev/null
+++ b/app/src/main/res/drawable/bg_rounded_ripple_4dp_pressed.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/timetable_lesson_annotation.xml b/app/src/main/res/drawable/timetable_lesson_annotation.xml
new file mode 100644
index 00000000..b22dc045
--- /dev/null
+++ b/app/src/main/res/drawable/timetable_lesson_annotation.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/timetable_lesson_bg_dark.xml b/app/src/main/res/drawable/timetable_lesson_bg_dark.xml
new file mode 100644
index 00000000..718c9f4e
--- /dev/null
+++ b/app/src/main/res/drawable/timetable_lesson_bg_dark.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/timetable_lesson_bg_light.xml b/app/src/main/res/drawable/timetable_lesson_bg_light.xml
new file mode 100644
index 00000000..7a17b6cd
--- /dev/null
+++ b/app/src/main/res/drawable/timetable_lesson_bg_light.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_timetable_v2.xml b/app/src/main/res/layout/fragment_timetable_v2.xml
new file mode 100644
index 00000000..78893ddf
--- /dev/null
+++ b/app/src/main/res/layout/fragment_timetable_v2.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_timetable_v2_day.xml b/app/src/main/res/layout/fragment_timetable_v2_day.xml
new file mode 100644
index 00000000..9912a5ef
--- /dev/null
+++ b/app/src/main/res/layout/fragment_timetable_v2_day.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/timetable_hour_label.xml b/app/src/main/res/layout/timetable_hour_label.xml
new file mode 100644
index 00000000..4daa29c6
--- /dev/null
+++ b/app/src/main/res/layout/timetable_hour_label.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/timetable_lesson.xml b/app/src/main/res/layout/timetable_lesson.xml
new file mode 100644
index 00000000..4b857f8f
--- /dev/null
+++ b/app/src/main/res/layout/timetable_lesson.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index b7deaa68..972b8254 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -3,4 +3,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index dafa8099..dd006b4d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -985,4 +985,10 @@
Nie udało się otworzyć ustawień
Tworzenie powiadomień
Librus - logowanie
+ Dzisiaj
+ Lekcja odwołana
+ Zastępstwo
+ Zastępstwo: zamiast %s
+ Lekcja przeniesiona na godz. %s
+ Lekcja przeniesiona na %s, godz. %s
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 116c7a40..6e2b64de 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -1,4 +1,4 @@
-
+