diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt index 98180d87..c842e769 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt @@ -47,6 +47,7 @@ import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity import pl.szczodrzynski.edziennik.utils.* import pl.szczodrzynski.edziennik.utils.managers.GradesManager import pl.szczodrzynski.edziennik.utils.managers.NotificationChannelsManager +import pl.szczodrzynski.edziennik.utils.managers.TimetableManager import pl.szczodrzynski.edziennik.utils.managers.UserActionManager import java.util.concurrent.TimeUnit import kotlin.coroutines.CoroutineContext @@ -67,6 +68,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { val notificationChannelsManager by lazy { NotificationChannelsManager(this) } val userActionManager by lazy { UserActionManager(this) } val gradesManager by lazy { GradesManager(this) } + val timetableManager by lazy { TimetableManager(this) } val db get() = App.db diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableDao.kt index 295739ea..9ac9709b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableDao.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableDao.kt @@ -5,10 +5,9 @@ package pl.szczodrzynski.edziennik.data.db.dao import androidx.lifecycle.LiveData -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query +import androidx.room.* +import androidx.sqlite.db.SimpleSQLiteQuery +import androidx.sqlite.db.SupportSQLiteQuery import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.full.LessonFull @@ -53,6 +52,9 @@ interface TimetableDao { @Query("DELETE FROM timetable WHERE profileId = :profileId AND type != -1 AND ((type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo))") fun clearBetweenDates(profileId: Int, dateFrom: Date, dateTo: Date) + @RawQuery(observedEntities = [Lesson::class]) + fun getRaw(query: SupportSQLiteQuery): LiveData> + @Query(""" $QUERY WHERE timetable.profileId = :profileId AND type != -1 AND type != 0 @@ -67,12 +69,11 @@ interface TimetableDao { """) fun getChangesForDateNow(profileId: Int, date: Date): List - @Query(""" + fun getForDate(profileId: Int, date: Date) = getRaw(SimpleSQLiteQuery(""" $QUERY - WHERE timetable.profileId = :profileId AND ((type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date)) + WHERE timetable.profileId = $profileId AND ((type != 3 AND date = "${date.stringY_m_d}") OR ((type = 3 OR type = 1) AND oldDate = "${date.stringY_m_d}")) ORDER BY id, type - """) - fun getForDate(profileId: Int, date: Date): LiveData> + """)) @Query(""" $QUERY diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Lesson.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Lesson.kt index a46fd338..ceb6cf9c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Lesson.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/Lesson.kt @@ -5,6 +5,7 @@ package pl.szczodrzynski.edziennik.data.db.entity import androidx.room.Entity +import androidx.room.Ignore import androidx.room.Index import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @@ -66,6 +67,9 @@ open class Lesson(val profileId: Int, var id: Long) { fun buildId(): Long = (displayDate?.combineWith(displayStartTime) ?: 0L) / 6L * 10L + (hashCode() and 0xFFFF) + @Ignore + var showAsUnseen = false + override fun toString(): String { return "Lesson(profileId=$profileId, " + "id=$id, " + diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt index 1b3b576f..b384cb46 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt @@ -25,7 +25,6 @@ import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment -import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableUtils import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Week @@ -50,7 +49,7 @@ class LessonDetailsDialog( get() = job + Dispatchers.Main private lateinit var adapter: EventListAdapter - private val utils by lazy { TimetableUtils() } + private val manager by lazy { app.timetableManager } init { run { if (activity.isFinishing) @@ -91,7 +90,7 @@ class LessonDetailsDialog( val lessonTime = lesson.displayStartTime ?: return b.lessonDate.text = Week.getFullDayName(lessonDate.weekDay) + ", " + lessonDate.formattedString - b.annotationVisible = utils.getAnnotation(activity, lesson, b.annotation) + b.annotationVisible = manager.getAnnotation(activity, lesson, b.annotation) if (lesson.type >= Lesson.TYPE_SHIFTED_SOURCE) { b.shiftedLayout.visibility = View.VISIBLE diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/PagerFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/PagerFragment.kt new file mode 100644 index 00000000..9a0a7b9c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/PagerFragment.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-2-22. + */ + +package pl.szczodrzynski.edziennik.ui.modules.base + +import androidx.fragment.app.Fragment + +abstract class PagerFragment : Fragment() { + private var isPageCreated = false + + /** + * Called when the page is first shown, or if previous + * [onPageCreated] returned false + * + * @return true if the view is set up + * @return false if the setup failed. The method may be then called + * again, when page becomes visible. + */ + abstract fun onPageCreated(): Boolean + + override fun onResume() { + if (!isPageCreated) { + isPageCreated = onPageCreated() + } + super.onResume() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt index aa185f06..da2450e4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt @@ -5,15 +5,15 @@ package pl.szczodrzynski.edziennik.ui.modules.timetable import android.os.Bundle -import android.util.Log +import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import android.widget.ProgressBar import android.widget.TextView import androidx.asynclayoutinflater.view.AsyncLayoutInflater import androidx.core.view.setPadding -import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import com.linkedin.android.tachyon.DayView import com.linkedin.android.tachyon.DayViewConfig @@ -27,6 +27,7 @@ import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding import pl.szczodrzynski.edziennik.databinding.TimetableNoTimetableBinding import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog +import pl.szczodrzynski.edziennik.ui.modules.base.PagerFragment import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_END_HOUR import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment.Companion.DEFAULT_START_HOUR import pl.szczodrzynski.edziennik.utils.ListenerScrollView @@ -35,7 +36,7 @@ import java.util.* import kotlin.coroutines.CoroutineContext import kotlin.math.min -class TimetableDayFragment : Fragment(), CoroutineScope { +class TimetableDayFragment : PagerFragment(), CoroutineScope { companion object { private const val TAG = "TimetableDayFragment" } @@ -44,7 +45,7 @@ class TimetableDayFragment : Fragment(), CoroutineScope { private lateinit var activity: MainActivity private lateinit var inflater: AsyncLayoutInflater - private lateinit var job: Job + private val job: Job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main @@ -53,7 +54,7 @@ class TimetableDayFragment : Fragment(), CoroutineScope { private var endHour = DEFAULT_END_HOUR private var firstEventMinute = 24 * 60 - private val utils by lazy { TimetableUtils() } + private val manager by lazy { app.timetableManager } // find SwipeRefreshLayout in the hierarchy private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) } @@ -88,25 +89,23 @@ class TimetableDayFragment : Fragment(), CoroutineScope { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { activity = (getActivity() as MainActivity?) ?: return null - if (context == null) - return null + context ?: return null app = activity.application as App - job = Job() this.inflater = AsyncLayoutInflater(context!!) date = arguments?.getInt("date")?.let { Date.fromValue(it) } ?: Date.getToday() startHour = arguments?.getInt("startHour") ?: DEFAULT_START_HOUR endHour = arguments?.getInt("endHour") ?: DEFAULT_END_HOUR return FrameLayout(activity).apply { layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) + addView(ProgressBar(activity).apply { + layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER) + }) } } - 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") + override fun onPageCreated(): Boolean { + if (!isAdded) + return false // observe lesson database app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer { lessons -> @@ -117,6 +116,8 @@ class TimetableDayFragment : Fragment(), CoroutineScope { processLessonList(lessons, events) } }) + + return true } private fun processLessonList(lessons: List, events: List) { @@ -174,6 +175,8 @@ class TimetableDayFragment : Fragment(), CoroutineScope { } dayView.setHourLabelViews(hourLabelViews) + lessons.forEach { it.showAsUnseen = !it.seen } + buildLessonViews(lessons.filter { it.type != Lesson.TYPE_NO_LESSONS }, events) } @@ -209,7 +212,6 @@ class TimetableDayFragment : Fragment(), CoroutineScope { eventView.tag = lesson eventView.setOnClickListener { - Log.d(TAG, "Clicked ${it.tag}") if (isAdded && it.tag is LessonFull) LessonDetailsDialog(activity, it.tag as LessonFull) } @@ -275,10 +277,13 @@ class TimetableDayFragment : Fragment(), CoroutineScope { lb.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet) lb.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet) - lb.unread = lesson.type != Lesson.TYPE_NORMAL && !lesson.seen + lb.unread = lesson.type != Lesson.TYPE_NORMAL && lesson.showAsUnseen + if (!lesson.seen) { + manager.markAsSeen(lesson) + } //lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD) - lb.annotationVisible = utils.getAnnotation(activity, lesson, lb.annotation) + lb.annotationVisible = manager.getAnnotation(activity, lesson, lb.annotation) // The day view needs the event time ranges in the start minute/end minute format, // so calculate those here diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableFragment.kt index 000b5777..fb23116c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableFragment.kt @@ -15,7 +15,6 @@ import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.fragment.app.Fragment -import androidx.lifecycle.Observer import androidx.viewpager.widget.ViewPager import com.google.android.material.datepicker.MaterialDatePicker import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial @@ -26,12 +25,9 @@ import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.db.entity.Metadata -import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding -import pl.szczodrzynski.edziennik.observeOnce import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog import pl.szczodrzynski.edziennik.ui.dialogs.timetable.GenerateBlockTimetableDialog -import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem @@ -50,7 +46,7 @@ class TimetableFragment : Fragment(), CoroutineScope { private lateinit var activity: MainActivity private lateinit var b: FragmentTimetableV2Binding - private lateinit var job: Job + private val job: Job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main @@ -61,11 +57,6 @@ class TimetableFragment : Fragment(), CoroutineScope { activity = (getActivity() as MainActivity?) ?: return null context ?: return null app = activity.application as App - job = Job() - 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) // TODO: 2020-01-05 resolve issues with page scrolling (and scrolling up) with viewpager and swipe to refresh //b.refreshLayout.setParent(activity.swipeRefreshLayout) @@ -91,8 +82,7 @@ class TimetableFragment : Fragment(), CoroutineScope { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { launch { - // TODO check if app, activity, b can be null - if (app.profile == null || !isAdded) + if (!isAdded) return@launch if (app.profile.getStudentData("timetableNotPublic", false)) { @@ -141,7 +131,7 @@ class TimetableFragment : Fragment(), CoroutineScope { startHour, endHour ) - b.viewPager.offscreenPageLimit = 2 + b.viewPager.offscreenPageLimit = 1 b.viewPager.adapter = pagerAdapter b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { override fun onPageScrollStateChanged(state: Int) { @@ -162,7 +152,7 @@ class TimetableFragment : Fragment(), CoroutineScope { activity.gainAttentionFAB() fabShown = true } - markLessonsAsSeen() + //markLessonsAsSeen() } }) @@ -224,7 +214,7 @@ class TimetableFragment : Fragment(), CoroutineScope { }) }} - private fun markLessonsAsSeen() = pageSelection?.let { date -> + /*private fun markLessonsAsSeen() = pageSelection?.let { date -> app.db.timetableDao().getForDate(App.profileId, date).observeOnce(this@TimetableFragment, Observer { lessons -> lessons.forEach { lesson -> if (lesson.type != Lesson.TYPE_NORMAL && lesson.type != Lesson.TYPE_NO_LESSONS @@ -233,5 +223,5 @@ class TimetableFragment : Fragment(), CoroutineScope { } } }) - } + }*/ } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetablePagerAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetablePagerAdapter.kt index eab85e18..31851498 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetablePagerAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetablePagerAdapter.kt @@ -33,11 +33,6 @@ class TimetablePagerAdapter( putInt("endHour", endHour) } } - /*return TimetableDayFragment().apply { - arguments = Bundle().also { - it.putLong("date", items[position].value.toLong()) - } - }*/ } override fun getCount(): Int { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableUtils.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/TimetableManager.kt similarity index 87% rename from app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableUtils.kt rename to app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/TimetableManager.kt index 9a704854..a9ea6695 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableUtils.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/TimetableManager.kt @@ -1,22 +1,35 @@ /* - * Copyright (c) Kuba Szczodrzyński 2020-2-16. + * Copyright (c) Kuba Szczodrzyński 2020-3-10. */ -package pl.szczodrzynski.edziennik.ui.modules.timetable +package pl.szczodrzynski.edziennik.utils.managers import android.content.Context import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter import android.widget.TextView -import pl.szczodrzynski.edziennik.R +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.full.LessonFull -import pl.szczodrzynski.edziennik.resolveAttr -import pl.szczodrzynski.edziennik.setText -import pl.szczodrzynski.edziennik.setTintColor import pl.szczodrzynski.navlib.getColorFromAttr +import kotlin.coroutines.CoroutineContext + +class TimetableManager(val app: App) : CoroutineScope { + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Default + + fun markAsSeen(lesson: LessonFull) { + lesson.seen = true + startCoroutineTimer(500L, 0L) { + app.db.metadataDao().setSeen(lesson.profileId, lesson, true) + } + } -class TimetableUtils { fun getAnnotation(context: Context, lesson: LessonFull, annotation: TextView): Boolean { var annotationVisible = false when (lesson.type) { diff --git a/app/src/main/res/layout/timetable_lesson.xml b/app/src/main/res/layout/timetable_lesson.xml index 5ca2f56c..9992da27 100644 --- a/app/src/main/res/layout/timetable_lesson.xml +++ b/app/src/main/res/layout/timetable_lesson.xml @@ -74,8 +74,8 @@ tools:text="pracownia urządzeń techniki komputerowej" />