1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2024-09-20 00:29:09 -05:00

Add dashboard (#1267)

This commit is contained in:
Rafał Borcz 2021-07-30 18:49:19 +02:00 committed by GitHub
parent 3278c11cce
commit 3422951e47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 3589 additions and 66 deletions

View File

@ -164,7 +164,7 @@ ext {
}
dependencies {
implementation "io.github.wulkanowy:sdk:bb08354"
implementation "io.github.wulkanowy:sdk:496dc01d15"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
@ -182,7 +182,7 @@ dependencies {
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.viewpager:viewpager:1.0.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation "androidx.constraintlayout:constraintlayout:2.1.0-beta02"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation "com.google.android.material:material:1.4.0"
implementation "com.github.wulkanowy:material-chips-input:2.2.0"
@ -209,6 +209,7 @@ dependencies {
implementation "com.squareup.moshi:moshi:$moshi"
implementation "com.squareup.moshi:moshi-adapters:$moshi"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi"
implementation "com.jakewharton.timber:timber:4.7.1"
implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation 'com.github.bastienpaulfr:Treessence:1.0.4'
@ -216,6 +217,7 @@ dependencies {
implementation "io.coil-kt:coil:1.3.0"
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
implementation 'com.fredporciuncula:flow-preferences:1.5.0'
playImplementation platform('com.google.firebase:firebase-bom:28.3.0')
playImplementation 'com.google.firebase:firebase-analytics-ktx'
@ -244,9 +246,9 @@ dependencies {
testImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version"
androidTestImplementation "androidx.test:core:1.3.0"
androidTestImplementation "androidx.test:runner:1.3.0"
androidTestImplementation "androidx.test.ext:junit:1.1.2"
androidTestImplementation "androidx.test:core:1.4.0"
androidTestImplementation "androidx.test:runner:1.4.0"
androidTestImplementation "androidx.test.ext:junit:1.1.3"
androidTestImplementation "io.mockk:mockk-android:$mockk"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
}

View File

@ -8,6 +8,7 @@ import androidx.preference.PreferenceManager
import com.chuckerteam.chucker.api.ChuckerCollector
import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.chuckerteam.chucker.api.RetentionManager
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -18,6 +19,7 @@ import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AppInfo
import kotlinx.coroutines.ExperimentalCoroutinesApi
import timber.log.Timber
import javax.inject.Singleton
@ -77,6 +79,12 @@ internal class RepositoryModule {
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context)
@OptIn(ExperimentalCoroutinesApi::class)
@Singleton
@Provides
fun provideFlowSharedPref(sharedPreferences: SharedPreferences) =
FlowSharedPreferences(sharedPreferences)
@Singleton
@Provides
fun provideStudentDao(database: AppDatabase) = database.studentDao

View File

@ -4,12 +4,13 @@ import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Conference
import kotlinx.coroutines.flow.Flow
import java.time.LocalDateTime
import javax.inject.Singleton
@Dao
@Singleton
interface ConferenceDao : BaseDao<Conference> {
@Query("SELECT * FROM Conferences WHERE diary_id = :diaryId AND student_id = :studentId")
fun loadAll(diaryId: Int, studentId: Int): Flow<List<Conference>>
@Query("SELECT * FROM Conferences WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :startDate")
fun loadAll(diaryId: Int, studentId: Int, startDate: LocalDateTime): Flow<List<Conference>>
}

View File

@ -25,9 +25,17 @@ class AttendanceSummaryRepository @Inject constructor(
private val cacheKey = "attendance_summary"
fun getAttendanceSummary(student: Student, semester: Semester, subjectId: Int, forceRefresh: Boolean) = networkBoundResource(
fun getAttendanceSummary(
student: Student,
semester: Semester,
subjectId: Int,
forceRefresh: Boolean
) = networkBoundResource(
mutex = saveFetchResultMutex,
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
shouldFetch = {
it.isEmpty() || forceRefresh
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
},
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)

View File

@ -13,6 +13,9 @@ import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneOffset
import javax.inject.Inject
import javax.inject.Singleton
@ -31,7 +34,8 @@ class ConferenceRepository @Inject constructor(
student: Student,
semester: Semester,
forceRefresh: Boolean,
notify: Boolean = false
notify: Boolean = false,
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)
) = networkBoundResource(
mutex = saveFetchResultMutex,
shouldFetch = {
@ -39,15 +43,13 @@ class ConferenceRepository @Inject constructor(
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
},
query = {
conferenceDb.loadAll(
semester.diaryId,
student.studentId
)
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
},
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getConferences()
.mapToEntities(semester)
.filter { it.date >= startDate }
},
saveFetchResult = { old, new ->
val conferencesToSave = (new uniqueSubtract old).onEach {
@ -60,9 +62,12 @@ class ConferenceRepository @Inject constructor(
}
)
fun getConferenceFromDatabase(semester: Semester): Flow<List<Conference>> {
return conferenceDb.loadAll(semester.diaryId, semester.studentId)
}
fun getConferenceFromDatabase(semester: Semester): Flow<List<Conference>> =
conferenceDb.loadAll(
diaryId = semester.diaryId,
studentId = semester.studentId,
startDate = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)
)
suspend fun updateConference(conference: List<Conference>) = conferenceDb.updateAll(conference)
}

View File

@ -2,16 +2,24 @@ package io.github.wulkanowy.data.repositories
import android.content.Context
import android.content.SharedPreferences
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
import com.fredporciuncula.flow.preferences.Preference
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Singleton
@OptIn(ExperimentalCoroutinesApi::class)
@Singleton
class PreferencesRepository @Inject constructor(
private val sharedPref: SharedPreferences,
private val flowSharedPref: FlowSharedPreferences,
@ApplicationContext val context: Context
) {
val startMenuIndex: Int
@ -151,6 +159,36 @@ class PreferencesRepository @Inject constructor(
R.bool.pref_default_optional_arithmetic_average
)
val selectedDashboardTilesFlow: Flow<Set<DashboardItem.Tile>>
get() = selectedDashboardTilesPreference.asFlow()
.map { set ->
set.map { DashboardItem.Tile.valueOf(it) }
.plus(DashboardItem.Tile.ACCOUNT)
.toSet()
}
var selectedDashboardTiles: Set<DashboardItem.Tile>
get() = selectedDashboardTilesPreference.get()
.map { DashboardItem.Tile.valueOf(it) }
.plus(DashboardItem.Tile.ACCOUNT)
.toSet()
set(value) {
val filteredValue = value.filterNot { it == DashboardItem.Tile.ACCOUNT }
.map { it.name }
.toSet()
selectedDashboardTilesPreference.set(filteredValue)
}
private val selectedDashboardTilesPreference: Preference<Set<String>>
get() {
val defaultSet =
context.resources.getStringArray(R.array.pref_default_dashboard_tiles).toSet()
val prefKey = context.getString(R.string.pref_key_dashboard_tiles)
return flowSharedPref.getStringSet(prefKey, defaultSet)
}
private fun getString(id: Int, default: Int) = getString(context.getString(id), default)
private fun getString(id: String, default: Int) =

View File

@ -0,0 +1,713 @@
package io.github.wulkanowy.ui.modules.dashboard
import android.annotation.SuppressLint
import android.graphics.Typeface
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.databinding.ItemDashboardAccountBinding
import io.github.wulkanowy.databinding.ItemDashboardAnnouncementsBinding
import io.github.wulkanowy.databinding.ItemDashboardConferencesBinding
import io.github.wulkanowy.databinding.ItemDashboardExamsBinding
import io.github.wulkanowy.databinding.ItemDashboardGradesBinding
import io.github.wulkanowy.databinding.ItemDashboardHomeworkBinding
import io.github.wulkanowy.databinding.ItemDashboardHorizontalGroupBinding
import io.github.wulkanowy.databinding.ItemDashboardLessonsBinding
import io.github.wulkanowy.utils.createNameInitialsDrawable
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.left
import io.github.wulkanowy.utils.nickOrName
import io.github.wulkanowy.utils.toFormattedString
import java.time.Duration
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.Timer
import javax.inject.Inject
import kotlin.concurrent.timer
class DashboardAdapter @Inject constructor() :
ListAdapter<DashboardItem, RecyclerView.ViewHolder>(DashboardAdapterDiffCallback()) {
var lessonsTimer: Timer? = null
var onAccountTileClickListener: () -> Unit = {}
var onLuckyNumberTileClickListener: () -> Unit = {}
var onMessageTileClickListener: () -> Unit = {}
var onGradeTileClickListener: () -> Unit = {}
var onAttendanceTileClickListener: () -> Unit = {}
var onLessonsTileClickListener: () -> Unit = {}
var onHomeworkTileClickListener: () -> Unit = {}
var onAnnouncementsTileClickListener: () -> Unit = {}
var onExamsTileClickListener: () -> Unit = {}
var onConferencesTileClickListener: () -> Unit = {}
override fun getItemViewType(position: Int) = getItem(position).type.ordinal
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
DashboardItem.Type.ACCOUNT.ordinal -> AccountViewHolder(
ItemDashboardAccountBinding.inflate(inflater, parent, false)
)
DashboardItem.Type.HORIZONTAL_GROUP.ordinal -> HorizontalGroupViewHolder(
ItemDashboardHorizontalGroupBinding.inflate(inflater, parent, false)
)
DashboardItem.Type.GRADES.ordinal -> GradesViewHolder(
ItemDashboardGradesBinding.inflate(inflater, parent, false)
)
DashboardItem.Type.LESSONS.ordinal -> LessonsViewHolder(
ItemDashboardLessonsBinding.inflate(inflater, parent, false)
)
DashboardItem.Type.HOMEWORK.ordinal -> HomeworkViewHolder(
ItemDashboardHomeworkBinding.inflate(inflater, parent, false)
)
DashboardItem.Type.ANNOUNCEMENTS.ordinal -> AnnouncementsViewHolder(
ItemDashboardAnnouncementsBinding.inflate(inflater, parent, false)
)
DashboardItem.Type.EXAMS.ordinal -> ExamsViewHolder(
ItemDashboardExamsBinding.inflate(inflater, parent, false)
)
DashboardItem.Type.CONFERENCES.ordinal -> ConferencesViewHolder(
ItemDashboardConferencesBinding.inflate(inflater, parent, false)
)
else -> throw IllegalArgumentException()
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is AccountViewHolder -> bindAccountViewHolder(holder, position)
is HorizontalGroupViewHolder -> bindHorizontalGroupViewHolder(holder, position)
is GradesViewHolder -> bindGradesViewHolder(holder, position)
is LessonsViewHolder -> bindLessonsViewHolder(holder, position)
is HomeworkViewHolder -> bindHomeworkViewHolder(holder, position)
is AnnouncementsViewHolder -> bindAnnouncementsViewHolder(holder, position)
is ExamsViewHolder -> bindExamsViewHolder(holder, position)
is ConferencesViewHolder -> bindConferencesViewHolder(holder, position)
}
}
fun clearTimers() {
lessonsTimer?.let {
it.cancel()
it.purge()
}
lessonsTimer = null
}
private fun bindAccountViewHolder(accountViewHolder: AccountViewHolder, position: Int) {
val item = getItem(position) as DashboardItem.Account
val student = item.student
val isLoading = item.isLoading
val avatar = student?.let {
accountViewHolder.binding.root.context.createNameInitialsDrawable(
text = it.nickOrName,
backgroundColor = it.avatarColor
)
}
with(accountViewHolder.binding) {
dashboardAccountItemContent.isVisible = !isLoading
dashboardAccountItemProgress.isVisible = isLoading
dashboardAccountItemAvatar.setImageDrawable(avatar)
dashboardAccountItemName.text = student?.nickOrName.orEmpty()
dashboardAccountItemSchoolName.text = student?.schoolName.orEmpty()
root.setOnClickListener { onAccountTileClickListener() }
}
}
@SuppressLint("SetTextI18n")
private fun bindHorizontalGroupViewHolder(
horizontalGroupViewHolder: HorizontalGroupViewHolder,
position: Int
) {
val item = getItem(position) as DashboardItem.HorizontalGroup
val unreadMessagesCount = item.unreadMessagesCount
val attendancePercentage = item.attendancePercentage
val luckyNumber = item.luckyNumber
val error = item.error
val isLoading = item.isLoading
val binding = horizontalGroupViewHolder.binding
val context = binding.root.context
val attendanceColor = when {
attendancePercentage ?: 0.0 <= ATTENDANCE_SECOND_WARNING_THRESHOLD -> {
context.getThemeAttrColor(R.attr.colorPrimary)
}
attendancePercentage ?: 0.0 <= ATTENDANCE_FIRST_WARNING_THRESHOLD -> {
context.getThemeAttrColor(R.attr.colorTimetableChange)
}
else -> context.getThemeAttrColor(R.attr.colorOnSurface)
}
with(binding.dashboardHorizontalGroupItemAttendanceValue) {
text = "%.2f%%".format(attendancePercentage)
setTextColor(attendanceColor)
}
with(binding) {
dashboardHorizontalGroupItemMessageValue.text = unreadMessagesCount.toString()
dashboardHorizontalGroupItemLuckyValue.text = if (luckyNumber == -1) {
context.getString(R.string.dashboard_horizontal_group_no_lukcy_number)
} else luckyNumber?.toString()
if (dashboardHorizontalGroupItemInfoContainer.isVisible != (error != null || isLoading)) {
dashboardHorizontalGroupItemInfoContainer.isVisible = error != null || isLoading
}
if (dashboardHorizontalGroupItemInfoProgress.isVisible != isLoading) {
dashboardHorizontalGroupItemInfoProgress.isVisible = isLoading
}
dashboardHorizontalGroupItemInfoErrorText.isVisible = error != null
with(dashboardHorizontalGroupItemLuckyContainer) {
isVisible = error == null && !isLoading && luckyNumber != null
setOnClickListener { onLuckyNumberTileClickListener() }
}
with(dashboardHorizontalGroupItemAttendanceContainer) {
isVisible = error == null && !isLoading && attendancePercentage != null
updateLayoutParams<ConstraintLayout.LayoutParams> {
matchConstraintPercentWidth = when {
luckyNumber == null && unreadMessagesCount == null -> 1.0f
luckyNumber == null || unreadMessagesCount == null -> 0.5f
else -> 0.4f
}
}
setOnClickListener { onAttendanceTileClickListener() }
}
with(dashboardHorizontalGroupItemMessageContainer) {
isVisible = error == null && !isLoading && unreadMessagesCount != null
setOnClickListener { onMessageTileClickListener() }
}
}
}
private fun bindGradesViewHolder(gradesViewHolder: GradesViewHolder, position: Int) {
val item = getItem(position) as DashboardItem.Grades
val subjectWithGrades = item.subjectWithGrades.orEmpty()
val gradeTheme = item.gradeTheme
val error = item.error
val isLoading = item.isLoading
val dashboardGradesAdapter = gradesViewHolder.adapter.apply {
this.items = subjectWithGrades.toList()
this.gradeTheme = gradeTheme.orEmpty()
}
with(gradesViewHolder.binding) {
dashboardGradesItemEmpty.isVisible =
subjectWithGrades.isEmpty() && error == null && !isLoading
dashboardGradesItemError.isVisible = error != null && !isLoading
dashboardGradesItemProgress.isVisible =
isLoading && error == null && subjectWithGrades.isEmpty()
with(dashboardGradesItemRecycler) {
adapter = dashboardGradesAdapter
layoutManager = LinearLayoutManager(context)
isVisible = subjectWithGrades.isNotEmpty() && error == null
suppressLayout(true)
}
root.setOnClickListener { onGradeTileClickListener() }
}
}
private fun bindLessonsViewHolder(lessonsViewHolder: LessonsViewHolder, position: Int) {
val item = getItem(position) as DashboardItem.Lessons
val timetableFull = item.lessons
val binding = lessonsViewHolder.binding
fun updateLessonState() {
val currentDateTime = LocalDateTime.now()
val currentDate = LocalDate.now()
val currentTimetable = timetableFull?.lessons
.orEmpty()
.filter { it.date == currentDate }
.filter { it.end.isAfter(currentDateTime) }
.filterNot { it.canceled }
val currentDayHeader =
timetableFull?.headers.orEmpty().singleOrNull { it.date == currentDate }
val tomorrowTimetable = timetableFull?.lessons.orEmpty()
.filter { it.date == currentDate.plusDays(1) }
.filterNot { it.canceled }
val tomorrowDayHeader =
timetableFull?.headers.orEmpty().singleOrNull { it.date == currentDate.plusDays(1) }
when {
currentTimetable.isNotEmpty() -> {
updateLessonView(item, currentTimetable, binding)
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
}
currentDayHeader != null && currentDayHeader.content.isNotBlank() -> {
updateLessonView(item, emptyList(), binding, currentDayHeader)
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
}
tomorrowTimetable.isNotEmpty() -> {
updateLessonView(item, tomorrowTimetable, binding)
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
}
tomorrowDayHeader != null && tomorrowDayHeader.content.isNotBlank() -> {
updateLessonView(item, emptyList(), binding, tomorrowDayHeader)
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
}
else -> {
updateLessonView(item, emptyList(), binding)
binding.dashboardLessonsItemTitleTomorrow.isVisible =
!(item.isLoading && item.error == null)
}
}
}
updateLessonState()
lessonsTimer?.cancel()
lessonsTimer = timer(period = 1000) {
Handler(Looper.getMainLooper()).post { updateLessonState() }
}
binding.root.setOnClickListener { onLessonsTileClickListener() }
}
private fun updateLessonView(
item: DashboardItem.Lessons,
timetableToShow: List<Timetable>,
binding: ItemDashboardLessonsBinding,
header: TimetableHeader? = null,
) {
val currentDateTime = LocalDateTime.now()
val nextLessons = timetableToShow.filter { it.end.isAfter(currentDateTime) }
.sortedBy { it.start }
with(binding) {
dashboardLessonsItemEmpty.isVisible =
(timetableToShow.isEmpty() || nextLessons.isEmpty()) && item.error == null && header == null && !item.isLoading
dashboardLessonsItemError.isVisible = item.error != null && !item.isLoading
dashboardLessonsItemProgress.isVisible =
item.isLoading && (timetableToShow.isEmpty() || nextLessons.isEmpty()) && item.error == null && header == null
val secondLesson = nextLessons.getOrNull(1)
val firstLesson = nextLessons.getOrNull(0)
updateFirstLessonView(binding, firstLesson, currentDateTime)
updateSecondLesson(binding, firstLesson, secondLesson)
updateLessonSummary(binding, nextLessons)
updateLessonHeader(binding, header)
}
}
private fun updateFirstLessonView(
binding: ItemDashboardLessonsBinding,
firstLesson: Timetable?,
currentDateTime: LocalDateTime
) {
val context = binding.root.context
val sansSerifFont = Typeface.create("sans-serif", Typeface.NORMAL)
val sansSerifMediumFont = Typeface.create("sans-serif-medium", Typeface.NORMAL)
with(binding) {
dashboardLessonsItemFirstTitle.isVisible = firstLesson != null
dashboardLessonsItemFirstTime.isVisible = firstLesson != null
dashboardLessonsItemFirstTimeRange.isVisible = firstLesson != null
dashboardLessonsItemFirstValue.isVisible = firstLesson != null
}
firstLesson ?: return
val minutesToStartLesson =
Duration.between(currentDateTime, firstLesson.start).toMinutes()
val isFirstTimeVisible: Boolean
val isFirstTimeRangeVisible: Boolean
val firstTimeText: String
val firstTimeRangeText: String
val firstTitleText: String
val firstTitleAndValueTextColor: Int
val firstTitleAndValueTextFont: Typeface
if (currentDateTime.isBefore(firstLesson.start)) {
if (minutesToStartLesson > 60) {
val formattedStartTime = firstLesson.start.toFormattedString("HH:mm")
val formattedEndTime = firstLesson.end.toFormattedString("HH:mm")
firstTimeRangeText = "${formattedStartTime}-${formattedEndTime}"
firstTimeText = ""
isFirstTimeRangeVisible = true
isFirstTimeVisible = false
} else {
firstTimeText = context.resources.getQuantityString(
R.plurals.dashboard_timetable_first_lesson_time_in_minutes,
minutesToStartLesson.toInt(),
minutesToStartLesson
)
firstTimeRangeText = ""
isFirstTimeRangeVisible = false
isFirstTimeVisible = true
}
when {
minutesToStartLesson < 60 -> {
firstTitleAndValueTextColor = context.getThemeAttrColor(R.attr.colorPrimary)
firstTitleAndValueTextFont = sansSerifMediumFont
firstTitleText =
context.getString(R.string.dashboard_timetable_first_lesson_title_moment)
}
minutesToStartLesson < 240 -> {
firstTitleAndValueTextColor =
context.getThemeAttrColor(R.attr.colorOnSurface)
firstTitleAndValueTextFont = sansSerifFont
firstTitleText =
context.getString(R.string.dashboard_timetable_first_lesson_title_soon)
}
else -> {
firstTitleAndValueTextColor =
context.getThemeAttrColor(R.attr.colorOnSurface)
firstTitleAndValueTextFont = sansSerifFont
firstTitleText =
context.getString(R.string.dashboard_timetable_first_lesson_title_first)
}
}
} else {
val minutesToEndLesson = firstLesson.left!!.toMinutes()
firstTimeText = context.resources.getQuantityString(
R.plurals.dashboard_timetable_first_lesson_time_more_minutes,
minutesToEndLesson.toInt(),
minutesToEndLesson
)
firstTimeRangeText = ""
isFirstTimeRangeVisible = false
isFirstTimeVisible = true
firstTitleAndValueTextColor = context.getThemeAttrColor(R.attr.colorPrimary)
firstTitleAndValueTextFont = sansSerifMediumFont
firstTitleText = context.getString(R.string.dashboard_timetable_first_lesson_title_now)
}
with(binding.dashboardLessonsItemFirstTime) {
isVisible = isFirstTimeVisible
text = firstTimeText
}
with(binding.dashboardLessonsItemFirstTimeRange) {
isVisible = isFirstTimeRangeVisible
text = firstTimeRangeText
}
with(binding.dashboardLessonsItemFirstTitle) {
setTextColor(firstTitleAndValueTextColor)
typeface = firstTitleAndValueTextFont
text = firstTitleText
}
with(binding.dashboardLessonsItemFirstValue) {
setTextColor(firstTitleAndValueTextColor)
typeface = firstTitleAndValueTextFont
text = context.getString(
R.string.dashboard_timetable_lesson_value,
firstLesson.subject,
firstLesson.room
)
}
}
private fun updateSecondLesson(
binding: ItemDashboardLessonsBinding,
firstLesson: Timetable?,
secondLesson: Timetable?
) {
val context = binding.root.context
val formattedStartTime = secondLesson?.start?.toFormattedString("HH:mm")
val formattedEndTime = secondLesson?.end?.toFormattedString("HH:mm")
val secondTimeText = "${formattedStartTime}-${formattedEndTime}"
val secondValueText = if (secondLesson != null) {
context.getString(
R.string.dashboard_timetable_lesson_value,
secondLesson.subject,
secondLesson.room
)
} else {
context.getString(R.string.dashboard_timetable_second_lesson_value_end)
}
with(binding.dashboardLessonsItemSecondTime) {
isVisible = secondLesson != null
text = secondTimeText
}
with(binding.dashboardLessonsItemSecondValue) {
isVisible = !(secondLesson == null && firstLesson == null)
text = secondValueText
}
binding.dashboardLessonsItemSecondTitle.isVisible =
!(secondLesson == null && firstLesson == null)
}
private fun updateLessonSummary(
binding: ItemDashboardLessonsBinding,
nextLessons: List<Timetable>
) {
val context = binding.root.context
val formattedEndTime = nextLessons.lastOrNull()?.end?.toFormattedString("HH:mm")
with(binding) {
dashboardLessonsItemThirdTime.isVisible =
nextLessons.size > LESSON_SUMMARY_VISIBILITY_THRESHOLD
dashboardLessonsItemThirdTitle.isVisible =
nextLessons.size > LESSON_SUMMARY_VISIBILITY_THRESHOLD
dashboardLessonsItemThirdValue.isVisible =
nextLessons.size > LESSON_SUMMARY_VISIBILITY_THRESHOLD
dashboardLessonsItemDivider.isVisible =
nextLessons.size > LESSON_SUMMARY_VISIBILITY_THRESHOLD
dashboardLessonsItemThirdValue.text = context.resources.getQuantityString(
R.plurals.dashboard_timetable_third_value,
nextLessons.size - LESSON_SUMMARY_VISIBILITY_THRESHOLD,
nextLessons.size - LESSON_SUMMARY_VISIBILITY_THRESHOLD
)
dashboardLessonsItemThirdTime.text =
context.getString(R.string.dashboard_timetable_third_time, formattedEndTime)
}
}
private fun updateLessonHeader(
binding: ItemDashboardLessonsBinding,
header: TimetableHeader?
) {
with(binding.dashboardLessonsItemDayHeader) {
isVisible = header != null
text = header?.content
}
}
private fun bindHomeworkViewHolder(homeworkViewHolder: HomeworkViewHolder, position: Int) {
val item = getItem(position) as DashboardItem.Homework
val homeworkList = item.homework.orEmpty()
val error = item.error
val isLoading = item.isLoading
val context = homeworkViewHolder.binding.root.context
val homeworkAdapter = homeworkViewHolder.adapter.apply {
this.items = homeworkList.take(MAX_VISIBLE_LIST_ITEMS)
}
with(homeworkViewHolder.binding) {
dashboardHomeworkItemEmpty.isVisible =
homeworkList.isEmpty() && error == null && !isLoading
dashboardHomeworkItemError.isVisible = error != null && !isLoading
dashboardHomeworkItemProgress.isVisible =
isLoading && error == null && homeworkList.isEmpty()
dashboardHomeworkItemDivider.isVisible = homeworkList.size > MAX_VISIBLE_LIST_ITEMS
dashboardHomeworkItemMore.isVisible = homeworkList.size > MAX_VISIBLE_LIST_ITEMS
dashboardHomeworkItemMore.text = context.resources.getQuantityString(
R.plurals.dashboard_homework_more,
homeworkList.size - MAX_VISIBLE_LIST_ITEMS,
homeworkList.size - MAX_VISIBLE_LIST_ITEMS
)
with(dashboardHomeworkItemRecycler) {
adapter = homeworkAdapter
layoutManager = LinearLayoutManager(context)
isVisible = homeworkList.isNotEmpty() && error == null
suppressLayout(true)
}
root.setOnClickListener { onHomeworkTileClickListener() }
}
}
private fun bindAnnouncementsViewHolder(
announcementsViewHolder: AnnouncementsViewHolder,
position: Int
) {
val item = getItem(position) as DashboardItem.Announcements
val schoolAnnouncementList = item.announcement.orEmpty()
val error = item.error
val isLoading = item.isLoading
val context = announcementsViewHolder.binding.root.context
val schoolAnnouncementsAdapter = announcementsViewHolder.adapter.apply {
this.items = schoolAnnouncementList.take(MAX_VISIBLE_LIST_ITEMS)
}
with(announcementsViewHolder.binding) {
dashboardAnnouncementsItemEmpty.isVisible =
schoolAnnouncementList.isEmpty() && error == null && !isLoading
dashboardAnnouncementsItemError.isVisible = error != null && !isLoading
dashboardAnnouncementsItemProgress.isVisible =
isLoading && error == null && schoolAnnouncementList.isEmpty()
dashboardAnnouncementsItemDivider.isVisible =
schoolAnnouncementList.size > MAX_VISIBLE_LIST_ITEMS
dashboardAnnouncementsItemMore.isVisible =
schoolAnnouncementList.size > MAX_VISIBLE_LIST_ITEMS
dashboardAnnouncementsItemMore.text = context.resources.getQuantityString(
R.plurals.dashboard_announcements_more,
schoolAnnouncementList.size - MAX_VISIBLE_LIST_ITEMS,
schoolAnnouncementList.size - MAX_VISIBLE_LIST_ITEMS
)
with(dashboardAnnouncementsItemRecycler) {
layoutManager = LinearLayoutManager(context)
adapter = schoolAnnouncementsAdapter
isVisible = schoolAnnouncementList.isNotEmpty() && error == null
suppressLayout(true)
}
root.setOnClickListener { onAnnouncementsTileClickListener() }
}
}
private fun bindExamsViewHolder(examsViewHolder: ExamsViewHolder, position: Int) {
val item = getItem(position) as DashboardItem.Exams
val exams = item.exams.orEmpty()
val error = item.error
val isLoading = item.isLoading
val context = examsViewHolder.binding.root.context
val examAdapter = examsViewHolder.adapter.apply {
this.items = exams.take(MAX_VISIBLE_LIST_ITEMS)
}
with(examsViewHolder.binding) {
dashboardExamsItemEmpty.isVisible = exams.isEmpty() && error == null && !isLoading
dashboardExamsItemError.isVisible = error != null && !isLoading
dashboardExamsItemProgress.isVisible = isLoading && error == null && exams.isEmpty()
dashboardExamsItemDivider.isVisible = exams.size > MAX_VISIBLE_LIST_ITEMS
dashboardExamsItemMore.isVisible = exams.size > MAX_VISIBLE_LIST_ITEMS
dashboardExamsItemMore.text = context.resources.getQuantityString(
R.plurals.dashboard_exams_more,
exams.size - MAX_VISIBLE_LIST_ITEMS,
exams.size - MAX_VISIBLE_LIST_ITEMS
)
with(dashboardExamsItemRecycler) {
layoutManager = LinearLayoutManager(context)
adapter = examAdapter
isVisible = exams.isNotEmpty() && error == null
suppressLayout(true)
}
root.setOnClickListener { onExamsTileClickListener() }
}
}
private fun bindConferencesViewHolder(
conferencesViewHolder: ConferencesViewHolder,
position: Int
) {
val item = getItem(position) as DashboardItem.Conferences
val conferences = item.conferences.orEmpty()
val error = item.error
val isLoading = item.isLoading
val context = conferencesViewHolder.binding.root.context
val conferenceAdapter = conferencesViewHolder.adapter.apply {
this.items = conferences.take(MAX_VISIBLE_LIST_ITEMS)
}
with(conferencesViewHolder.binding) {
dashboardConferencesItemEmpty.isVisible =
conferences.isEmpty() && error == null && !isLoading
dashboardConferencesItemError.isVisible = error != null && !isLoading
dashboardConferencesItemProgress.isVisible =
isLoading && error == null && conferences.isEmpty()
dashboardConferencesItemDivider.isVisible = conferences.size > MAX_VISIBLE_LIST_ITEMS
dashboardConferencesItemMore.isVisible = conferences.size > MAX_VISIBLE_LIST_ITEMS
dashboardConferencesItemMore.text = context.resources.getQuantityString(
R.plurals.dashboard_conference_more,
conferences.size - MAX_VISIBLE_LIST_ITEMS,
conferences.size - MAX_VISIBLE_LIST_ITEMS
)
with(dashboardConferencesItemRecycler) {
layoutManager = LinearLayoutManager(context)
adapter = conferenceAdapter
isVisible = conferences.isNotEmpty() && error == null
suppressLayout(true)
}
root.setOnClickListener { onConferencesTileClickListener() }
}
}
class AccountViewHolder(val binding: ItemDashboardAccountBinding) :
RecyclerView.ViewHolder(binding.root)
class HorizontalGroupViewHolder(val binding: ItemDashboardHorizontalGroupBinding) :
RecyclerView.ViewHolder(binding.root)
class GradesViewHolder(val binding: ItemDashboardGradesBinding) :
RecyclerView.ViewHolder(binding.root) {
val adapter by lazy { DashboardGradesAdapter() }
}
class LessonsViewHolder(val binding: ItemDashboardLessonsBinding) :
RecyclerView.ViewHolder(binding.root)
class HomeworkViewHolder(val binding: ItemDashboardHomeworkBinding) :
RecyclerView.ViewHolder(binding.root) {
val adapter by lazy { DashboardHomeworkAdapter() }
}
class AnnouncementsViewHolder(val binding: ItemDashboardAnnouncementsBinding) :
RecyclerView.ViewHolder(binding.root) {
val adapter by lazy { DashboardAnnouncementsAdapter() }
}
class ExamsViewHolder(val binding: ItemDashboardExamsBinding) :
RecyclerView.ViewHolder(binding.root) {
val adapter by lazy { DashboardExamsAdapter() }
}
class ConferencesViewHolder(val binding: ItemDashboardConferencesBinding) :
RecyclerView.ViewHolder(binding.root) {
val adapter by lazy { DashboardConferencesAdapter() }
}
class DashboardAdapterDiffCallback : DiffUtil.ItemCallback<DashboardItem>() {
override fun areItemsTheSame(oldItem: DashboardItem, newItem: DashboardItem) =
oldItem.type == newItem.type
override fun areContentsTheSame(oldItem: DashboardItem, newItem: DashboardItem) =
oldItem == newItem
}
private companion object {
private const val LESSON_SUMMARY_VISIBILITY_THRESHOLD = 2
private const val MAX_VISIBLE_LIST_ITEMS = 5
private const val ATTENDANCE_FIRST_WARNING_THRESHOLD = 75.0
private const val ATTENDANCE_SECOND_WARNING_THRESHOLD = 50.0
}
}

View File

@ -0,0 +1,36 @@
package io.github.wulkanowy.ui.modules.dashboard
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.databinding.SubitemDashboardAnnouncementsBinding
import io.github.wulkanowy.utils.toFormattedString
class DashboardAnnouncementsAdapter :
RecyclerView.Adapter<DashboardAnnouncementsAdapter.ViewHolder>() {
var items = emptyList<SchoolAnnouncement>()
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
SubitemDashboardAnnouncementsBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
with(holder.binding) {
dashboardHomeworkSubitemTime.text = item.date.toFormattedString()
dashboardHomeworkSubitemTitle.text = item.subject
}
}
class ViewHolder(val binding: SubitemDashboardAnnouncementsBinding) :
RecyclerView.ViewHolder(binding.root)
}

View File

@ -0,0 +1,36 @@
package io.github.wulkanowy.ui.modules.dashboard
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.databinding.SubitemDashboardConferencesBinding
import io.github.wulkanowy.utils.toFormattedString
class DashboardConferencesAdapter :
RecyclerView.Adapter<DashboardConferencesAdapter.ViewHolder>() {
var items = emptyList<Conference>()
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
SubitemDashboardConferencesBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
with(holder.binding) {
dashboardHomeworkSubitemTime.text = item.date.toFormattedString("HH:mm dd.MM.yyyy")
dashboardHomeworkSubitemTitle.text = item.title
}
}
class ViewHolder(val binding: SubitemDashboardConferencesBinding) :
RecyclerView.ViewHolder(binding.root)
}

View File

@ -0,0 +1,59 @@
package io.github.wulkanowy.ui.modules.dashboard
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.databinding.SubitemDashboardExamsBinding
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.toFormattedString
import java.time.LocalDate
class DashboardExamsAdapter :
RecyclerView.Adapter<DashboardExamsAdapter.ViewHolder>() {
var items = emptyList<Exam>()
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
SubitemDashboardExamsBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
val context = holder.binding.root.context
val primaryWarningTextColor = context.getThemeAttrColor(
if (item.date == LocalDate.now()) {
R.attr.colorPrimary
} else {
android.R.attr.textColorPrimary
}
)
val secondaryWarningTextColor = context.getThemeAttrColor(
if (item.date == LocalDate.now()) {
R.attr.colorPrimary
} else {
android.R.attr.textColorSecondary
}
)
with(holder.binding) {
dashboardHomeworkSubitemTime.text = item.date.toFormattedString("dd.MM")
dashboardHomeworkSubitemTime.setTextColor(secondaryWarningTextColor)
dashboardHomeworkSubitemTitle.text = "${item.type} - ${item.subject}"
dashboardHomeworkSubitemTitle.setTextColor(primaryWarningTextColor)
}
}
class ViewHolder(val binding: SubitemDashboardExamsBinding) :
RecyclerView.ViewHolder(binding.root)
}

View File

@ -0,0 +1,180 @@
package io.github.wulkanowy.ui.modules.dashboard
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.databinding.FragmentDashboardBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.account.AccountFragment
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
import io.github.wulkanowy.ui.modules.exam.ExamFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import io.github.wulkanowy.utils.capitalise
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.toFormattedString
import java.time.LocalDate
import javax.inject.Inject
@AndroidEntryPoint
class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragment_dashboard),
DashboardView, MainView.TitledView, MainView.MainChildView {
@Inject
lateinit var presenter: DashboardPresenter
@Inject
lateinit var dashboardAdapter: DashboardAdapter
override val titleStringId get() = R.string.dashboard_title
override var subtitleString =
LocalDate.now().toFormattedString("EEEE, d MMMM yyyy").capitalise()
companion object {
fun newInstance() = DashboardFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentDashboardBinding.bind(view)
presenter.onAttachView(this)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.action_menu_dashboard, menu)
}
override fun initView() {
val mainActivity = requireActivity() as MainActivity
dashboardAdapter.apply {
onAccountTileClickListener = { mainActivity.pushView(AccountFragment.newInstance()) }
onLuckyNumberTileClickListener = {
mainActivity.pushView(LuckyNumberFragment.newInstance())
}
onMessageTileClickListener = { mainActivity.pushView(MessageFragment.newInstance()) }
onAttendanceTileClickListener = {
mainActivity.pushView(AttendanceFragment.newInstance())
}
onLessonsTileClickListener = { mainActivity.pushView(TimetableFragment.newInstance()) }
onGradeTileClickListener = { mainActivity.pushView(GradeFragment.newInstance()) }
onHomeworkTileClickListener = { mainActivity.pushView(HomeworkFragment.newInstance()) }
onAnnouncementsTileClickListener = {
mainActivity.pushView(SchoolAnnouncementFragment.newInstance())
}
onExamsTileClickListener = { mainActivity.pushView(ExamFragment.newInstance()) }
onConferencesTileClickListener = {
mainActivity.pushView(ConferenceFragment.newInstance())
}
}
with(binding) {
dashboardErrorRetry.setOnClickListener { presenter.onRetry() }
dashboardErrorDetails.setOnClickListener { presenter.onDetailsClick() }
dashboardSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
dashboardSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
dashboardSwipe.setProgressBackgroundColorSchemeColor(
requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)
)
with(dashboardRecycler) {
layoutManager = LinearLayoutManager(context)
adapter = dashboardAdapter
(itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.dashboard_menu_tiles -> presenter.onDashboardTileSettingsSelected()
else -> false
}
}
override fun showDashboardTileSettings(selectedItems: List<DashboardItem.Tile>) {
val entries = requireContext().resources.getStringArray(R.array.dashboard_tile_entries)
val values = requireContext().resources.getStringArray(R.array.dashboard_tile_values)
val selectedItemsState = values.map { value -> selectedItems.any { it.name == value } }
AlertDialog.Builder(requireContext())
.setTitle(R.string.pref_dashboard_appearance_tiles_title)
.setMultiChoiceItems(entries, selectedItemsState.toBooleanArray()) { _, _, _ -> }
.setPositiveButton(android.R.string.ok) { dialog, _ ->
val selectedState = (dialog as AlertDialog).listView.checkedItemPositions
val selectedValues = values.filterIndexed { index, _ -> selectedState[index] }
presenter.onDashboardTileSettingSelected(selectedValues)
}
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
}
override fun updateData(data: List<DashboardItem>) {
dashboardAdapter.submitList(data.toMutableList())
}
override fun showMessage(text: String) {
//Empty function to avoid message flood
}
override fun showRefresh(show: Boolean) {
binding.dashboardSwipe.isRefreshing = show
}
override fun showProgress(show: Boolean) {
binding.dashboardProgress.isVisible = show
}
override fun showContent(show: Boolean) {
binding.dashboardRecycler.isVisible = show
}
override fun showErrorView(show: Boolean) {
binding.dashboardErrorContainer.isVisible = show
}
override fun setErrorDetails(message: String) {
binding.dashboardErrorMessage.text = message
}
override fun resetView() {
binding.dashboardRecycler.smoothScrollToPosition(0)
}
override fun popViewToRoot() {
(requireActivity() as MainActivity).popView(20)
}
override fun onFragmentReselected() {
if (::presenter.isInitialized) presenter.onViewReselected()
}
override fun onDestroyView() {
dashboardAdapter.clearTimers()
presenter.onDetachView()
super.onDestroyView()
}
}

View File

@ -0,0 +1,49 @@
package io.github.wulkanowy.ui.modules.dashboard
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.databinding.SubitemDashboardGradesBinding
import io.github.wulkanowy.databinding.SubitemDashboardSmallGradeBinding
import io.github.wulkanowy.utils.getBackgroundColor
class DashboardGradesAdapter : RecyclerView.Adapter<DashboardGradesAdapter.ViewHolder>() {
var items = listOf<Pair<String, List<Grade>>>()
var gradeTheme = ""
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
SubitemDashboardGradesBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val (subject, grades) = items[position]
val context = holder.binding.root.context
with(holder.binding) {
dashboardGradesSubitemTitle.text = subject
grades.forEach {
val subitemBinding = SubitemDashboardSmallGradeBinding.inflate(
LayoutInflater.from(context),
dashboardGradesSubitemGradeContainer,
false
)
with(subitemBinding.dashboardSmallGradeSubitemValue) {
text = it.entry
setBackgroundResource(it.getBackgroundColor(gradeTheme))
}
dashboardGradesSubitemGradeContainer.addView(subitemBinding.root)
}
}
}
class ViewHolder(val binding: SubitemDashboardGradesBinding) :
RecyclerView.ViewHolder(binding.root)
}

View File

@ -0,0 +1,56 @@
package io.github.wulkanowy.ui.modules.dashboard
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.databinding.SubitemDashboardHomeworkBinding
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.toFormattedString
import java.time.LocalDate
class DashboardHomeworkAdapter : RecyclerView.Adapter<DashboardHomeworkAdapter.ViewHolder>() {
var items = emptyList<Homework>()
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
SubitemDashboardHomeworkBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
val context = holder.binding.root.context
val formattedDate = item.date.toFormattedString("dd.MM")
val primaryWarningTextColor = context.getThemeAttrColor(
if (item.date == LocalDate.now()) {
R.attr.colorPrimary
} else {
android.R.attr.textColorPrimary
}
)
val secondaryWarningTextColor = context.getThemeAttrColor(
if (item.date == LocalDate.now()) {
R.attr.colorPrimary
} else {
android.R.attr.textColorSecondary
}
)
with(holder.binding) {
dashboardHomeworkSubitemTitle.text = "${item.subject} - ${item.content}"
dashboardHomeworkSubitemTitle.setTextColor(primaryWarningTextColor)
dashboardHomeworkSubitemTime.text =
context.getString(R.string.dashboard_homework_time, formattedDate)
dashboardHomeworkSubitemTime.setTextColor(secondaryWarningTextColor)
}
}
class ViewHolder(val binding: SubitemDashboardHomeworkBinding) :
RecyclerView.ViewHolder(binding.root)
}

View File

@ -0,0 +1,134 @@
package io.github.wulkanowy.ui.modules.dashboard
import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.pojos.TimetableFull
import io.github.wulkanowy.data.db.entities.Homework as EntitiesHomework
sealed class DashboardItem(val type: Type) {
abstract val error: Throwable?
abstract val isLoading: Boolean
abstract val isDataLoaded: Boolean
data class Account(
val student: Student? = null,
override val error: Throwable? = null,
override val isLoading: Boolean = false
) : DashboardItem(Type.ACCOUNT) {
override val isDataLoaded get() = student != null
}
data class HorizontalGroup(
val unreadMessagesCount: Int? = null,
val attendancePercentage: Double? = null,
val luckyNumber: Int? = null,
override val error: Throwable? = null,
override val isLoading: Boolean = false
) : DashboardItem(Type.HORIZONTAL_GROUP) {
override val isDataLoaded
get() = unreadMessagesCount != null || attendancePercentage != null || luckyNumber != null
}
data class Grades(
val subjectWithGrades: Map<String, List<Grade>>? = null,
val gradeTheme: String? = null,
override val error: Throwable? = null,
override val isLoading: Boolean = false
) : DashboardItem(Type.GRADES) {
override val isDataLoaded get() = subjectWithGrades != null
}
data class Lessons(
val lessons: TimetableFull? = null,
override val error: Throwable? = null,
override val isLoading: Boolean = false
) : DashboardItem(Type.LESSONS) {
override val isDataLoaded get() = lessons != null
}
data class Homework(
val homework: List<EntitiesHomework>? = null,
override val error: Throwable? = null,
override val isLoading: Boolean = false
) : DashboardItem(Type.HOMEWORK) {
override val isDataLoaded get() = homework != null
}
data class Announcements(
val announcement: List<SchoolAnnouncement>? = null,
override val error: Throwable? = null,
override val isLoading: Boolean = false
) : DashboardItem(Type.ANNOUNCEMENTS) {
override val isDataLoaded get() = announcement != null
}
data class Exams(
val exams: List<Exam>? = null,
override val error: Throwable? = null,
override val isLoading: Boolean = false
) : DashboardItem(Type.EXAMS) {
override val isDataLoaded get() = exams != null
}
data class Conferences(
val conferences: List<Conference>? = null,
override val error: Throwable? = null,
override val isLoading: Boolean = false
) : DashboardItem(Type.CONFERENCES) {
override val isDataLoaded get() = conferences != null
}
enum class Type {
ACCOUNT,
HORIZONTAL_GROUP,
LESSONS,
GRADES,
HOMEWORK,
ANNOUNCEMENTS,
EXAMS,
CONFERENCES,
ADS
}
enum class Tile {
ACCOUNT,
LUCKY_NUMBER,
MESSAGES,
ATTENDANCE,
LESSONS,
GRADES,
HOMEWORK,
ANNOUNCEMENTS,
EXAMS,
CONFERENCES,
ADS
}
}
fun DashboardItem.Tile.toDashboardItemType() = when (this) {
DashboardItem.Tile.ACCOUNT -> DashboardItem.Type.ACCOUNT
DashboardItem.Tile.LUCKY_NUMBER -> DashboardItem.Type.HORIZONTAL_GROUP
DashboardItem.Tile.MESSAGES -> DashboardItem.Type.HORIZONTAL_GROUP
DashboardItem.Tile.ATTENDANCE -> DashboardItem.Type.HORIZONTAL_GROUP
DashboardItem.Tile.LESSONS -> DashboardItem.Type.LESSONS
DashboardItem.Tile.GRADES -> DashboardItem.Type.GRADES
DashboardItem.Tile.HOMEWORK -> DashboardItem.Type.HOMEWORK
DashboardItem.Tile.ANNOUNCEMENTS -> DashboardItem.Type.ANNOUNCEMENTS
DashboardItem.Tile.EXAMS -> DashboardItem.Type.EXAMS
DashboardItem.Tile.CONFERENCES -> DashboardItem.Type.CONFERENCES
DashboardItem.Tile.ADS -> DashboardItem.Type.ADS
}

View File

@ -0,0 +1,684 @@
package io.github.wulkanowy.ui.modules.dashboard
import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
import io.github.wulkanowy.data.repositories.ConferenceRepository
import io.github.wulkanowy.data.repositories.ExamRepository
import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.HomeworkRepository
import io.github.wulkanowy.data.repositories.LuckyNumberRepository
import io.github.wulkanowy.data.repositories.MessageRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.calculatePercentage
import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.flowWithResourceIn
import io.github.wulkanowy.utils.nextOrSameSchoolDay
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
import java.time.LocalDate
import java.time.LocalDateTime
import javax.inject.Inject
class DashboardPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val luckyNumberRepository: LuckyNumberRepository,
private val gradeRepository: GradeRepository,
private val semesterRepository: SemesterRepository,
private val messageRepository: MessageRepository,
private val attendanceSummaryRepository: AttendanceSummaryRepository,
private val timetableRepository: TimetableRepository,
private val homeworkRepository: HomeworkRepository,
private val examRepository: ExamRepository,
private val conferenceRepository: ConferenceRepository,
private val preferencesRepository: PreferencesRepository,
private val schoolAnnouncementRepository: SchoolAnnouncementRepository
) : BasePresenter<DashboardView>(errorHandler, studentRepository) {
private val dashboardItemLoadedList = mutableListOf<DashboardItem>()
private val dashboardItemRefreshLoadedList = mutableListOf<DashboardItem>()
private lateinit var dashboardItemsToLoad: Set<DashboardItem.Type>
private var dashboardTilesToLoad: Set<DashboardItem.Tile> = emptySet()
private lateinit var lastError: Throwable
override fun onAttachView(view: DashboardView) {
super.onAttachView(view)
with(view) {
initView()
showProgress(true)
showContent(false)
}
preferencesRepository.selectedDashboardTilesFlow
.onEach { loadData(tilesToLoad = it) }
.launch("dashboard_pref")
}
fun loadData(forceRefresh: Boolean = false, tilesToLoad: Set<DashboardItem.Tile>) {
val oldDashboardDataToLoad = dashboardTilesToLoad
dashboardTilesToLoad = tilesToLoad
dashboardItemsToLoad = dashboardTilesToLoad.map { it.toDashboardItemType() }.toSet()
removeUnselectedTiles()
val newTileList = generateTileListToLoad(oldDashboardDataToLoad, forceRefresh)
loadTiles(forceRefresh, newTileList)
}
private fun removeUnselectedTiles() {
val isLuckyNumberToLoad =
dashboardTilesToLoad.any { it == DashboardItem.Tile.LUCKY_NUMBER }
val isMessagesToLoad =
dashboardTilesToLoad.any { it == DashboardItem.Tile.MESSAGES }
val isAttendanceToLoad =
dashboardTilesToLoad.any { it == DashboardItem.Tile.ATTENDANCE }
dashboardItemLoadedList.removeAll { loadedTile -> dashboardItemsToLoad.none { it == loadedTile.type } }
val horizontalGroup =
dashboardItemLoadedList.find { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup?
if (horizontalGroup != null) {
val horizontalIndex = dashboardItemLoadedList.indexOf(horizontalGroup)
dashboardItemLoadedList.remove(horizontalGroup)
var updatedHorizontalGroup = horizontalGroup
if (horizontalGroup.luckyNumber != null && !isLuckyNumberToLoad) {
updatedHorizontalGroup = updatedHorizontalGroup.copy(luckyNumber = null)
}
if (horizontalGroup.attendancePercentage != null && !isAttendanceToLoad) {
updatedHorizontalGroup = updatedHorizontalGroup.copy(attendancePercentage = null)
}
if (horizontalGroup.unreadMessagesCount != null && !isMessagesToLoad) {
updatedHorizontalGroup = updatedHorizontalGroup.copy(unreadMessagesCount = null)
}
if (horizontalGroup.error != null) {
updatedHorizontalGroup = updatedHorizontalGroup.copy(error = null, isLoading = true)
}
dashboardItemLoadedList.add(horizontalIndex, updatedHorizontalGroup)
}
view?.updateData(dashboardItemLoadedList)
}
private fun loadTiles(forceRefresh: Boolean, tileList: List<DashboardItem.Tile>) {
tileList.forEach {
when (it) {
DashboardItem.Tile.ACCOUNT -> loadCurrentAccount(forceRefresh)
DashboardItem.Tile.LUCKY_NUMBER -> loadLuckyNumber(forceRefresh)
DashboardItem.Tile.MESSAGES -> loadMessages(forceRefresh)
DashboardItem.Tile.ATTENDANCE -> loadAttendance(forceRefresh)
DashboardItem.Tile.LESSONS -> loadLessons(forceRefresh)
DashboardItem.Tile.GRADES -> loadGrades(forceRefresh)
DashboardItem.Tile.HOMEWORK -> loadHomework(forceRefresh)
DashboardItem.Tile.ANNOUNCEMENTS -> loadSchoolAnnouncements(forceRefresh)
DashboardItem.Tile.EXAMS -> loadExams(forceRefresh)
DashboardItem.Tile.CONFERENCES -> loadConferences(forceRefresh)
DashboardItem.Tile.ADS -> TODO()
}
}
}
private fun generateTileListToLoad(
oldDashboardTileToLoad: Set<DashboardItem.Tile>,
forceRefresh: Boolean
) = dashboardTilesToLoad.filter { newTileToLoad ->
oldDashboardTileToLoad.none { it == newTileToLoad } || forceRefresh
}
fun onSwipeRefresh() {
Timber.i("Force refreshing the dashboard")
loadData(true, preferencesRepository.selectedDashboardTiles)
}
fun onRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
loadData(true, preferencesRepository.selectedDashboardTiles)
}
fun onViewReselected() {
Timber.i("Dashboard view is reselected")
view?.run {
resetView()
popViewToRoot()
}
}
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
fun onDashboardTileSettingsSelected(): Boolean {
view?.showDashboardTileSettings(preferencesRepository.selectedDashboardTiles.toList())
return true
}
fun onDashboardTileSettingSelected(selectedItems: List<String>) {
preferencesRepository.selectedDashboardTiles = selectedItems.map {
DashboardItem.Tile.valueOf(it)
}.toSet()
}
private fun loadCurrentAccount(forceRefresh: Boolean) {
flowWithResource { studentRepository.getCurrentStudent(false) }
.onEach {
when (it.status) {
Status.LOADING -> {
Timber.i("Loading dashboard account data started")
if (forceRefresh) return@onEach
updateData(DashboardItem.Account(it.data, isLoading = true), forceRefresh)
}
Status.SUCCESS -> {
Timber.i("Loading dashboard account result: Success")
updateData(DashboardItem.Account(it.data), forceRefresh)
}
Status.ERROR -> {
Timber.i("Loading dashboard account result: An exception occurred")
errorHandler.dispatch(it.error!!)
updateData(DashboardItem.Account(error = it.error), forceRefresh)
}
}
}
.launch("dashboard_account")
}
private fun loadLuckyNumber(forceRefresh: Boolean) {
flowWithResourceIn {
val student = studentRepository.getCurrentStudent(true)
luckyNumberRepository.getLuckyNumber(student, forceRefresh)
}.onEach {
when (it.status) {
Status.LOADING -> {
Timber.i("Loading dashboard lucky number data started")
if (forceRefresh) return@onEach
processHorizontalGroupData(
luckyNumber = it.data?.luckyNumber,
isLoading = true,
forceRefresh = forceRefresh
)
}
Status.SUCCESS -> {
Timber.i("Loading dashboard lucky number result: Success")
processHorizontalGroupData(
luckyNumber = it.data?.luckyNumber ?: -1,
forceRefresh = forceRefresh
)
}
Status.ERROR -> {
Timber.i("Loading dashboard lucky number result: An exception occurred")
errorHandler.dispatch(it.error!!)
processHorizontalGroupData(error = it.error, forceRefresh = forceRefresh)
}
}
}.launch("dashboard_lucky_number")
}
private fun loadMessages(forceRefresh: Boolean) {
flowWithResourceIn {
val student = studentRepository.getCurrentStudent(true)
val semester = semesterRepository.getCurrentSemester(student)
messageRepository.getMessages(student, semester, MessageFolder.RECEIVED, forceRefresh)
}.onEach {
when (it.status) {
Status.LOADING -> {
Timber.i("Loading dashboard messages data started")
if (forceRefresh) return@onEach
val unreadMessagesCount = it.data?.count { message -> message.unread }
processHorizontalGroupData(
unreadMessagesCount = unreadMessagesCount,
isLoading = true,
forceRefresh = forceRefresh
)
}
Status.SUCCESS -> {
Timber.i("Loading dashboard messages result: Success")
val unreadMessagesCount = it.data?.count { message -> message.unread }
processHorizontalGroupData(
unreadMessagesCount = unreadMessagesCount,
forceRefresh = forceRefresh
)
}
Status.ERROR -> {
Timber.i("Loading dashboard messages result: An exception occurred")
errorHandler.dispatch(it.error!!)
processHorizontalGroupData(error = it.error, forceRefresh = forceRefresh)
}
}
}.launch("dashboard_messages")
}
private fun loadAttendance(forceRefresh: Boolean) {
flowWithResourceIn {
val student = studentRepository.getCurrentStudent(true)
val semester = semesterRepository.getCurrentSemester(student)
attendanceSummaryRepository.getAttendanceSummary(student, semester, -1, forceRefresh)
}.onEach {
when (it.status) {
Status.LOADING -> {
Timber.i("Loading dashboard attendance data started")
if (forceRefresh) return@onEach
val attendancePercentage = it.data?.calculatePercentage()
processHorizontalGroupData(
attendancePercentage = attendancePercentage,
isLoading = true,
forceRefresh = forceRefresh
)
}
Status.SUCCESS -> {
Timber.i("Loading dashboard attendance result: Success")
val attendancePercentage = it.data?.calculatePercentage()
processHorizontalGroupData(
attendancePercentage = attendancePercentage,
forceRefresh = forceRefresh
)
}
Status.ERROR -> {
Timber.i("Loading dashboard attendance result: An exception occurred")
errorHandler.dispatch(it.error!!)
processHorizontalGroupData(error = it.error, forceRefresh = forceRefresh)
}
}
}.launch("dashboard_attendance")
}
private fun loadGrades(forceRefresh: Boolean) {
flowWithResourceIn {
val student = studentRepository.getCurrentStudent(true)
val semester = semesterRepository.getCurrentSemester(student)
gradeRepository.getGrades(student, semester, forceRefresh)
}.map { originalResource ->
val filteredSubjectWithGrades = originalResource.data?.first.orEmpty()
.filter { grade ->
grade.date.isAfter(LocalDate.now().minusDays(7))
}
.groupBy { grade -> grade.subject }
.mapValues { entry ->
entry.value
.take(5)
.sortedBy { grade -> grade.date }
}
.toList()
.sortedBy { subjectWithGrades -> subjectWithGrades.second[0].date }
.toMap()
Resource(
status = originalResource.status,
data = filteredSubjectWithGrades.takeIf { originalResource.data != null },
error = originalResource.error
)
}.onEach {
when (it.status) {
Status.LOADING -> {
Timber.i("Loading dashboard grades data started")
if (forceRefresh) return@onEach
updateData(
DashboardItem.Grades(
subjectWithGrades = it.data,
gradeTheme = preferencesRepository.gradeColorTheme,
isLoading = true
), forceRefresh
)
}
Status.SUCCESS -> {
Timber.i("Loading dashboard grades result: Success")
updateData(
DashboardItem.Grades(
subjectWithGrades = it.data,
gradeTheme = preferencesRepository.gradeColorTheme
), forceRefresh
)
}
Status.ERROR -> {
Timber.i("Loading dashboard grades result: An exception occurred")
errorHandler.dispatch(it.error!!)
updateData(DashboardItem.Grades(error = it.error), forceRefresh)
}
}
}.launch("dashboard_grades")
}
private fun loadLessons(forceRefresh: Boolean) {
flowWithResourceIn {
val student = studentRepository.getCurrentStudent(true)
val semester = semesterRepository.getCurrentSemester(student)
val date = LocalDate.now().nextOrSameSchoolDay
timetableRepository.getTimetable(
student = student,
semester = semester,
start = date,
end = date.plusDays(1),
forceRefresh = forceRefresh
)
}.onEach {
when (it.status) {
Status.LOADING -> {
Timber.i("Loading dashboard lessons data started")
if (forceRefresh) return@onEach
updateData(DashboardItem.Lessons(it.data, isLoading = true), forceRefresh)
}
Status.SUCCESS -> {
Timber.i("Loading dashboard lessons result: Success")
updateData(DashboardItem.Lessons(it.data), forceRefresh)
}
Status.ERROR -> {
Timber.i("Loading dashboard lessons result: An exception occurred")
errorHandler.dispatch(it.error!!)
updateData(DashboardItem.Lessons(error = it.error), forceRefresh)
}
}
}.launch("dashboard_lessons")
}
private fun loadHomework(forceRefresh: Boolean) {
flowWithResourceIn {
val student = studentRepository.getCurrentStudent(true)
val semester = semesterRepository.getCurrentSemester(student)
val date = LocalDate.now().nextOrSameSchoolDay
homeworkRepository.getHomework(
student = student,
semester = semester,
start = date,
end = date,
forceRefresh = forceRefresh
)
}.map { homeworkResource ->
val currentDate = LocalDate.now()
val filteredHomework = homeworkResource.data?.filter {
(it.date.isAfter(currentDate) || it.date == currentDate) && !it.isDone
}
homeworkResource.copy(data = filteredHomework)
}.onEach {
when (it.status) {
Status.LOADING -> {
Timber.i("Loading dashboard homework data started")
if (forceRefresh) return@onEach
updateData(
DashboardItem.Homework(it.data ?: emptyList(), isLoading = true),
forceRefresh
)
}
Status.SUCCESS -> {
Timber.i("Loading dashboard homework result: Success")
updateData(DashboardItem.Homework(it.data ?: emptyList()), forceRefresh)
}
Status.ERROR -> {
Timber.i("Loading dashboard homework result: An exception occurred")
errorHandler.dispatch(it.error!!)
updateData(DashboardItem.Homework(error = it.error), forceRefresh)
}
}
}.launch("dashboard_homework")
}
private fun loadSchoolAnnouncements(forceRefresh: Boolean) {
flowWithResourceIn {
val student = studentRepository.getCurrentStudent(true)
schoolAnnouncementRepository.getSchoolAnnouncements(student, forceRefresh)
}.onEach {
when (it.status) {
Status.LOADING -> {
Timber.i("Loading dashboard announcements data started")
if (forceRefresh) return@onEach
updateData(
DashboardItem.Announcements(
it.data ?: emptyList(),
isLoading = true
), forceRefresh
)
}
Status.SUCCESS -> {
Timber.i("Loading dashboard announcements result: Success")
updateData(DashboardItem.Announcements(it.data ?: emptyList()), forceRefresh)
}
Status.ERROR -> {
Timber.i("Loading dashboard announcements result: An exception occurred")
errorHandler.dispatch(it.error!!)
updateData(DashboardItem.Announcements(error = it.error), forceRefresh)
}
}
}.launch("dashboard_announcements")
}
private fun loadExams(forceRefresh: Boolean) {
flowWithResourceIn {
val student = studentRepository.getCurrentStudent(true)
val semester = semesterRepository.getCurrentSemester(student)
examRepository.getExams(
student = student,
semester = semester,
start = LocalDate.now(),
end = LocalDate.now().plusDays(7),
forceRefresh = forceRefresh
)
}.onEach {
when (it.status) {
Status.LOADING -> {
Timber.i("Loading dashboard exams data started")
if (forceRefresh) return@onEach
updateData(
DashboardItem.Exams(it.data.orEmpty(), isLoading = true),
forceRefresh
)
}
Status.SUCCESS -> {
Timber.i("Loading dashboard exams result: Success")
updateData(DashboardItem.Exams(it.data ?: emptyList()), forceRefresh)
}
Status.ERROR -> {
Timber.i("Loading dashboard exams result: An exception occurred")
errorHandler.dispatch(it.error!!)
updateData(DashboardItem.Exams(error = it.error), forceRefresh)
}
}
}.launch("dashboard_exams")
}
private fun loadConferences(forceRefresh: Boolean) {
flowWithResourceIn {
val student = studentRepository.getCurrentStudent(true)
val semester = semesterRepository.getCurrentSemester(student)
conferenceRepository.getConferences(
student = student,
semester = semester,
forceRefresh = forceRefresh,
startDate = LocalDateTime.now()
)
}.onEach {
when (it.status) {
Status.LOADING -> {
Timber.i("Loading dashboard conferences data started")
if (forceRefresh) return@onEach
updateData(
DashboardItem.Conferences(it.data ?: emptyList(), isLoading = true),
forceRefresh
)
}
Status.SUCCESS -> {
Timber.i("Loading dashboard conferences result: Success")
updateData(DashboardItem.Conferences(it.data ?: emptyList()), forceRefresh)
}
Status.ERROR -> {
Timber.i("Loading dashboard conferences result: An exception occurred")
errorHandler.dispatch(it.error!!)
updateData(DashboardItem.Conferences(error = it.error), forceRefresh)
}
}
}.launch("dashboard_conferences")
}
private fun processHorizontalGroupData(
luckyNumber: Int? = null,
unreadMessagesCount: Int? = null,
attendancePercentage: Double? = null,
error: Throwable? = null,
isLoading: Boolean = false,
forceRefresh: Boolean
) {
val isLuckyNumberToLoad =
dashboardTilesToLoad.any { it == DashboardItem.Tile.LUCKY_NUMBER }
val isMessagesToLoad =
dashboardTilesToLoad.any { it == DashboardItem.Tile.MESSAGES }
val isAttendanceToLoad =
dashboardTilesToLoad.any { it == DashboardItem.Tile.ATTENDANCE }
val isPushedToList =
dashboardItemLoadedList.any { it.type == DashboardItem.Type.HORIZONTAL_GROUP }
if (error != null) {
updateData(DashboardItem.HorizontalGroup(error = error), forceRefresh)
return
}
if (isLoading) {
val horizontalGroup =
dashboardItemLoadedList.find { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup?
val updatedHorizontalGroup =
horizontalGroup?.copy(isLoading = true) ?: DashboardItem.HorizontalGroup(isLoading = true)
updateData(updatedHorizontalGroup, forceRefresh)
}
if (forceRefresh && !isPushedToList) {
updateData(DashboardItem.HorizontalGroup(), forceRefresh)
}
val horizontalGroup =
dashboardItemLoadedList.single { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup
when {
luckyNumber != null -> {
updateData(horizontalGroup.copy(luckyNumber = luckyNumber), forceRefresh)
}
unreadMessagesCount != null -> {
updateData(
horizontalGroup.copy(unreadMessagesCount = unreadMessagesCount),
forceRefresh
)
}
attendancePercentage != null -> {
updateData(
horizontalGroup.copy(attendancePercentage = attendancePercentage),
forceRefresh
)
}
}
val isHorizontalGroupLoaded = dashboardItemLoadedList.any {
if (it !is DashboardItem.HorizontalGroup) return@any false
val isLuckyNumberStateCorrect = (it.luckyNumber != null) == isLuckyNumberToLoad
val isMessagesStateCorrect = (it.unreadMessagesCount != null) == isMessagesToLoad
val isAttendanceStateCorrect = (it.attendancePercentage != null) == isAttendanceToLoad
isLuckyNumberStateCorrect && isAttendanceStateCorrect && isMessagesStateCorrect
}
if (isHorizontalGroupLoaded) {
val updatedHorizontalGroup =
dashboardItemLoadedList.single { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup
updateData(updatedHorizontalGroup.copy(isLoading = false, error = null), forceRefresh)
}
}
private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) {
val isForceRefreshError = forceRefresh && dashboardItem.error != null
with(dashboardItemLoadedList) {
removeAll { it.type == dashboardItem.type && !isForceRefreshError }
if (!isForceRefreshError) add(dashboardItem)
sortBy { tile -> dashboardItemsToLoad.single { it == tile.type }.ordinal }
}
if (forceRefresh) {
with(dashboardItemRefreshLoadedList) {
removeAll { it.type == dashboardItem.type }
add(dashboardItem)
}
}
dashboardItemLoadedList.sortBy { tile -> dashboardItemsToLoad.single { it == tile.type }.ordinal }
val isItemsLoaded =
dashboardItemsToLoad.all { type -> dashboardItemLoadedList.any { it.type == type } }
val isRefreshItemLoaded =
dashboardItemsToLoad.all { type -> dashboardItemRefreshLoadedList.any { it.type == type } }
val isItemsDataLoaded = isItemsLoaded && dashboardItemLoadedList.all {
it.isDataLoaded || it.error != null
}
val isRefreshItemsDataLoaded = isRefreshItemLoaded && dashboardItemRefreshLoadedList.all {
it.isDataLoaded || it.error != null
}
if (isRefreshItemsDataLoaded) {
view?.showRefresh(false)
dashboardItemRefreshLoadedList.clear()
}
view?.run {
if (!forceRefresh) {
showProgress(!isItemsDataLoaded)
showContent(isItemsDataLoaded)
}
updateData(dashboardItemLoadedList.toList())
}
if (isItemsLoaded) {
val filteredItems =
dashboardItemLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT }
val isAccountItemError =
dashboardItemLoadedList.single { it.type == DashboardItem.Type.ACCOUNT }.error != null
val isGeneralError =
filteredItems.all { it.error != null } && filteredItems.isNotEmpty() || isAccountItemError
val errorMessage = filteredItems.map { it.error?.stackTraceToString() }.toString()
lastError = Exception(errorMessage)
view?.run {
showProgress(false)
showContent(!isGeneralError)
showErrorView(isGeneralError)
}
}
}
}

View File

@ -0,0 +1,26 @@
package io.github.wulkanowy.ui.modules.dashboard
import io.github.wulkanowy.ui.base.BaseView
interface DashboardView : BaseView {
fun initView()
fun updateData(data: List<DashboardItem>)
fun showDashboardTileSettings(selectedItems: List<DashboardItem.Tile>)
fun showProgress(show: Boolean)
fun showContent(show: Boolean)
fun showRefresh(show: Boolean)
fun showErrorView(show: Boolean)
fun setErrorDetails(message: String)
fun resetView()
fun popViewToRoot()
}

View File

@ -33,7 +33,7 @@ import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
import io.github.wulkanowy.ui.modules.exam.ExamFragment
import io.github.wulkanowy.ui.modules.dashboard.DashboardFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
@ -218,12 +218,12 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
with(binding.mainBottomNav) {
with(menu) {
add(Menu.NONE, 0, Menu.NONE, R.string.grade_title)
add(Menu.NONE, 0, Menu.NONE, R.string.dashboard_title)
.setIcon(R.drawable.ic_main_dashboard)
add(Menu.NONE, 1, Menu.NONE, R.string.grade_title)
.setIcon(R.drawable.ic_main_grade)
add(Menu.NONE, 1, Menu.NONE, R.string.attendance_title)
add(Menu.NONE, 2, Menu.NONE, R.string.attendance_title)
.setIcon(R.drawable.ic_main_attendance)
add(Menu.NONE, 2, Menu.NONE, R.string.exam_title)
.setIcon(R.drawable.ic_main_exam)
add(Menu.NONE, 3, Menu.NONE, R.string.timetable_title)
.setIcon(R.drawable.ic_main_timetable)
add(Menu.NONE, 4, Menu.NONE, R.string.more_title)
@ -256,9 +256,9 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
}
fragmentHideStrategy = HIDE
rootFragments = listOf(
DashboardFragment.newInstance(),
GradeFragment.newInstance(),
AttendanceFragment.newInstance(),
ExamFragment.newInstance(),
TimetableFragment.newInstance(),
MoreFragment.newInstance()
)

View File

@ -9,15 +9,15 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.databinding.FragmentMoreBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
import io.github.wulkanowy.ui.modules.exam.ExamFragment
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
import io.github.wulkanowy.utils.getCompatDrawable
import javax.inject.Inject
@ -48,9 +48,6 @@ class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more),
override val noteRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.note_title) to getCompatDrawable(R.drawable.ic_more_note) }
override val luckyNumberRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.lucky_number_title) to getCompatDrawable(R.drawable.ic_more_lucky_number) }
override val conferencesRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.conferences_title) to getCompatDrawable(R.drawable.ic_more_conferences) }
@ -66,6 +63,9 @@ class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more),
override val settingsRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.settings_title) to getCompatDrawable(R.drawable.ic_more_settings) }
override val examRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.exam_title) to getCompatDrawable(R.drawable.ic_main_exam) }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentMoreBinding.bind(view)
@ -104,10 +104,6 @@ class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more),
(activity as? MainActivity)?.pushView(NoteFragment.newInstance())
}
override fun openLuckyNumberView() {
(activity as? MainActivity)?.pushView(LuckyNumberFragment.newInstance())
}
override fun openSchoolAnnouncementView() {
(activity as? MainActivity)?.pushView(SchoolAnnouncementFragment.newInstance())
}
@ -128,6 +124,10 @@ class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more),
(activity as? MainActivity)?.pushView(SettingsFragment.newInstance())
}
override fun openExamView() {
(activity as? MainActivity)?.pushView(ExamFragment.newInstance())
}
override fun popView(depth: Int) {
(activity as? MainActivity)?.popView(depth)
}

View File

@ -23,9 +23,9 @@ class MorePresenter @Inject constructor(
view?.run {
when (title) {
messagesRes?.first -> openMessagesView()
examRes?.first -> openExamView()
homeworkRes?.first -> openHomeworkView()
noteRes?.first -> openNoteView()
luckyNumberRes?.first -> openLuckyNumberView()
conferencesRes?.first -> openConferencesView()
schoolAnnouncementRes?.first -> openSchoolAnnouncementView()
schoolAndTeachersRes?.first -> openSchoolAndTeachersView()
@ -45,9 +45,9 @@ class MorePresenter @Inject constructor(
view?.run {
updateData(listOfNotNull(
messagesRes,
examRes,
homeworkRes,
noteRes,
luckyNumberRes,
conferencesRes,
schoolAnnouncementRes,
schoolAndTeachersRes,

View File

@ -11,8 +11,6 @@ interface MoreView : BaseView {
val noteRes: Pair<String, Drawable?>?
val luckyNumberRes: Pair<String, Drawable?>?
val conferencesRes: Pair<String, Drawable?>?
val schoolAnnouncementRes: Pair<String, Drawable?>?
@ -23,6 +21,8 @@ interface MoreView : BaseView {
val settingsRes: Pair<String, Drawable?>?
val examRes: Pair<String, Drawable?>?
fun initView()
fun updateData(data: List<Pair<String, Drawable?>>)
@ -37,8 +37,6 @@ interface MoreView : BaseView {
fun openNoteView()
fun openLuckyNumberView()
fun openSchoolAnnouncementView()
fun openConferencesView()
@ -46,4 +44,6 @@ interface MoreView : BaseView {
fun openSchoolAndTeachersView()
fun openMobileDevicesView()
fun openExamView()
}

View File

@ -1,48 +1,46 @@
package io.github.wulkanowy.utils
import android.annotation.SuppressLint
import java.text.SimpleDateFormat
import java.time.DayOfWeek.FRIDAY
import java.time.DayOfWeek.MONDAY
import java.time.DayOfWeek.SATURDAY
import java.time.DayOfWeek.SUNDAY
import java.time.Instant.ofEpochMilli
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalDateTime.now
import java.time.LocalDateTime.ofInstant
import java.time.LocalTime
import java.time.Month
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter.ofPattern
import java.time.format.DateTimeFormatter
import java.time.temporal.TemporalAdjusters.firstInMonth
import java.time.temporal.TemporalAdjusters.next
import java.time.temporal.TemporalAdjusters.previous
import java.util.Locale
private const val DATE_PATTERN = "dd.MM.yyyy"
private const val DEFAULT_DATE_PATTERN = "dd.MM.yyyy"
fun String.toLocalDate(format: String = DATE_PATTERN): LocalDate =
LocalDate.parse(this, ofPattern(format))
fun String.toLocalDate(format: String = DEFAULT_DATE_PATTERN): LocalDate =
LocalDate.parse(this, DateTimeFormatter.ofPattern(format))
fun LocalDateTime.toTimestamp() =
atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC).toInstant().toEpochMilli()
fun Long.toLocalDateTime(): LocalDateTime = ofInstant(ofEpochMilli(this), ZoneId.systemDefault())
fun Long.toLocalDateTime(): LocalDateTime =
LocalDateTime.ofInstant(Instant.ofEpochMilli(this), ZoneId.systemDefault())
fun LocalDate.toTimestamp() = atTime(LocalTime.now()).toTimestamp()
fun LocalDate.toFormattedString(format: String = DATE_PATTERN): String = format(ofPattern(format))
fun LocalDate.toFormattedString(pattern: String = DEFAULT_DATE_PATTERN): String =
format(DateTimeFormatter.ofPattern(pattern))
fun LocalDateTime.toFormattedString(format: String = DATE_PATTERN): String =
format(ofPattern(format))
fun LocalDateTime.toFormattedString(pattern: String = DEFAULT_DATE_PATTERN): String =
format(DateTimeFormatter.ofPattern(pattern))
@SuppressLint("DefaultLocale")
fun Month.getFormattedName(): String {
val formatter = SimpleDateFormat("LLLL", Locale.getDefault())
val date = now().withMonth(value)
val date = LocalDateTime.now().withMonth(value)
return formatter.format(date.toInstant(ZoneOffset.UTC).toEpochMilli()).capitalise()
}
@ -85,7 +83,7 @@ inline val LocalDate.previousOrSameSchoolDay: LocalDate
}
inline val LocalDate.weekDayName: String
get() = format(ofPattern("EEEE", Locale.getDefault()))
get() = format(DateTimeFormatter.ofPattern("EEEE", Locale.getDefault()))
inline val LocalDate.monday: LocalDate
get() = with(MONDAY)

View 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="@android:color/white"
android:pathData="M19,5v2h-4L15,5h4M9,5v6L5,11L5,5h4m10,8v6h-4v-6h4M9,17v2L5,19v-2h4M21,3h-8v6h8L21,3zM11,3L3,3v10h8L11,3zM21,11h-8v10h8L21,11zM11,15L3,15v6h8v-6z" />
</vector>

View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/dashboard_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/dashboard_swipe"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/dashboard_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingTop="6dp"
android:paddingBottom="6dp" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<LinearLayout
android:id="@+id/dashboard_error_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone"
tools:ignore="UseCompoundDrawables"
tools:visibility="visible">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
app:srcCompat="@drawable/ic_error"
app:tint="?colorOnBackground"
tools:ignore="contentDescription" />
<TextView
android:id="@+id/dashboard_error_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:padding="8dp"
android:text="@string/error_unknown"
android:textSize="20sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/dashboard_error_details"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="@string/all_details" />
<com.google.android.material.button.MaterialButton
android:id="@+id/dashboard_error_retry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/all_retry" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginVertical="6dp"
app:cardElevation="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/dashboard_account_item_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/dashboard_account_item_avatar"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/dashboard_account_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:textSize="16sp"
app:layout_constraintStart_toEndOf="@id/dashboard_account_item_avatar"
app:layout_constraintTop_toTopOf="parent"
tools:text="John Smith" />
<TextView
android:id="@+id/dashboard_account_item_school_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginBottom="16dp"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/dashboard_account_item_avatar"
app:layout_constraintTop_toBottomOf="@id/dashboard_account_item_name"
tools:text="Szkoła Wulkanowego " />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/dashboard_account_item_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="gone" />
</com.google.android.material.card.MaterialCardView>

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginVertical="6dp"
android:clickable="true"
android:focusable="true"
android:foreground="?selectableItemBackground"
app:cardElevation="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/dashboard_announcements_item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:drawablePadding="8dp"
android:text="@string/dashboard_announcements_title"
android:textSize="18sp"
android:textStyle="bold"
app:drawableStartCompat="@drawable/ic_all_about"
app:drawableTint="?colorOnSurface"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/dashboard_announcements_item_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_announcements_item_title"
app:layout_constraintVertical_bias="0"
tools:itemCount="2"
tools:listitem="@layout/subitem_dashboard_announcements"
tools:visibility="visible" />
<View
android:id="@+id/dashboard_announcements_item_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
android:background="?colorDivider"
app:layout_constraintTop_toBottomOf="@id/dashboard_announcements_item_recycler" />
<TextView
android:id="@+id/dashboard_announcements_item_more"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
tools:visibility="visible"
android:fontFamily="sans-serif-medium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_announcements_item_divider"
tools:text="Jeszcze 5 ogłoszeń więcej" />
<TextView
android:id="@+id/dashboard_announcements_item_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:text="@string/dashboard_announcements_no_announcements"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_announcements_item_title"
tools:visibility="gone" />
<TextView
android:id="@+id/dashboard_announcements_item_error"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:gravity="center_vertical"
android:layout_marginBottom="16dp"
android:drawablePadding="8dp"
android:text="@string/dashboard_announcements_error"
android:textColor="?colorError"
android:textSize="14sp"
tools:visibility="gone"
app:drawableStartCompat="@drawable/ic_error"
app:drawableTint="?colorError"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_announcements_item_title" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/dashboard_announcements_item_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_announcements_item_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginVertical="6dp"
android:clickable="true"
android:focusable="true"
android:foreground="?selectableItemBackground"
app:cardElevation="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/dashboard_conferences_item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:drawablePadding="8dp"
android:text="@string/dashboard_conferences_title"
android:textSize="18sp"
android:textStyle="bold"
app:drawableStartCompat="@drawable/ic_more_conferences"
app:drawableTint="?colorOnSurface"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/dashboard_conferences_item_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_conferences_item_title"
app:layout_constraintVertical_bias="0"
tools:itemCount="2"
tools:listitem="@layout/subitem_dashboard_exams" />
<View
android:id="@+id/dashboard_conferences_item_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
android:background="?colorDivider"
app:layout_constraintTop_toBottomOf="@id/dashboard_conferences_item_recycler" />
<TextView
android:id="@+id/dashboard_conferences_item_more"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:fontFamily="sans-serif-medium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_conferences_item_divider"
tools:text="Jeszcze 5 zebrań więcej" />
<TextView
android:id="@+id/dashboard_conferences_item_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:text="@string/dashboard_conferences_no_conferences"
android:textSize="14sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_conferences_item_title" />
<TextView
android:id="@+id/dashboard_conferences_item_error"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:gravity="center_vertical"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:drawablePadding="8dp"
android:text="@string/dashboard_conferences_error"
android:textColor="?colorError"
android:textSize="14sp"
android:visibility="gone"
app:layout_constraintHorizontal_bias="0"
app:drawableStartCompat="@drawable/ic_error"
app:drawableTint="?colorError"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_conferences_item_title" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/dashboard_conferences_item_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_conferences_item_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginVertical="6dp"
android:clickable="true"
android:focusable="true"
android:foreground="?selectableItemBackground"
app:cardElevation="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/dashboard_exams_item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:drawablePadding="8dp"
android:text="@string/dashboard_exams_title"
android:textSize="18sp"
android:textStyle="bold"
app:drawableStartCompat="@drawable/ic_main_exam"
app:drawableTint="?colorOnSurface"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/dashboard_exams_item_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_exams_item_title"
app:layout_constraintVertical_bias="0"
tools:itemCount="2"
tools:listitem="@layout/subitem_dashboard_exams" />
<View
android:id="@+id/dashboard_exams_item_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
android:background="?colorDivider"
app:layout_constraintTop_toBottomOf="@id/dashboard_exams_item_recycler" />
<TextView
android:id="@+id/dashboard_exams_item_more"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:fontFamily="sans-serif-medium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_exams_item_divider"
tools:text="Jeszcze 5 sprawdzianów więcej" />
<TextView
android:id="@+id/dashboard_exams_item_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:text="@string/dashboard_exams_no_exams"
android:textSize="14sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_exams_item_title" />
<TextView
android:id="@+id/dashboard_exams_item_error"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:text="@string/dashboard_exams_error"
android:textColor="?colorError"
android:textSize="14sp"
android:visibility="gone"
app:drawableStartCompat="@drawable/ic_error"
app:drawableTint="?colorError"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_exams_item_title" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/dashboard_exams_item_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_exams_item_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginVertical="6dp"
android:foreground="?selectableItemBackground"
app:cardElevation="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/dashboard_grades_item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:drawablePadding="8dp"
android:text="@string/dashboard_grade_title"
android:textSize="18sp"
android:textStyle="bold"
app:drawableStartCompat="@drawable/ic_main_grade"
app:drawableTint="?colorOnSurface"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/dashboard_grades_item_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="10dp"
android:overScrollMode="never"
android:scrollbars="none"
android:nestedScrollingEnabled="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_grades_item_title"
tools:itemCount="3"
tools:listitem="@layout/subitem_dashboard_grades" />
<TextView
android:id="@+id/dashboard_grades_item_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:text="@string/dashboard_grade_no_grade"
android:textSize="14sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_grades_item_title" />
<TextView
android:id="@+id/dashboard_grades_item_error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:drawablePadding="8dp"
android:text="@string/dashboard_grade_error"
android:textColor="?colorError"
android:textSize="14sp"
android:visibility="gone"
android:gravity="center_vertical"
app:drawableStartCompat="@drawable/ic_error"
app:drawableTint="?colorError"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_grades_item_title" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/dashboard_grades_item_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_grades_item_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginVertical="6dp"
android:clickable="true"
android:focusable="true"
android:foreground="?selectableItemBackground"
app:cardElevation="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/dashboard_homework_item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:drawablePadding="8dp"
android:text="@string/dashboard_homework_title"
android:textSize="18sp"
android:textStyle="bold"
app:drawableStartCompat="@drawable/ic_more_homework"
app:drawableTint="?colorOnSurface"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/dashboard_homework_item_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_homework_item_title"
app:layout_constraintVertical_bias="0"
tools:itemCount="5"
tools:listitem="@layout/subitem_dashboard_homework" />
<View
android:id="@+id/dashboard_homework_item_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
android:background="?colorDivider"
app:layout_constraintTop_toBottomOf="@id/dashboard_homework_item_recycler" />
<TextView
android:id="@+id/dashboard_homework_item_more"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:fontFamily="sans-serif-medium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_homework_item_divider"
tools:text="Jeszcze 5 zadań więcej" />
<TextView
android:id="@+id/dashboard_homework_item_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:text="@string/dashboard_homework_no_homework"
android:textSize="14sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_homework_item_title" />
<TextView
android:id="@+id/dashboard_homework_item_error"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:gravity="center_vertical"
android:drawablePadding="8dp"
android:text="@string/dashboard_homework_error"
android:textColor="?colorError"
android:textSize="14sp"
android:visibility="gone"
app:drawableStartCompat="@drawable/ic_error"
app:drawableTint="?colorError"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_homework_item_title" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/dashboard_homework_item_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_homework_item_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -0,0 +1,218 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:layout_marginVertical="2dp">
<com.google.android.material.card.MaterialCardView
android:id="@+id/dashboard_horizontal_group_item_lucky_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginVertical="4dp"
android:layout_marginStart="4dp"
app:cardElevation="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/dashboard_horizontal_group_item_message_container"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/dashboard_horizontal_group_item_lucky_icon"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:src="@drawable/ic_more_lucky_number"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/dashboard_horizontal_group_item_lucky_value"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?colorOnSurface"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/dashboard_horizontal_group_item_lucky_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:includeFontPadding="false"
android:maxLines="1"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/dashboard_horizontal_group_item_lucky_icon"
app:layout_constraintTop_toTopOf="parent"
tools:text="16" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/dashboard_horizontal_group_item_message_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginVertical="4dp"
android:layout_marginStart="8dp"
app:cardElevation="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/dashboard_horizontal_group_item_attendance_container"
app:layout_constraintStart_toEndOf="@id/dashboard_horizontal_group_item_lucky_container"
app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginStart="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/dashboard_horizontal_group_item_message_icon"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:src="@drawable/ic_more_messages"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/dashboard_horizontal_group_item_message_value"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?colorOnSurface"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/dashboard_horizontal_group_item_message_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:includeFontPadding="false"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/dashboard_horizontal_group_item_message_icon"
app:layout_constraintTop_toTopOf="parent"
tools:text="16" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/dashboard_horizontal_group_item_attendance_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginVertical="4dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="4dp"
app:cardElevation="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/dashboard_horizontal_group_item_message_container"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.40">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/dashboard_horizontal_group_item_attendance_icon"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:src="@drawable/ic_main_attendance"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/dashboard_horizontal_group_item_attendance_value"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?colorOnSurface"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/dashboard_horizontal_group_item_attendance_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:includeFontPadding="false"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/dashboard_horizontal_group_item_attendance_icon"
app:layout_constraintTop_toTopOf="parent"
tools:text="99,00%" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/dashboard_horizontal_group_item_info_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="4dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:visibility="gone"
app:cardElevation="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/dashboard_horizontal_group_item_message_container"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.40">
<TextView
android:id="@+id/dashboard_horizontal_group_item_info_error_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:text="@string/dashboard_horizontal_group_error"
android:textColor="?colorError"
android:textSize="14sp"
app:drawableStartCompat="@drawable/ic_error"
app:drawableTint="?colorError"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_lessons_item_title"
tools:visibility="gone" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/dashboard_horizontal_group_item_info_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:indeterminate="true"
app:indicatorSize="28dp" />
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,253 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginVertical="6dp"
android:clickable="true"
android:focusable="true"
android:foreground="?selectableItemBackground"
app:cardElevation="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/dashboard_lessons_item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:drawablePadding="8dp"
android:text="@string/dashboard_timetable_title"
android:textSize="18sp"
android:textStyle="bold"
app:drawableStartCompat="@drawable/ic_main_timetable"
app:drawableTint="?colorOnSurface"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/dashboard_lessons_item_title_tomorrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:text="@string/dashboard_timetable_title_tomorrow"
android:textColor="?colorOnSurface"
android:textSize="14sp"
app:layout_constraintBaseline_toBaselineOf="@id/dashboard_lessons_item_title"
app:layout_constraintBottom_toBottomOf="@id/dashboard_lessons_item_title"
app:layout_constraintStart_toEndOf="@id/dashboard_lessons_item_title" />
<TextView
android:id="@+id/dashboard_lessons_item_first_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="6dp"
android:fontFamily="sans-serif-medium"
android:textColor="?colorPrimary"
android:textSize="13sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_lessons_item_title"
tools:text="Teraz:" />
<TextView
android:id="@+id/dashboard_lessons_item_first_value"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="12dp"
android:fontFamily="sans-serif-medium"
android:textColor="?colorPrimary"
android:textSize="13sp"
app:layout_constraintEnd_toStartOf="@id/dashboard_lessons_item_first_time_barrier"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/dashboard_lessons_item_first_title"
app:layout_constraintTop_toTopOf="@id/dashboard_lessons_item_first_title"
tools:text="Matematyka, Sala 28" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/dashboard_lessons_item_first_time_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="start"
app:constraint_referenced_ids="dashboard_lessons_item_first_time, dashboard_lessons_item_first_time_range" />
<TextView
android:id="@+id/dashboard_lessons_item_first_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:background="@drawable/background_timetable_time_left"
android:paddingStart="6dp"
android:paddingEnd="6dp"
android:paddingBottom="1dp"
android:textColor="@android:color/white"
android:textSize="13sp"
app:backgroundTint="?colorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/dashboard_lessons_item_first_title"
tools:text="jeszcze 15 minut" />
<TextView
android:id="@+id/dashboard_lessons_item_first_time_range"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:textSize="13sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/dashboard_lessons_item_first_title"
tools:text="10:45 - 11:45"
tools:visibility="gone" />
<TextView
android:id="@+id/dashboard_lessons_item_second_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="6dp"
android:text="@string/dashboard_timetable_second_lessons_title"
android:textSize="13sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_lessons_item_first_value" />
<TextView
android:id="@+id/dashboard_lessons_item_second_value"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="16dp"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/dashboard_lessons_item_second_time"
app:layout_constraintStart_toEndOf="@id/dashboard_lessons_item_second_title"
app:layout_constraintTop_toTopOf="@id/dashboard_lessons_item_second_title"
app:layout_constraintVertical_bias="0"
tools:text="Matematyka (28)" />
<TextView
android:id="@+id/dashboard_lessons_item_second_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:textSize="13sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/dashboard_lessons_item_second_title"
tools:text="10:45 - 11:45" />
<View
android:id="@+id/dashboard_lessons_item_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="4dp"
android:background="?colorDivider"
app:layout_constraintTop_toBottomOf="@id/dashboard_lessons_item_second_value" />
<TextView
android:id="@+id/dashboard_lessons_item_third_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="16dp"
android:text="@string/dashboard_timetable_third_title"
android:textSize="13sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_lessons_item_divider"
tools:text="Poźniej" />
<TextView
android:id="@+id/dashboard_lessons_item_third_value"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="12dp"
android:textSize="13sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/dashboard_lessons_item_third_time"
app:layout_constraintStart_toEndOf="@id/dashboard_lessons_item_third_title"
app:layout_constraintTop_toTopOf="@id/dashboard_lessons_item_third_title"
tools:text="jeszcze 5 kolejnych lekcji" />
<TextView
android:id="@+id/dashboard_lessons_item_third_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:textSize="13sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/dashboard_lessons_item_third_title"
tools:text="do 15:55" />
<TextView
android:id="@+id/dashboard_lessons_item_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:text="@string/dashboard_timetable_no_lessons"
android:textSize="14sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_lessons_item_title" />
<TextView
android:id="@+id/dashboard_lessons_item_day_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_lessons_item_title"
tools:text="@tools:sample/lorem"
tools:visibility="gone" />
<TextView
android:id="@+id/dashboard_lessons_item_error"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:gravity="center_vertical"
android:drawablePadding="8dp"
android:text="@string/dashboard_timetable_error"
android:textColor="?colorError"
android:textSize="14sp"
android:visibility="gone"
app:drawableStartCompat="@drawable/ic_error"
app:drawableTint="?colorError"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_lessons_item_title" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/dashboard_lessons_item_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_lessons_item_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/dashboard_homework_subitem_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginEnd="24dp"
android:ellipsize="end"
android:maxLines="1"
android:textSize="13sp"
app:layout_constraintEnd_toStartOf="@id/dashboard_homework_subitem_time"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Dzień wolny od zajęć dydaktycznych" />
<TextView
android:id="@+id/dashboard_homework_subitem_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginEnd="12dp"
android:textColor="?android:textColorSecondary"
android:textSize="13sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="02.11.2020" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/dashboard_homework_subitem_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginEnd="24dp"
android:ellipsize="end"
android:maxLines="1"
android:text="Spotaknie z rodzicami/opiekunami"
android:textSize="13sp"
app:layout_constraintEnd_toStartOf="@id/dashboard_homework_subitem_time"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/dashboard_homework_subitem_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginEnd="12dp"
android:text="17:00 02.11.2020"
android:textSize="13sp"
android:textColor="?android:textColorSecondary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools">
<TextView
android:id="@+id/dashboard_homework_subitem_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginEnd="24dp"
android:ellipsize="end"
android:maxLines="1"
tools:text="Sprawdzian - Matematyka"
android:textSize="13sp"
app:layout_constraintEnd_toStartOf="@id/dashboard_homework_subitem_time"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/dashboard_homework_subitem_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginEnd="12dp"
tools:text="02.11"
android:textSize="13sp"
android:textColor="?android:textColorSecondary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/dashboard_grades_subitem_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textSize="13sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Urządzenia techniki kompu..." />
<LinearLayout
android:id="@+id/dashboard_grades_subitem_grade_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="6dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/dashboard_grades_subitem_title"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="UselessLeaf" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/dashboard_homework_subitem_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginEnd="24dp"
android:ellipsize="end"
android:maxLines="1"
android:textSize="13sp"
app:layout_constraintEnd_toStartOf="@id/dashboard_homework_subitem_time"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Język polski - Zadanie z podręcznika str.107-108" />
<TextView
android:id="@+id/dashboard_homework_subitem_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:textSize="13sp"
android:textColor="?android:textColorSecondary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="do 18.05" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/dashboard_small_grade_subitem_value"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_marginStart="4dp"
android:background="@color/grade_material_default"
android:gravity="center"
android:maxLength="5"
android:minWidth="20dp"
android:textColor="@android:color/white"
android:textSize="12sp"
android:textStyle="bold"
tools:text="6" />

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/dashboard_menu_tiles"
android:icon="@drawable/ic_more_settings"
android:orderInCategory="1"
android:title="@string/pref_dashboard_appearance_tiles_title"
app:iconTint="@color/material_on_surface_emphasis_medium"
app:showAsAction="ifRoom" />
</menu>

View File

@ -40,7 +40,7 @@
<item>https://vulcan.net.pl/?login</item>
<item>https://vulcan.net.pl/?login</item>
<item>https://vulcan.net.pl/?login</item>
<item>http://fakelog.tk/?email</item>
<item>http://fakelog.cf/?email</item>
</string-array>
<string-array name="hosts_symbols">
<item>Default</item>

View File

@ -25,4 +25,12 @@
<bool name="pref_default_homework_fullscreen">false</bool>
<bool name="pref_default_subjects_without_grades">false</bool>
<bool name="pref_default_optional_arithmetic_average">false</bool>
<string-array name="pref_default_dashboard_tiles">
<item>LUCKY_NUMBER</item>
<item>MESSAGES</item>
<item>ATTENDANCE</item>
<item>LESSONS</item>
<item>GRADES</item>
<item>ANNOUNCEMENTS</item>
</string-array>
</resources>

View File

@ -3,6 +3,7 @@
<string name="pref_key_start_menu">start_menu</string>
<string name="pref_key_attendance_present">attendance_present</string>
<string name="pref_key_app_theme">app_theme</string>
<string name="pref_key_dashboard_tiles">dashboard_tiles</string>
<string name="pref_key_grade_color_scheme">grade_color_scheme</string>
<string name="pref_key_expand_grade">expand_grade</string>
<string name="pref_key_grade_average_mode">grade_average_mode</string>

View File

@ -109,4 +109,27 @@
<item>both_semesters</item>
<item>all_year</item>
</string-array>
<string-array name="dashboard_tile_entries">
<item>Lucky number</item>
<item>Unread messages</item>
<item>Attendance</item>
<item>Lessons</item>
<item>Grades</item>
<item>Homework</item>
<item>School announcements</item>
<item>Exams</item>
<item>Conferences</item>
</string-array>
<string-array name="dashboard_tile_values" translatable="false">
<item>LUCKY_NUMBER</item>
<item>MESSAGES</item>
<item>ATTENDANCE</item>
<item>LESSONS</item>
<item>GRADES</item>
<item>HOMEWORK</item>
<item>ANNOUNCEMENTS</item>
<item>EXAMS</item>
<item>CONFERENCES</item>
</string-array>
</resources>

View File

@ -23,6 +23,7 @@
<string name="account_quick_title">Select account</string>
<string name="account_details_title">Account details</string>
<string name="student_info_title">Student info</string>
<string name="dashboard_title">Dashboard</string>
<!--Subtitles-->
@ -469,6 +470,74 @@
<string name="logviewer_refresh">Refresh</string>
<!--Dashboard-->
<string name="dashboard_timetable_title">Lessons</string>
<string name="dashboard_timetable_title_tomorrow">(Tomorrow)</string>
<string name="dashboard_timetable_lesson_value">%1$s (%2$s)</string>
<string name="dashboard_timetable_first_lesson_title_moment">In a moment:</string>
<string name="dashboard_timetable_first_lesson_title_soon">Soon:</string>
<string name="dashboard_timetable_first_lesson_title_first">First:</string>
<string name="dashboard_timetable_first_lesson_title_now">Now:</string>
<plurals name="dashboard_timetable_first_lesson_time_in_minutes">
<item quantity="one">in %1$d minute</item>
<item quantity="other">in %1$d minutes</item>
</plurals>
<plurals name="dashboard_timetable_first_lesson_time_more_minutes">
<item quantity="one">%1$d more minute</item>
<item quantity="other">%1$d more minutes</item>
</plurals>
<string name="dashboard_timetable_second_lesson_value_end">End of lessons</string>
<string name="dashboard_timetable_second_lessons_title">Next:</string>
<string name="dashboard_timetable_third_title">Later:</string>
<plurals name="dashboard_timetable_third_value">
<item quantity="one">%1$d more lesson</item>
<item quantity="other">%1$d more lessons</item>
</plurals>
<string name="dashboard_timetable_third_time">until %1$s</string>
<string name="dashboard_timetable_no_lessons">No lessons</string>
<string name="dashboard_timetable_error">An error occurred while loading the lesson</string>
<string name="dashboard_homework_title">Homework</string>
<string name="dashboard_homework_no_homework">No homework</string>
<string name="dashboard_homework_error">An error occurred while loading the homework</string>
<plurals name="dashboard_homework_more">
<item quantity="one">%1$d more homework</item>
<item quantity="other">%1$d more homework</item>
</plurals>
<string name="dashboard_homework_time">due %1$s</string>
<string name="dashboard_grade_title">Last grades</string>
<string name="dashboard_grade_no_grade">No new grades</string>
<string name="dashboard_grade_error">An error occurred while loading the grades</string>
<string name="dashboard_announcements_title">School announcements</string>
<string name="dashboard_announcements_no_announcements">No announcements</string>
<string name="dashboard_announcements_error">An error occurred while loading the announcements</string>
<plurals name="dashboard_announcements_more">
<item quantity="one">%1$d more announcement</item>
<item quantity="other">%1$d more announcements</item>
</plurals>
<string name="dashboard_exams_title">Exams</string>
<string name="dashboard_exams_no_exams">No exams</string>
<string name="dashboard_exams_error">An error occurred while loading the exams</string>
<plurals name="dashboard_exams_more">
<item quantity="one">%1$d more exam</item>
<item quantity="other">%1$d more exams</item>
</plurals>
<string name="dashboard_conferences_title">Conferences</string>
<string name="dashboard_conferences_no_conferences">No conferences</string>
<string name="dashboard_conferences_error">An error occurred while loading the conferences</string>
<plurals name="dashboard_conference_more">
<item quantity="one">%1$d more conference</item>
<item quantity="other">%1$d more conferences</item>
</plurals>
<string name="dashboard_horizontal_group_error">An error occurred while loading data</string>
<string name="dashboard_horizontal_group_no_lukcy_number">No</string>
<!--Error dialog-->
<string name="dialog_error_check_update">Check for updates</string>
<string name="dialog_error_check_update_message">Before reporting a bug, check first if an update with the bug fix is available</string>
@ -552,6 +621,8 @@
<string name="pref_settings_sync_title">Synchronization</string>
<string name="pref_grades_appearance_header">Grades</string>
<string name="pref_dashboard_appearance_header">Dashboard</string>
<string name="pref_dashboard_appearance_tiles_title">Tiles visibility</string>
<string name="pref_attendance_appearance_view">Attendance</string>
<string name="pref_timetable_appearance_view">Timetable</string>
<string name="pref_grades_advanced_header">Grades</string>

View File

@ -29,6 +29,17 @@
app:title="@string/pref_view_list"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory
app:iconSpaceReserved="false"
app:title="@string/pref_dashboard_appearance_header">
<MultiSelectListPreference
app:entries="@array/dashboard_tile_entries"
app:entryValues="@array/dashboard_tile_values"
app:iconSpaceReserved="false"
app:defaultValue="@array/pref_default_dashboard_tiles"
app:key="@string/pref_key_dashboard_tiles"
app:title="@string/pref_dashboard_appearance_tiles_title" />
</PreferenceCategory>
<PreferenceCategory
app:iconSpaceReserved="false"
app:title="@string/pref_grades_appearance_header">
@ -38,8 +49,7 @@
app:entryValues="@array/grade_color_scheme_values"
app:iconSpaceReserved="false"
app:key="@string/pref_key_grade_color_scheme"
app:title="@string/pref_view_grade_color_scheme"
app:useSimpleSummaryProvider="true" />
app:title="@string/pref_view_grade_color_scheme" />
<SwitchPreferenceCompat
app:defaultValue="@bool/pref_default_expand_grade"
app:iconSpaceReserved="false"
@ -90,15 +100,15 @@
app:key="@string/pref_key_timetable_show_groups"
app:singleLineTitle="false"
app:title="@string/pref_view_timetable_show_groups" />
<!--Hidden due to not functional hybrid/mobile api modes
<ListPreference
app:defaultValue="@string/pref_default_timetable_show_whole_class"
app:entries="@array/timetable_show_whole_class_entries"
app:entryValues="@array/timetable_show_whole_class_values"
app:iconSpaceReserved="false"
app:key="@string/pref_key_timetable_show_whole_class"
app:title="@string/pref_view_timetable_show_whole_class"
app:useSimpleSummaryProvider="true" />
-->
<!--Hidden due to not functional hybrid/mobile api modes
<ListPreference
app:defaultValue="@string/pref_default_timetable_show_whole_class"
app:entries="@array/timetable_show_whole_class_entries"
app:entryValues="@array/timetable_show_whole_class_values"
app:iconSpaceReserved="false"
app:key="@string/pref_key_timetable_show_whole_class"
app:title="@string/pref_view_timetable_show_whole_class"
app:useSimpleSummaryProvider="true" />
-->
</PreferenceCategory>
</PreferenceScreen>