forked from github/szkolny
[APIv2/UI] Add new Timetable module. Implement in Mobidziennik.
This commit is contained in:
parent
01ac26e67b
commit
1b75424604
@ -166,6 +166,10 @@ dependencies {
|
|||||||
implementation "androidx.work:work-runtime-ktx:${versions.work}"
|
implementation "androidx.work:work-runtime-ktx:${versions.work}"
|
||||||
|
|
||||||
implementation 'com.hypertrack:hyperlog:0.0.10'
|
implementation 'com.hypertrack:hyperlog:0.0.10'
|
||||||
|
|
||||||
|
implementation 'com.github.kuba2k2:RecyclerTabLayout:700f980584'
|
||||||
|
|
||||||
|
implementation 'com.linkedin.android.tachyon:tachyon:1.0.2'
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
9
app/sampledata/check/ic_check.xml
Normal file
9
app/sampledata/check/ic_check.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF4caf50"
|
||||||
|
android:pathData="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"/>
|
||||||
|
</vector>
|
@ -6,8 +6,13 @@ import android.content.Context
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.text.*
|
||||||
|
import android.text.style.ForegroundColorSpan
|
||||||
|
import android.text.style.StrikethroughSpan
|
||||||
import android.util.LongSparseArray
|
import android.util.LongSparseArray
|
||||||
import android.util.SparseArray
|
import android.util.SparseArray
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.util.forEach
|
import androidx.core.util.forEach
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
@ -326,4 +331,64 @@ fun String.crc32(): Long {
|
|||||||
return crc.value
|
return crc.value
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Long.formatDate(format: String = "yyyy-MM-dd HH:mm:ss"): String = SimpleDateFormat(format).format(this)
|
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 <T : CharSequence> listOfNotEmpty(vararg elements: T): List<T> = elements.filterNot { it.isEmpty() }
|
||||||
|
|
||||||
|
fun List<CharSequence>.concat(delimiter: String? = null): CharSequence {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.size == 1) {
|
||||||
|
return this[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
var spanned = false
|
||||||
|
for (piece in this) {
|
||||||
|
if (piece is Spanned) {
|
||||||
|
spanned = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var first = true
|
||||||
|
if (spanned) {
|
||||||
|
val ssb = SpannableStringBuilder()
|
||||||
|
for (piece in this) {
|
||||||
|
if (!first && delimiter != null)
|
||||||
|
ssb.append(delimiter)
|
||||||
|
first = false
|
||||||
|
ssb.append(piece)
|
||||||
|
}
|
||||||
|
return SpannedString(ssb)
|
||||||
|
} else {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
for (piece in this) {
|
||||||
|
if (!first && delimiter != null)
|
||||||
|
sb.append(delimiter)
|
||||||
|
first = false
|
||||||
|
sb.append(piece)
|
||||||
|
}
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun TextView.setText(@StringRes resid: Int, vararg formatArgs: Any) {
|
||||||
|
text = context.getString(resid, formatArgs)
|
||||||
|
}
|
@ -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.notifications.NotificationsFragment
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment
|
import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsNewFragment
|
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.SwipeRefreshLayoutNoTouch
|
||||||
import pl.szczodrzynski.edziennik.utils.Themes
|
import pl.szczodrzynski.edziennik.utils.Themes
|
||||||
import pl.szczodrzynski.edziennik.utils.Utils
|
import pl.szczodrzynski.edziennik.utils.Utils
|
||||||
|
@ -5,15 +5,77 @@
|
|||||||
package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api
|
package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api
|
||||||
|
|
||||||
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
|
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.metadata.Metadata
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
|
||||||
import pl.szczodrzynski.edziennik.fixName
|
import pl.szczodrzynski.edziennik.fixName
|
||||||
import pl.szczodrzynski.edziennik.singleOrNull
|
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<String>) {
|
class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
|
||||||
init {
|
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()) {
|
if (lessonStr.isNotEmpty()) {
|
||||||
val lesson = lessonStr.split("|")
|
val lesson = lessonStr.split("|")
|
||||||
|
|
||||||
@ -76,9 +138,9 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
|
|||||||
if (originalLesson == null) {
|
if (originalLesson == null) {
|
||||||
// original lesson doesn't exist, save a new addition
|
// original lesson doesn't exist, save a new addition
|
||||||
// TODO
|
// TODO
|
||||||
/*if (!RegisterLessonChange.existsAddition(app.profile, registerLessonChange)) {
|
*//*if (!RegisterLessonChange.existsAddition(app.profile, registerLessonChange)) {
|
||||||
app.profile.timetable.addLessonAddition(registerLessonChange);
|
app.profile.timetable.addLessonAddition(registerLessonChange);
|
||||||
}*/
|
}*//*
|
||||||
} else {
|
} else {
|
||||||
// original lesson exists, so we need to compare them
|
// original lesson exists, so we need to compare them
|
||||||
if (!lessonChange.matches(originalLesson)) {
|
if (!lessonChange.matches(originalLesson)) {
|
||||||
@ -108,6 +170,6 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -136,6 +136,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
|
|||||||
var lessonsToRemove: DataRemoveModel? = null
|
var lessonsToRemove: DataRemoveModel? = null
|
||||||
val lessonList = mutableListOf<Lesson>()
|
val lessonList = mutableListOf<Lesson>()
|
||||||
val lessonChangeList = mutableListOf<LessonChange>()
|
val lessonChangeList = mutableListOf<LessonChange>()
|
||||||
|
val lessonNewList = mutableListOf<pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson>()
|
||||||
|
|
||||||
var gradesToRemove: DataRemoveModel? = null
|
var gradesToRemove: DataRemoveModel? = null
|
||||||
val gradeList = mutableListOf<Grade>()
|
val gradeList = mutableListOf<Grade>()
|
||||||
@ -195,6 +196,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
|
|||||||
|
|
||||||
lessonList.clear()
|
lessonList.clear()
|
||||||
lessonChangeList.clear()
|
lessonChangeList.clear()
|
||||||
|
lessonNewList.clear()
|
||||||
gradeList.clear()
|
gradeList.clear()
|
||||||
noticeList.clear()
|
noticeList.clear()
|
||||||
attendanceList.clear()
|
attendanceList.clear()
|
||||||
@ -282,6 +284,10 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
|
|||||||
}
|
}
|
||||||
if (lessonChangeList.isNotEmpty())
|
if (lessonChangeList.isNotEmpty())
|
||||||
db.lessonChangeDao().addAll(lessonChangeList)
|
db.lessonChangeDao().addAll(lessonChangeList)
|
||||||
|
if (lessonNewList.isNotEmpty()) {
|
||||||
|
db.timetableDao().clear(profile.id)
|
||||||
|
db.timetableDao() += lessonNewList
|
||||||
|
}
|
||||||
if (gradeList.isNotEmpty()) {
|
if (gradeList.isNotEmpty()) {
|
||||||
db.gradeDao().addAll(gradeList)
|
db.gradeDao().addAll(gradeList)
|
||||||
}
|
}
|
||||||
|
@ -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.teachers.TeacherDao;
|
||||||
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team;
|
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.teams.TeamDao;
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.modules.timetable.TimetableDao;
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date;
|
import pl.szczodrzynski.edziennik.utils.models.Date;
|
||||||
|
|
||||||
@Database(entities = {
|
@Database(entities = {
|
||||||
@ -103,7 +104,8 @@ import pl.szczodrzynski.edziennik.utils.models.Date;
|
|||||||
Classroom.class,
|
Classroom.class,
|
||||||
NoticeType.class,
|
NoticeType.class,
|
||||||
AttendanceType.class,
|
AttendanceType.class,
|
||||||
Metadata.class}, version = 63)
|
pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson.class,
|
||||||
|
Metadata.class}, version = 64)
|
||||||
@TypeConverters({
|
@TypeConverters({
|
||||||
ConverterTime.class,
|
ConverterTime.class,
|
||||||
ConverterDate.class,
|
ConverterDate.class,
|
||||||
@ -141,6 +143,7 @@ public abstract class AppDb extends RoomDatabase {
|
|||||||
public abstract ClassroomDao classroomDao();
|
public abstract ClassroomDao classroomDao();
|
||||||
public abstract NoticeTypeDao noticeTypeDao();
|
public abstract NoticeTypeDao noticeTypeDao();
|
||||||
public abstract AttendanceTypeDao attendanceTypeDao();
|
public abstract AttendanceTypeDao attendanceTypeDao();
|
||||||
|
public abstract TimetableDao timetableDao();
|
||||||
public abstract MetadataDao metadataDao();
|
public abstract MetadataDao metadataDao();
|
||||||
|
|
||||||
private static volatile AppDb INSTANCE;
|
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");
|
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) {
|
public static AppDb getDatabase(final Context context) {
|
||||||
@ -789,7 +823,8 @@ public abstract class AppDb extends RoomDatabase {
|
|||||||
MIGRATION_59_60,
|
MIGRATION_59_60,
|
||||||
MIGRATION_60_61,
|
MIGRATION_60_61,
|
||||||
MIGRATION_61_62,
|
MIGRATION_61_62,
|
||||||
MIGRATION_62_63
|
MIGRATION_62_63,
|
||||||
|
MIGRATION_63_64
|
||||||
)
|
)
|
||||||
.allowMainThreadQueries()
|
.allowMainThreadQueries()
|
||||||
//.fallbackToDestructiveMigration()
|
//.fallbackToDestructiveMigration()
|
||||||
|
@ -4,11 +4,18 @@
|
|||||||
|
|
||||||
package pl.szczodrzynski.edziennik.data.db.modules.timetable
|
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.Date
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
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 {
|
companion object {
|
||||||
const val TYPE_NORMAL = 0
|
const val TYPE_NORMAL = 0
|
||||||
const val TYPE_CANCELLED = 1
|
const val TYPE_CANCELLED = 1
|
||||||
@ -17,15 +24,14 @@ open class Lesson(val profileId: Int) {
|
|||||||
const val TYPE_SHIFTED_TARGET = 4 /* target lesson */
|
const val TYPE_SHIFTED_TARGET = 4 /* target lesson */
|
||||||
}
|
}
|
||||||
|
|
||||||
@ColumnInfo(name = "lessonType")
|
|
||||||
var type: Int = TYPE_NORMAL
|
var type: Int = TYPE_NORMAL
|
||||||
|
|
||||||
var date: Date? = null
|
var date: Date? = null
|
||||||
var lessonNumber: Int? = null
|
var lessonNumber: Int? = null
|
||||||
var startTime: Time? = null
|
var startTime: Time? = null
|
||||||
var endTime: Time? = null
|
var endTime: Time? = null
|
||||||
var teacherId: Long? = null
|
|
||||||
var subjectId: Long? = null
|
var subjectId: Long? = null
|
||||||
|
var teacherId: Long? = null
|
||||||
var teamId: Long? = null
|
var teamId: Long? = null
|
||||||
var classroom: String? = null
|
var classroom: String? = null
|
||||||
|
|
||||||
@ -33,8 +39,58 @@ open class Lesson(val profileId: Int) {
|
|||||||
var oldLessonNumber: Int? = null
|
var oldLessonNumber: Int? = null
|
||||||
var oldStartTime: Time? = null
|
var oldStartTime: Time? = null
|
||||||
var oldEndTime: Time? = null
|
var oldEndTime: Time? = null
|
||||||
var oldTeacherId: Long? = null
|
|
||||||
var oldSubjectId: Long? = null
|
var oldSubjectId: Long? = null
|
||||||
|
var oldTeacherId: Long? = null
|
||||||
var oldTeamId: Long? = null
|
var oldTeamId: Long? = null
|
||||||
var oldClassroom: String? = null
|
var oldClassroom: String? = null
|
||||||
}
|
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
*/
|
@ -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
|
||||||
|
}
|
@ -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<Lesson>)
|
||||||
|
|
||||||
|
@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<List<LessonFull>>
|
||||||
|
}
|
@ -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<Date>()
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -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<Date>) : 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
|
||||||
|
}
|
||||||
|
}
|
@ -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<View>()
|
||||||
|
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<List<LessonFull>> { lessons ->
|
||||||
|
buildLessonViews(lessons)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildLessonViews(lessons: List<LessonFull>) {
|
||||||
|
val eventViews = mutableListOf<View>()
|
||||||
|
val eventTimeRanges = mutableListOf<DayView.EventTimeRange>()
|
||||||
|
|
||||||
|
// 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<CharSequence>().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<CharSequence>().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<CharSequence>().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)
|
||||||
|
}
|
||||||
|
}
|
@ -182,6 +182,10 @@ public class Date implements Comparable<Date> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isLeap() {
|
||||||
|
return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
public static Date getToday()
|
public static Date getToday()
|
||||||
{
|
{
|
||||||
Calendar cal = Calendar.getInstance();
|
Calendar cal = Calendar.getInstance();
|
||||||
|
10
app/src/main/res/drawable-v21/bg_rounded_ripple_4dp.xml
Normal file
10
app/src/main/res/drawable-v21/bg_rounded_ripple_4dp.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="?android:attr/colorControlHighlight">
|
||||||
|
<item android:id="@android:id/mask">
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="#000000" />
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
6
app/src/main/res/drawable/bg_rounded_ripple_4dp.xml
Normal file
6
app/src/main/res/drawable/bg_rounded_ripple_4dp.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_pressed="true" android:drawable="@drawable/bg_rounded_edittext_pressed" />
|
||||||
|
<item android:state_focused="true" android:drawable="@drawable/bg_rounded_edittext_pressed" />
|
||||||
|
<item android:state_selected="true" android:drawable="@drawable/bg_rounded_edittext_pressed" />
|
||||||
|
</selector>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<shape
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
<stroke android:color="@color/dividerColor" android:width="1dp" />
|
||||||
|
<solid android:color="#DCDCDC" />
|
||||||
|
</shape>
|
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:topLeftRadius="4dp" android:topRightRadius="4dp" />
|
||||||
|
<solid android:color="#2196f3" tools:color="?timetable_lesson_cancelled_color" />
|
||||||
|
</shape>
|
7
app/src/main/res/drawable/timetable_lesson_bg_dark.xml
Normal file
7
app/src/main/res/drawable/timetable_lesson_bg_dark.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<shape android:shape="rectangle"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
<solid android:color="@color/colorSurface_4dp" />
|
||||||
|
</shape>
|
7
app/src/main/res/drawable/timetable_lesson_bg_light.xml
Normal file
7
app/src/main/res/drawable/timetable_lesson_bg_light.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<shape android:shape="rectangle"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
<stroke android:width="1dp" android:color="#1e000000" />
|
||||||
|
</shape>
|
45
app/src/main/res/layout/fragment_timetable_v2.xml
Normal file
45
app/src/main/res/layout/fragment_timetable_v2.xml
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?colorSurface"
|
||||||
|
style="@style/Widget.MaterialComponents.AppBarLayout.Surface">
|
||||||
|
|
||||||
|
<com.nshmura.recyclertablayout.RecyclerTabLayout
|
||||||
|
android:id="@+id/tabLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="@color/colorSurface_6dp"
|
||||||
|
app:rtl_tabTextAppearance="@style/rtl_RecyclerTabLayout.Tab"
|
||||||
|
app:rtl_tabIndicatorColor="?colorPrimary"
|
||||||
|
app:rtl_tabMinWidth="90dp"
|
||||||
|
app:rtl_tabMaxWidth="300dp"
|
||||||
|
app:rtl_tabSelectedTextColor="?colorPrimary"
|
||||||
|
app:rtl_tabPaddingStart="16dp"
|
||||||
|
app:rtl_tabPaddingEnd="16dp"
|
||||||
|
app:rtl_tabPaddingTop="12dp"
|
||||||
|
app:rtl_tabPaddingBottom="12dp"/>
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.viewpager.widget.ViewPager
|
||||||
|
android:id="@+id/viewPager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</layout>
|
40
app/src/main/res/layout/fragment_timetable_v2_day.xml
Normal file
40
app/src/main/res/layout/fragment_timetable_v2_day.xml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/date"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/hello_blank_fragment"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:id="@+id/dayScroll"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.linkedin.android.tachyon.DayView
|
||||||
|
android:id="@+id/day"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="10dp"
|
||||||
|
app:dividerHeight="1dp"
|
||||||
|
app:endHour="18"
|
||||||
|
app:eventMargin="2dp"
|
||||||
|
app:halfHourDividerColor="#e0e0e0"
|
||||||
|
app:halfHourHeight="60dp"
|
||||||
|
app:hourDividerColor="#b0b0b0"
|
||||||
|
app:hourLabelMarginEnd="10dp"
|
||||||
|
app:hourLabelWidth="40dp"
|
||||||
|
app:startHour="5" />
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</layout>
|
16
app/src/main/res/layout/timetable_hour_label.xml
Normal file
16
app/src/main/res/layout/timetable_hour_label.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright 2019 LinkedIn Corporation -->
|
||||||
|
<!-- All Rights Reserved. -->
|
||||||
|
<!-- -->
|
||||||
|
<!-- Licensed under the BSD 2-Clause License (the "License"). See License in the project root -->
|
||||||
|
<!-- for license information. -->
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
style="@style/Base.TextAppearance.AppCompat.Small"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textAlignment="viewEnd"
|
||||||
|
tools:text="1 PM" />
|
105
app/src/main/res/layout/timetable_lesson.xml
Normal file
105
app/src/main/res/layout/timetable_lesson.xml
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?android:colorBackground"
|
||||||
|
android:foreground="@drawable/bg_rounded_ripple_4dp"
|
||||||
|
tools:padding="32dp">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:layout_height="90dp"
|
||||||
|
android:background="?timetable_lesson_bg"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="4dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/annotation"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/timetable_lesson_annotation"
|
||||||
|
android:fontFamily="sans-serif-condensed"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:textColor="#000"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="italic"
|
||||||
|
android:text="@string/timetable_lesson_cancelled"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="top"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/subjectName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:fontFamily="sans-serif-light"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="pracownia urządzeń techniki komputerowej nazwa przedmiotu jest bardzo długa i pewnie się tu nie zmieści w ogóle" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/attendanceIcon"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_weight="0"
|
||||||
|
tools:srcCompat="@sample/check"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView4"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_weight="0"
|
||||||
|
app:srcCompat="@drawable/bg_circle"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="bottom"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/detailsFirst"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:singleLine="true"
|
||||||
|
tools:text="8:10 - 8:55 • 015 językowa → 016 językowa" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/detailsSecond"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="middle"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="@style/NavView.TextView.Helper"
|
||||||
|
android:textSize="12sp"
|
||||||
|
tools:text="Paweł Informatyczny • 2b3T n1" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
</layout>
|
@ -3,4 +3,8 @@
|
|||||||
<attr name="colorSection" format="color" />
|
<attr name="colorSection" format="color" />
|
||||||
<attr name="cardBackgroundDimmed" format="color" />
|
<attr name="cardBackgroundDimmed" format="color" />
|
||||||
<attr name="cardBackgroundHighlight" format="color" />
|
<attr name="cardBackgroundHighlight" format="color" />
|
||||||
|
<attr name="timetable_lesson_bg" format="reference" />
|
||||||
|
<attr name="timetable_lesson_cancelled_color" format="color" />
|
||||||
|
<attr name="timetable_lesson_change_color" format="color" />
|
||||||
|
<attr name="timetable_lesson_shifted_color" format="color" />
|
||||||
</resources>
|
</resources>
|
@ -985,4 +985,10 @@
|
|||||||
<string name="app_manager_open_failed">Nie udało się otworzyć ustawień</string>
|
<string name="app_manager_open_failed">Nie udało się otworzyć ustawień</string>
|
||||||
<string name="edziennik_notification_api_notify_title">Tworzenie powiadomień</string>
|
<string name="edziennik_notification_api_notify_title">Tworzenie powiadomień</string>
|
||||||
<string name="login_librus_captcha_title">Librus - logowanie</string>
|
<string name="login_librus_captcha_title">Librus - logowanie</string>
|
||||||
|
<string name="timetable_today">Dzisiaj</string>
|
||||||
|
<string name="timetable_lesson_cancelled">Lekcja odwołana</string>
|
||||||
|
<string name="timetable_lesson_change">Zastępstwo</string>
|
||||||
|
<string name="timetable_lesson_change_format">Zastępstwo: zamiast %s</string>
|
||||||
|
<string name="timetable_lesson_shifted_same_day">Lekcja przeniesiona na godz. %s</string>
|
||||||
|
<string name="timetable_lesson_shifted_other_day">Lekcja przeniesiona na %s, godz. %s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources>
|
||||||
|
|
||||||
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
|
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
|
||||||
<item name="colorAccent">@color/colorAccent</item>
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
@ -95,6 +95,11 @@
|
|||||||
<item name="mal_color_secondary">?android:textColorSecondary</item>
|
<item name="mal_color_secondary">?android:textColorSecondary</item>
|
||||||
<item name="mal_card_background">?colorSurface</item>
|
<item name="mal_card_background">?colorSurface</item>
|
||||||
<item name="mal_divider_color">@color/dividerColor</item>
|
<item name="mal_divider_color">@color/dividerColor</item>
|
||||||
|
|
||||||
|
<item name="timetable_lesson_bg">@drawable/timetable_lesson_bg_light</item>
|
||||||
|
<item name="timetable_lesson_cancelled_color">#9f9f9f</item>
|
||||||
|
<item name="timetable_lesson_change_color">#ffb300</item>
|
||||||
|
<item name="timetable_lesson_shifted_color">#4caf50</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="AppTheme.Dark" parent="NavView.Dark">
|
<style name="AppTheme.Dark" parent="NavView.Dark">
|
||||||
<item name="colorPrimary">#64b5f6</item>
|
<item name="colorPrimary">#64b5f6</item>
|
||||||
@ -119,6 +124,11 @@
|
|||||||
<item name="mal_color_secondary">@color/secondaryTextDark</item>
|
<item name="mal_color_secondary">@color/secondaryTextDark</item>
|
||||||
<item name="mal_card_background">?colorSurface</item>
|
<item name="mal_card_background">?colorSurface</item>
|
||||||
<item name="mal_divider_color">@color/dividerColor</item>
|
<item name="mal_divider_color">@color/dividerColor</item>
|
||||||
|
|
||||||
|
<item name="timetable_lesson_bg">@drawable/timetable_lesson_bg_dark</item>
|
||||||
|
<item name="timetable_lesson_cancelled_color">#838383</item>
|
||||||
|
<item name="timetable_lesson_change_color">#ffb300</item>
|
||||||
|
<item name="timetable_lesson_shifted_color">#4caf50</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user