Skip to content

Commit f6e63fe

Browse files
author
alllexey
committed
Improve ScheduleActivity
1 parent 9e44b6c commit f6e63fe

File tree

10 files changed

+385
-46
lines changed

10 files changed

+385
-46
lines changed

app/src/main/java/me/alllexey123/itmowidgets/ui/schedule/DayScheduleAdapter.kt

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import api.myitmo.model.Schedule
1111
import me.alllexey123.itmowidgets.R
1212
import me.alllexey123.itmowidgets.util.ScheduleUtils
1313
import java.time.Duration
14+
import java.time.LocalDate
15+
import java.time.LocalDateTime
1416
import java.time.LocalTime
1517
import java.time.format.DateTimeFormatter
1618

@@ -27,17 +29,25 @@ class DayScheduleAdapter(private var schedules: List<Schedule>) :
2729

2830
override fun onBindViewHolder(holder: DayViewHolder, position: Int) {
2931
val daySchedule = schedules[position]
30-
holder.dayTitle.text = ScheduleUtils.getRuDayOfWeek(daySchedule.date.dayOfWeek)
31-
val dtf = DateTimeFormatter.ofPattern("dd.MM")
32-
holder.dayDate.text = dtf.format(daySchedule.date)
32+
val date = daySchedule.date
33+
val lessons = daySchedule.lessons
34+
35+
holder.dayTitle.text = ScheduleUtils.getRuDayOfWeek(date.dayOfWeek)
36+
holder.dayDate.text = "${date.dayOfMonth} ${ScheduleUtils.getRussianMonthInGenitiveCase(date.monthValue)}"
37+
val numberOfLessonsText = if (lessons.isEmpty()) {
38+
"нет пар"
39+
} else {
40+
"${lessons.size} ${ScheduleUtils.lessonDeclension(lessons.size)}"
41+
}
42+
holder.numberOfLessons.text = numberOfLessonsText
3343

3444
val layoutManager = LinearLayoutManager(
3545
holder.innerRecyclerView.context,
3646
LinearLayoutManager.VERTICAL,
3747
false
3848
)
3949

40-
val processed = processLessonsWithBreaks(daySchedule.lessons)
50+
val processed = processLessonsWithBreaks(lessons, date)
4151
layoutManager.initialPrefetchItemCount = processed.size
4252

4353
val lessonAdapter = LessonAdapter(processed)
@@ -56,20 +66,36 @@ class DayScheduleAdapter(private var schedules: List<Schedule>) :
5666
override fun getItemCount(): Int = schedules.size
5767

5868
inner class DayViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
59-
val dayTitle: TextView = itemView.findViewById(R.id.dayTitleTextView)
69+
val dayTitle: TextView = itemView.findViewById(R.id.day_title)
70+
71+
val numberOfLessons: TextView = itemView.findViewById(R.id.number_of_lessons)
6072

61-
val dayDate: TextView = itemView.findViewById(R.id.dayDateTextView)
62-
val innerRecyclerView: RecyclerView = itemView.findViewById(R.id.innerRecyclerView)
73+
val dayDate: TextView = itemView.findViewById(R.id.day_date)
74+
val innerRecyclerView: RecyclerView = itemView.findViewById(R.id.inner_recycler_view)
6375
}
6476

65-
fun processLessonsWithBreaks(lessons: List<Lesson>): List<ScheduleItem> {
77+
fun processLessonsWithBreaks(lessons: List<Lesson>, date: LocalDate): List<ScheduleItem> {
6678
val BIG_BREAK_THRESHOLD = Duration.ofMinutes(60)
6779

80+
val now = LocalDateTime.now()
81+
6882
val processedList = mutableListOf<ScheduleItem>()
6983
val sortedLessons = lessons.sortedBy { it.timeStart }
7084

7185
sortedLessons.forEachIndexed { index, currentLesson ->
72-
processedList.add(ScheduleItem.LessonItem(currentLesson))
86+
val lessonStartTime = ScheduleUtils.parseTime(date, currentLesson.timeStart)
87+
val lessonEndTime = ScheduleUtils.parseTime(date, currentLesson.timeEnd)
88+
val lessonState = if (lessonEndTime < now) {
89+
ScheduleItem.LessonState.COMPLETED
90+
} else {
91+
if (lessonStartTime < now) {
92+
ScheduleItem.LessonState.CURRENT
93+
} else {
94+
ScheduleItem.LessonState.UPCOMING
95+
}
96+
}
97+
98+
processedList.add(ScheduleItem.LessonItem(currentLesson, lessonState))
7399

74100
if (index < sortedLessons.size - 1) {
75101
val nextLesson = sortedLessons[index + 1]
@@ -90,6 +116,20 @@ class DayScheduleAdapter(private var schedules: List<Schedule>) :
90116
}
91117
}
92118

119+
if (processedList.isEmpty()) {
120+
val nowDate = now.toLocalDate()
121+
val lessonState = if (date < nowDate) {
122+
ScheduleItem.LessonState.COMPLETED
123+
} else {
124+
if (date > nowDate) {
125+
ScheduleItem.LessonState.UPCOMING
126+
} else {
127+
ScheduleItem.LessonState.CURRENT
128+
}
129+
}
130+
return listOf(ScheduleItem.NoLessonsItem(lessonState))
131+
}
132+
93133
return processedList
94134
}
95135

app/src/main/java/me/alllexey123/itmowidgets/ui/schedule/LessonAdapter.kt

Lines changed: 82 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package me.alllexey123.itmowidgets.ui.schedule
22

3+
import android.os.Handler
4+
import android.os.Looper
35
import android.view.LayoutInflater
46
import android.view.View
57
import android.view.ViewGroup
@@ -13,21 +15,21 @@ import me.alllexey123.itmowidgets.R
1315
import me.alllexey123.itmowidgets.util.ScheduleUtils
1416
import java.time.LocalTime
1517
import java.time.format.DateTimeFormatter
16-
import kotlin.math.max
1718

1819
class LessonAdapter(private val scheduleList: List<ScheduleItem>) :
1920
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
2021

2122
companion object {
2223
private const val VIEW_TYPE_LESSON = 1
2324
private const val VIEW_TYPE_BREAK = 2
25+
private const val VIEW_TYPE_NO_LESSONS = 3
2426
}
2527

2628
override fun getItemViewType(position: Int): Int {
27-
if (scheduleList.isEmpty()) return VIEW_TYPE_LESSON
2829
return when (scheduleList[position]) {
2930
is ScheduleItem.LessonItem -> VIEW_TYPE_LESSON
3031
is ScheduleItem.BreakItem -> VIEW_TYPE_BREAK
32+
is ScheduleItem.NoLessonsItem -> VIEW_TYPE_NO_LESSONS
3133
}
3234
}
3335

@@ -36,7 +38,7 @@ class LessonAdapter(private val scheduleList: List<ScheduleItem>) :
3638
return when (viewType) {
3739
VIEW_TYPE_LESSON -> {
3840
val view = LayoutInflater.from(parent.context)
39-
.inflate(R.layout.item_lesson_list_entry_dot, parent, false)
41+
.inflate(R.layout.item_schedule_lesson, parent, false)
4042
LessonViewHolder(view)
4143
}
4244

@@ -46,28 +48,52 @@ class LessonAdapter(private val scheduleList: List<ScheduleItem>) :
4648
BreakViewHolder(view)
4749
}
4850

51+
VIEW_TYPE_NO_LESSONS -> {
52+
val view = LayoutInflater.from(parent.context)
53+
.inflate(R.layout.item_schedule_lesson, parent, false)
54+
LessonViewHolder(view)
55+
}
56+
4957
else -> throw IllegalArgumentException("Invalid view type")
5058
}
5159
}
5260

5361
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
54-
if (scheduleList.isEmpty()) {
55-
bindEmptyDay(holder as LessonViewHolder)
56-
return
57-
}
5862
when (val item = scheduleList[position]) {
5963
is ScheduleItem.LessonItem -> {
60-
bindLesson(holder as LessonViewHolder, item.lesson, position)
64+
bindLesson(holder as LessonViewHolder, item.lesson, item.lessonState, position)
6165
}
6266

6367
is ScheduleItem.BreakItem -> {
6468
bindBreak(holder as BreakViewHolder, item.from, item.to)
6569
}
70+
71+
is ScheduleItem.NoLessonsItem -> {
72+
bindEmptyDay(holder as LessonViewHolder, item.lessonState)
73+
}
6674
}
6775
}
6876

69-
private fun bindEmptyDay(holder: LessonViewHolder) {
77+
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
78+
super.onViewRecycled(holder)
79+
if (holder is LessonViewHolder) {
80+
holder.stopBlinking()
81+
}
82+
}
83+
84+
private fun bindEmptyDay(holder: LessonViewHolder, lessonState: ScheduleItem.LessonState) {
85+
holder.stopBlinking()
7086
holder.lessonTitle.text = "В этот день нет пар"
87+
val typeIndicatorDrawable = when (lessonState) {
88+
ScheduleItem.LessonState.COMPLETED -> R.drawable.indicator_circle
89+
ScheduleItem.LessonState.CURRENT -> {
90+
holder.startBlinking()
91+
R.drawable.indicator_circle_hollow_dot
92+
}
93+
ScheduleItem.LessonState.UPCOMING -> R.drawable.indicator_circle_hollow
94+
}
95+
96+
holder.typeIndicator.setImageResource(typeIndicatorDrawable)
7197
holder.typeIndicator.setColorFilter(
7298
ContextCompat.getColor(holder.itemView.context, R.color.free_color)
7399
)
@@ -78,9 +104,26 @@ class LessonAdapter(private val scheduleList: List<ScheduleItem>) :
78104
return
79105
}
80106

81-
private fun bindLesson(holder: LessonViewHolder, lesson: Lesson, position: Int) {
107+
private fun bindLesson(
108+
holder: LessonViewHolder,
109+
lesson: Lesson,
110+
lessonState: ScheduleItem.LessonState,
111+
position: Int
112+
) {
113+
holder.stopBlinking()
82114
holder.lessonTitle.text = lesson.subject ?: "Неизвестная дисциплина"
83115

116+
val typeIndicatorDrawable = when (lessonState) {
117+
ScheduleItem.LessonState.COMPLETED -> R.drawable.indicator_circle
118+
ScheduleItem.LessonState.CURRENT -> {
119+
holder.startBlinking()
120+
R.drawable.indicator_circle_hollow_dot
121+
}
122+
ScheduleItem.LessonState.UPCOMING -> R.drawable.indicator_circle_hollow
123+
}
124+
125+
holder.typeIndicator.setImageResource(typeIndicatorDrawable)
126+
84127
holder.typeIndicator.setColorFilter(
85128
ContextCompat.getColor(
86129
holder.itemView.context,
@@ -102,7 +145,9 @@ class LessonAdapter(private val scheduleList: List<ScheduleItem>) :
102145
if (lesson.building == null) "" else ScheduleUtils.shortenBuildingName(lesson.building!!)
103146
holder.locationLayout.visibility = View.VISIBLE
104147
} else {
105-
holder.locationLayout.visibility = View.GONE
148+
holder.locationRoom.text = "нет кабинета"
149+
holder.locationBuilding.text = ""
150+
holder.locationLayout.visibility = View.VISIBLE
106151
}
107152

108153
if (lesson.timeStart != null) {
@@ -112,7 +157,10 @@ class LessonAdapter(private val scheduleList: List<ScheduleItem>) :
112157
holder.timeLayout.visibility = View.GONE
113158
}
114159

115-
if (position == scheduleList.size - 1 || (scheduleList.getOrNull(position + 1) != null && scheduleList.getOrNull(position + 1) is ScheduleItem.BreakItem)) {
160+
if (position == scheduleList.size - 1 || (scheduleList.getOrNull(position + 1) != null && scheduleList.getOrNull(
161+
position + 1
162+
) is ScheduleItem.BreakItem)
163+
) {
116164
holder.divider.visibility = View.GONE
117165
} else {
118166
holder.divider.visibility = View.VISIBLE
@@ -124,35 +172,46 @@ class LessonAdapter(private val scheduleList: List<ScheduleItem>) :
124172
holder.breakText.text = "⋯ можно отдохнуть с ${dtf.format(from)} до ${dtf.format(to)}"
125173
}
126174

127-
override fun getItemCount() = max(1, scheduleList.size)
175+
override fun getItemCount() = scheduleList.size
128176

129177
inner class LessonViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
130178

131179
val typeIndicator: ImageView = itemView.findViewById(R.id.type_indicator)
132-
133180
val lessonTitle: TextView = itemView.findViewById(R.id.title)
134-
135181
val teacherLayout: LinearLayout = itemView.findViewById(R.id.teacher_layout)
136182
val teacherName: TextView = itemView.findViewById(R.id.teacher)
137-
138183
val locationLayout: LinearLayout = itemView.findViewById(R.id.location_layout)
139-
140184
val locationRoom: TextView = itemView.findViewById(R.id.location_room)
141-
142185
val locationBuilding: TextView = itemView.findViewById(R.id.location_building)
143-
144186
val timeLayout: LinearLayout = itemView.findViewById(R.id.time_layout)
145-
146187
val time: TextView = itemView.findViewById(R.id.time)
147-
148188
val divider: ImageView = itemView.findViewById(R.id.divider)
149189

190+
private val handler = Handler(Looper.getMainLooper())
191+
private var isHollow = false
192+
private val blinkingRunnable = object : Runnable {
193+
override fun run() {
194+
if (isHollow) {
195+
typeIndicator.setImageResource(R.drawable.indicator_circle_hollow_dot)
196+
} else {
197+
typeIndicator.setImageResource(R.drawable.indicator_circle_hollow)
198+
}
199+
isHollow = !isHollow
200+
handler.postDelayed(this, 1000)
201+
}
202+
}
203+
204+
fun startBlinking() {
205+
isHollow = false
206+
handler.post(blinkingRunnable)
207+
}
150208

209+
fun stopBlinking() {
210+
handler.removeCallbacks(blinkingRunnable)
211+
}
151212
}
152213

153214
inner class BreakViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
154-
155215
val breakText: TextView = itemView.findViewById(R.id.break_text)
156-
157216
}
158217
}

app/src/main/java/me/alllexey123/itmowidgets/ui/schedule/ScheduleItem.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@ import api.myitmo.model.Lesson
44
import java.time.LocalTime
55

66
sealed interface ScheduleItem {
7-
data class LessonItem(val lesson: Lesson) : ScheduleItem
7+
data class LessonItem(val lesson: Lesson, val lessonState: LessonState) : ScheduleItem
88

99
data class BreakItem(val from: LocalTime, val to: LocalTime) : ScheduleItem
10+
11+
data class NoLessonsItem(val lessonState: LessonState): ScheduleItem
12+
13+
enum class LessonState {
14+
UPCOMING,
15+
CURRENT,
16+
COMPLETED
17+
}
1018
}

app/src/main/java/me/alllexey123/itmowidgets/util/ScheduleUtils.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,16 @@ object ScheduleUtils {
9999
}
100100
}
101101

102+
fun getRussianMonthInGenitiveCase(monthNumber: Int): String {
103+
val months = arrayOf(
104+
"января", "февраля", "марта", "апреля", "мая", "июня",
105+
"июля", "августа", "сентября", "октября", "ноября", "декабря"
106+
)
107+
108+
return months[monthNumber - 1]
109+
}
110+
111+
102112
fun getWorkTypeColor(workTypeId: Int): Int {
103113
return when (workTypeId) {
104114
-1 -> R.color.free_color
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:shape="line">
4+
5+
<stroke
6+
android:width="1dp"
7+
android:color="?attr/colorOutlineVariant"
8+
android:dashWidth="8dp"
9+
android:dashGap="8dp" />
10+
11+
<size android:height="2dp" />
12+
</shape>
13+
14+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:shape="oval">
4+
<stroke
5+
android:width="2dp"
6+
android:color="@color/subtext_color"/>
7+
<solid android:color="@android:color/transparent"/>
8+
</shape>

0 commit comments

Comments
 (0)