[UI] Show attendance info in timetable and lesson dialog. (#74)

* Show attendance in timetable view

* Optimize code and UX

* Update some code

* Update attendance layout

* Fix timetable view

* Bump iconics version

* Change umbrella icon

* Update app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt

* Update app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt

* Update app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt

* Update app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt

* Update lesson cell margins

* Add attendance info in lesson dialog

Co-authored-by: Kuba Szczodrzyński <kuba@szczodrzynski.pl>
This commit is contained in:
Antoni Czaplicki 2021-09-22 00:03:31 +02:00 committed by GitHub
parent 3700a71c39
commit e8e9f04050
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 162 additions and 35 deletions

View File

@ -172,10 +172,10 @@ dependencies {
kapt "eu.szkolny.selective-dao:codegen:27f8f3f194" kapt "eu.szkolny.selective-dao:codegen:27f8f3f194"
// Iconics & related // Iconics & related
implementation "com.mikepenz:iconics-core:5.3.0-b01" implementation "com.mikepenz:iconics-core:5.3.1"
implementation "com.mikepenz:iconics-views:5.3.0-b01" implementation "com.mikepenz:iconics-views:5.3.1"
implementation "com.mikepenz:community-material-typeface:5.8.55.0-kotlin@aar" implementation "com.mikepenz:community-material-typeface:5.8.55.0-kotlin@aar"
implementation "eu.szkolny:szkolny-font:1.3" implementation "eu.szkolny:szkolny-font:77e33acc2a"
// Other dependencies // Other dependencies
implementation "cat.ereza:customactivityoncrash:2.3.0" implementation "cat.ereza:customactivityoncrash:2.3.0"

View File

@ -64,6 +64,8 @@ abstract class AttendanceDao : BaseDao<Attendance, AttendanceFull> {
getRawNow("$QUERY WHERE notified = 0 $ORDER_BY") getRawNow("$QUERY WHERE notified = 0 $ORDER_BY")
fun getNotNotifiedNow(profileId: Int) = fun getNotNotifiedNow(profileId: Int) =
getRawNow("$QUERY WHERE attendances.profileId = $profileId AND notified = 0 $ORDER_BY") getRawNow("$QUERY WHERE attendances.profileId = $profileId AND notified = 0 $ORDER_BY")
fun getAllByDateNow(profileId: Int, date: Date) =
getRawNow("$QUERY WHERE attendances.profileId = $profileId AND attendanceDate = '${date.stringY_m_d}' $ORDER_BY")
// GET ONE - NOW // GET ONE - NOW
fun getByIdNow(profileId: Int, id: Long) = fun getByIdNow(profileId: Int, id: Long) =

View File

@ -8,15 +8,20 @@ import android.content.Intent
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding
import pl.szczodrzynski.edziennik.onClick import pl.szczodrzynski.edziennik.onClick
@ -24,6 +29,7 @@ import pl.szczodrzynski.edziennik.setText
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceDetailsDialog
import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment
import pl.szczodrzynski.edziennik.utils.BetterLink import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
@ -34,6 +40,7 @@ import kotlin.coroutines.CoroutineContext
class LessonDetailsDialog( class LessonDetailsDialog(
val activity: AppCompatActivity, val activity: AppCompatActivity,
val lesson: LessonFull, val lesson: LessonFull,
val attendance: AttendanceFull? = null,
val onShowListener: ((tag: String) -> Unit)? = null, val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope { ) : CoroutineScope {
@ -52,6 +59,8 @@ class LessonDetailsDialog(
private lateinit var adapter: EventListAdapter private lateinit var adapter: EventListAdapter
private val manager private val manager
get() = app.timetableManager get() = app.timetableManager
private val attendanceManager
get() = app.attendanceManager
init { run { init { run {
if (activity.isFinishing) if (activity.isFinishing)
@ -170,6 +179,27 @@ class LessonDetailsDialog(
b.teamName = lesson.teamName b.teamName = lesson.teamName
} }
b.attendanceDivider.isVisible = attendance != null
b.attendanceLayout.isVisible = attendance != null
if (attendance != null) {
b.attendanceView.setAttendance(attendance, app.attendanceManager, bigView = true)
b.attendanceType.text = attendance.typeName
b.attendanceIcon.isVisible = attendance.let {
val icon = attendanceManager.getAttendanceIcon(it) ?: return@let false
val color = attendanceManager.getAttendanceColor(it)
b.attendanceIcon.setImageDrawable(
IconicsDrawable(activity, icon).apply {
colorInt = color
sizeDp = 24
}
)
true
}
b.attendanceDetails.onClick {
AttendanceDetailsDialog(activity, attendance, onShowListener, onDismissListener)
}
}
adapter = EventListAdapter( adapter = EventListAdapter(
activity, activity,
showWeekDay = false, showWeekDay = false,

View File

@ -9,19 +9,24 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.asynclayoutinflater.view.AsyncLayoutInflater import androidx.asynclayoutinflater.view.AsyncLayoutInflater
import androidx.core.view.isVisible import androidx.core.view.*
import androidx.core.view.marginTop
import androidx.core.view.setPadding
import androidx.core.view.updateLayoutParams
import com.linkedin.android.tachyon.DayView import com.linkedin.android.tachyon.DayView
import com.linkedin.android.tachyon.DayViewConfig import com.linkedin.android.tachyon.DayViewConfig
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import eu.szkolny.font.SzkolnyFont
import kotlinx.coroutines.* import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.TimetableDayFragmentBinding import pl.szczodrzynski.edziennik.databinding.TimetableDayFragmentBinding
@ -61,6 +66,8 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
private val manager private val manager
get() = app.timetableManager get() = app.timetableManager
private val attendanceManager
get() = app.attendanceManager
// find SwipeRefreshLayout in the hierarchy // find SwipeRefreshLayout in the hierarchy
private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) } private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) }
@ -102,14 +109,17 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
val events = withContext(Dispatchers.Default) { val events = withContext(Dispatchers.Default) {
app.db.eventDao().getAllByDateNow(App.profileId, date) app.db.eventDao().getAllByDateNow(App.profileId, date)
} }
processLessonList(lessons, events) val attendanceList = withContext(Dispatchers.Default) {
app.db.attendanceDao().getAllByDateNow(App.profileId, date)
}
processLessonList(lessons, events, attendanceList)
} }
} }
return true return true
} }
private fun processLessonList(lessons: List<LessonFull>, events: List<EventFull>) { private fun processLessonList(lessons: List<LessonFull>, events: List<EventFull>, attendanceList: List<AttendanceFull>) {
// no lessons - timetable not downloaded yet // no lessons - timetable not downloaded yet
if (lessons.isEmpty()) { if (lessons.isEmpty()) {
inflater.inflate(R.layout.timetable_no_timetable, b.root) { view, _, _ -> inflater.inflate(R.layout.timetable_no_timetable, b.root) { view, _, _ ->
@ -172,10 +182,10 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
lessons.forEach { it.showAsUnseen = !it.seen } lessons.forEach { it.showAsUnseen = !it.seen }
buildLessonViews(lessons.filter { it.type != Lesson.TYPE_NO_LESSONS }, events) buildLessonViews(lessons.filter { it.type != Lesson.TYPE_NO_LESSONS }, events, attendanceList)
} }
private fun buildLessonViews(lessons: List<LessonFull>, events: List<EventFull>) { private fun buildLessonViews(lessons: List<LessonFull>, events: List<EventFull>, attendanceList: List<AttendanceFull>) {
if (!isAdded) if (!isAdded)
return return
@ -192,6 +202,7 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
for (lesson in lessons) { for (lesson in lessons) {
val attendance = attendanceList.find { it.startTime == lesson.startTime }
val startTime = lesson.displayStartTime ?: continue val startTime = lesson.displayStartTime ?: continue
val endTime = lesson.displayEndTime ?: continue val endTime = lesson.displayEndTime ?: continue
@ -208,11 +219,17 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
val lb = TimetableLessonBinding.bind(eventView) val lb = TimetableLessonBinding.bind(eventView)
eventViews += eventView eventViews += eventView
eventView.tag = lesson eventView.tag = lesson to attendance
eventView.setOnClickListener { eventView.setOnClickListener {
if (isAdded && it.tag is LessonFull) if (isAdded && it.tag is Pair<*, *>) {
LessonDetailsDialog(activity, it.tag as LessonFull) val (lessonObj, attendanceObj) = it.tag as Pair<*, *>
LessonDetailsDialog(
activity = activity,
lesson = lessonObj as LessonFull,
attendance = attendanceObj as AttendanceFull?
)
}
} }
val eventList = events.filter { it.time != null && it.time == lesson.displayStartTime }.take(3) val eventList = events.filter { it.time != null && it.time == lesson.displayStartTime }.take(3)
@ -276,6 +293,18 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
lb.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet) lb.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet)
lb.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet) lb.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet)
lb.attendanceIcon.isVisible = attendance?.let {
val icon = attendanceManager.getAttendanceIcon(it) ?: return@let false
val color = attendanceManager.getAttendanceColor(it)
lb.attendanceIcon.setImageDrawable(
IconicsDrawable(activity, icon).apply {
colorInt = color
sizeDp = 24
}
)
true
} ?: false
lb.unread = lesson.type != Lesson.TYPE_NORMAL && lesson.showAsUnseen lb.unread = lesson.type != Lesson.TYPE_NORMAL && lesson.showAsUnseen
if (!lesson.seen) { if (!lesson.seen) {
manager.markAsSeen(lesson) manager.markAsSeen(lesson)
@ -283,6 +312,12 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
//lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD) //lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD)
lb.annotationVisible = manager.getAnnotation(activity, lesson, lb.annotation) lb.annotationVisible = manager.getAnnotation(activity, lesson, lb.annotation)
val lessonNumberMargin =
if (lb.annotationVisible) (-8).dp
else 0
lb.lessonNumberText.updateLayoutParams<LinearLayout.LayoutParams> {
updateMargins(top = lessonNumberMargin, bottom = lessonNumberMargin)
}
// The day view needs the event time ranges in the start minute/end minute format, // The day view needs the event time ranges in the start minute/end minute format,
// so calculate those here // so calculate those here

View File

@ -4,6 +4,9 @@
package pl.szczodrzynski.edziennik.utils.managers package pl.szczodrzynski.edziennik.utils.managers
import com.mikepenz.iconics.typeface.IIcon
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import eu.szkolny.font.SzkolnyFont
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -63,6 +66,17 @@ class AttendanceManager(val app: App) : CoroutineScope {
else getAttendanceColor(attendance.baseType) else getAttendanceColor(attendance.baseType)
} }
fun getAttendanceIcon(attendance: Attendance): IIcon? = when (attendance.baseType) {
Attendance.TYPE_PRESENT, Attendance.TYPE_PRESENT_CUSTOM -> CommunityMaterial.Icon.cmd_check
Attendance.TYPE_ABSENT -> CommunityMaterial.Icon.cmd_close
Attendance.TYPE_ABSENT_EXCUSED -> CommunityMaterial.Icon3.cmd_progress_close
Attendance.TYPE_RELEASED -> CommunityMaterial.Icon.cmd_account_arrow_right_outline
Attendance.TYPE_BELATED -> CommunityMaterial.Icon.cmd_clock_alert_outline
Attendance.TYPE_BELATED_EXCUSED -> CommunityMaterial.Icon.cmd_clock_check_outline
Attendance.TYPE_DAY_FREE -> SzkolnyFont.Icon.szf_umbrella_beach_outline
else -> null
}
/* _ _ _____ _____ _ __ _ /* _ _ _____ _____ _ __ _
| | | |_ _| / ____| (_)/ _(_) | | | |_ _| / ____| (_)/ _(_)
| | | | | | | (___ _ __ ___ ___ _| |_ _ ___ | | | | | | | (___ _ __ ___ ___ _| |_ _ ___

View File

@ -134,7 +134,8 @@
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:baselineAligned="false" android:baselineAligned="false"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal"
android:visibility="gone">
<TextView <TextView
android:id="@+id/shiftedText" android:id="@+id/shiftedText"
@ -290,6 +291,60 @@
</LinearLayout> </LinearLayout>
<View
android:id="@+id/attendanceDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:background="@drawable/divider"/>
<LinearLayout
android:id="@+id/attendanceLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:layout_marginTop="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceView
android:id="@+id/attendanceView"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
tools:background="@drawable/bg_rounded_8dp"
tools:backgroundTint="#f44336"
tools:gravity="center"
tools:text="nb"
tools:textSize="22sp" />
<TextView
android:id="@+id/attendanceType"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:maxLines="2"
android:ellipsize="end"
android:textSize="16sp"
tools:text="nieobecność usprawiedliweniowsza1234324" />
<ImageView
android:id="@+id/attendanceIcon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginHorizontal="8dp"
tools:srcCompat="@sample/check" />
<Button
android:id="@+id/attendanceDetails"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_lesson_attendance_details" />
</LinearLayout>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"

View File

@ -48,16 +48,16 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="top" android:layout_gravity="top"
android:paddingHorizontal="8dp" android:baselineAligned="false"
android:orientation="horizontal" android:orientation="horizontal"
android:baselineAligned="false"> android:paddingHorizontal="8dp"
android:paddingVertical="4dp">
<!--tools:background="@drawable/timetable_subject_color_rounded"--> <!--tools:background="@drawable/timetable_subject_color_rounded"-->
<TextView <TextView
android:id="@+id/subjectName" android:id="@+id/subjectName"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginVertical="4dp"
android:layout_weight="1" android:layout_weight="1"
android:ellipsize="end" android:ellipsize="end"
android:fontFamily="sans-serif-light" android:fontFamily="sans-serif-light"
@ -75,8 +75,8 @@
android:layout_height="12dp" android:layout_height="12dp"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginHorizontal="8dp" android:layout_marginHorizontal="8dp"
android:visibility="@{unread ? View.VISIBLE : View.GONE}" android:background="@drawable/unread_red_circle"
android:background="@drawable/unread_red_circle" /> android:visibility="@{unread ? View.VISIBLE : View.GONE}" />
<ImageView <ImageView
android:id="@+id/attendanceIcon" android:id="@+id/attendanceIcon"
@ -87,29 +87,18 @@
tools:srcCompat="@sample/check" tools:srcCompat="@sample/check"
tools:visibility="visible" /> 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" />
<TextView <TextView
android:id="@+id/textView6" android:id="@+id/lessonNumberText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:fontFamily="sans-serif-condensed-light" android:fontFamily="sans-serif-condensed-light"
android:includeFontPadding="false" android:includeFontPadding="false"
android:layout_marginBottom="-4dp"
android:paddingHorizontal="4dp" android:paddingHorizontal="4dp"
android:text="@{Integer.toString(lessonNumber)}" android:text="@{Integer.toString(lessonNumber)}"
android:textSize="28sp" android:textSize="28sp"
android:visibility="@{lessonNumber != null ? View.VISIBLE : View.GONE}" android:visibility="@{lessonNumber != null ? View.VISIBLE : View.GONE}"
tools:text="3"/> tools:text="3" />
<!--android:layout_marginTop="@{annotationVisible ? `-4dp` : `4dp`}"
android:layout_marginBottom="@{annotationVisible ? `-4dp` : `0dp`}"-->
</LinearLayout> </LinearLayout>
@ -149,7 +138,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="end|bottom" android:gravity="end|bottom"
android:orientation="horizontal"> android:orientation="horizontal"
android:paddingBottom="2dp">
<View <View
android:id="@+id/event3" android:id="@+id/event3"

View File

@ -1463,4 +1463,5 @@
<string name="notification_attendance_long_format">Rodzaj: %s\nTermin: %s, %s\nNr lekcji: %s\nPrzedmiot: %s\nNauczyciel: %s\nTemat lekcji: %s</string> <string name="notification_attendance_long_format">Rodzaj: %s\nTermin: %s, %s\nNr lekcji: %s\nPrzedmiot: %s\nNauczyciel: %s\nTemat lekcji: %s</string>
<string name="contributors_subtext_format" translatable="false">\@%s - %s</string> <string name="contributors_subtext_format" translatable="false">\@%s - %s</string>
<string name="contributors_headline">Najłatwiejszy sposób na korzystanie z e-dziennika.</string> <string name="contributors_headline">Najłatwiejszy sposób na korzystanie z e-dziennika.</string>
<string name="dialog_lesson_attendance_details">Szczegóły</string>
</resources> </resources>