1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2024-11-22 10:36:11 -06:00

Add school announcements (#1323)

This commit is contained in:
Mikołaj Pich 2021-05-03 17:24:01 +02:00 committed by GitHub
parent 075cfb20b1
commit 4e80441167
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 2859 additions and 5 deletions

File diff suppressed because it is too large Load Diff

View File

@ -185,4 +185,8 @@ internal class RepositoryModule {
@Singleton
@Provides
fun provideTimetableHeaderDao(database: AppDatabase) = database.timetableHeaderDao
@Singleton
@Provides
fun provideSchoolAnnouncementDao(database: AppDatabase) = database.schoolAnnouncementDao
}

View File

@ -10,6 +10,7 @@ import io.github.wulkanowy.data.db.dao.AttendanceDao
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
import io.github.wulkanowy.data.db.dao.ConferenceDao
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
import io.github.wulkanowy.data.db.dao.ExamDao
import io.github.wulkanowy.data.db.dao.GradeDao
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
@ -37,6 +38,7 @@ import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
@ -90,6 +92,7 @@ import io.github.wulkanowy.data.db.migrations.Migration34
import io.github.wulkanowy.data.db.migrations.Migration35
import io.github.wulkanowy.data.db.migrations.Migration36
import io.github.wulkanowy.data.db.migrations.Migration37
import io.github.wulkanowy.data.db.migrations.Migration38
import io.github.wulkanowy.data.db.migrations.Migration4
import io.github.wulkanowy.data.db.migrations.Migration5
import io.github.wulkanowy.data.db.migrations.Migration6
@ -129,6 +132,7 @@ import javax.inject.Singleton
TimetableAdditional::class,
StudentInfo::class,
TimetableHeader::class,
SchoolAnnouncement::class,
],
version = AppDatabase.VERSION_SCHEMA,
exportSchema = true
@ -137,7 +141,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
const val VERSION_SCHEMA = 37
const val VERSION_SCHEMA = 38
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(),
@ -176,6 +180,7 @@ abstract class AppDatabase : RoomDatabase() {
Migration35(appInfo),
Migration36(),
Migration37(),
Migration38(),
)
fun newInstance(
@ -243,4 +248,6 @@ abstract class AppDatabase : RoomDatabase() {
abstract val studentInfoDao: StudentInfoDao
abstract val timetableHeaderDao: TimetableHeaderDao
abstract val schoolAnnouncementDao: SchoolAnnouncementDao
}

View File

@ -0,0 +1,15 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import kotlinx.coroutines.flow.Flow
import javax.inject.Singleton
@Dao
@Singleton
interface SchoolAnnouncementDao : BaseDao<SchoolAnnouncement> {
@Query("SELECT * FROM SchoolAnnouncements WHERE student_id = :studentId")
fun loadAll(studentId: Int): Flow<List<SchoolAnnouncement>>
}

View File

@ -0,0 +1,24 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.LocalDate
@Entity(tableName = "SchoolAnnouncements")
data class SchoolAnnouncement(
@ColumnInfo(name = "student_id")
val studentId: Int,
val date: LocalDate,
val subject: String,
val content: String
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View File

@ -9,4 +9,4 @@ class Migration36 : Migration(35, 36) {
database.execSQL("ALTER TABLE Exams ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
database.execSQL("ALTER TABLE Homework ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
}
}
}

View File

@ -0,0 +1,19 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration38 : Migration(37, 38) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("""
CREATE TABLE IF NOT EXISTS `SchoolAnnouncements` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`student_id` INTEGER NOT NULL,
`date` INTEGER NOT NULL,
`subject` TEXT NOT NULL,
`content` TEXT NOT NULL
)
""")
}
}

View File

@ -0,0 +1,14 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.pojo.DirectorInformation as SdkDirectorInformation
fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map {
SchoolAnnouncement(
studentId = student.studentId,
date = it.date,
subject = it.subject,
content = it.content,
)
}

View File

@ -0,0 +1,43 @@
package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SchoolAnnouncementRepository @Inject constructor(
private val schoolAnnouncementDb: SchoolAnnouncementDao,
private val sdk: Sdk,
private val refreshHelper: AutoRefreshHelper,
) {
private val saveFetchResultMutex = Mutex()
private val cacheKey = "school_announcement"
fun getSchoolAnnouncements(student: Student, forceRefresh: Boolean) = networkBoundResource(
mutex = saveFetchResultMutex,
shouldFetch = {
it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(
getRefreshKey(cacheKey, student)
)
},
query = { schoolAnnouncementDb.loadAll(student.studentId) },
fetch = { sdk.init(student).getDirectorInformation().mapToEntities(student) },
saveFetchResult = { old, new ->
schoolAnnouncementDb.deleteAll(old uniqueSubtract new)
schoolAnnouncementDb.insertAll(new uniqueSubtract old)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
}
)
}

View File

@ -33,6 +33,7 @@ import io.github.wulkanowy.services.sync.works.LuckyNumberWork
import io.github.wulkanowy.services.sync.works.MessageWork
import io.github.wulkanowy.services.sync.works.NoteWork
import io.github.wulkanowy.services.sync.works.RecipientWork
import io.github.wulkanowy.services.sync.works.SchoolAnnouncementWork
import io.github.wulkanowy.services.sync.works.TeacherWork
import io.github.wulkanowy.services.sync.works.TimetableWork
import io.github.wulkanowy.services.sync.works.Work
@ -46,15 +47,18 @@ abstract class ServicesModule {
companion object {
@Provides
fun provideWorkManager(@ApplicationContext context: Context) = WorkManager.getInstance(context)
fun provideWorkManager(@ApplicationContext context: Context) =
WorkManager.getInstance(context)
@Singleton
@Provides
fun provideNotificationManager(@ApplicationContext context: Context) = NotificationManagerCompat.from(context)
fun provideNotificationManager(@ApplicationContext context: Context) =
NotificationManagerCompat.from(context)
@Singleton
@Provides
fun provideAlarmManager(@ApplicationContext context: Context): AlarmManager = context.getSystemService()!!
fun provideAlarmManager(@ApplicationContext context: Context): AlarmManager =
context.getSystemService()!!
}
@Binds
@ -109,6 +113,10 @@ abstract class ServicesModule {
@IntoSet
abstract fun provideGradeStatistics(work: GradeStatisticsWork): Work
@Binds
@IntoSet
abstract fun provideSchoolAnnouncementWork(work: SchoolAnnouncementWork): Work
@Binds
@IntoSet
abstract fun provideDebugChannel(channel: DebugChannel): Channel

View File

@ -0,0 +1,16 @@
package io.github.wulkanowy.services.sync.works
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository
import io.github.wulkanowy.utils.waitForResult
import javax.inject.Inject
class SchoolAnnouncementWork @Inject constructor(
private val schoolAnnouncementRepository: SchoolAnnouncementRepository,
) : Work {
override suspend fun doWork(student: Student, semester: Semester) {
schoolAnnouncementRepository.getSchoolAnnouncements(student, true).waitForResult()
}
}

View File

@ -9,6 +9,7 @@ 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.homework.HomeworkFragment
import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
@ -56,6 +57,9 @@ class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more),
override val conferencesRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.conferences_title) to getCompatDrawable(R.drawable.ic_more_conferences) }
override val schoolAnnouncementRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.school_announcement_title) to getCompatDrawable(R.drawable.ic_all_about) }
override val schoolAndTeachersRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.schoolandteachers_title) to getCompatDrawable((R.drawable.ic_more_schoolandteachers)) }
@ -108,6 +112,10 @@ class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more),
(activity as? MainActivity)?.pushView(MobileDeviceFragment.newInstance())
}
override fun openSchoolAnnouncementView() {
(activity as? MainActivity)?.pushView(SchoolAnnouncementFragment.newInstance())
}
override fun openConferencesView() {
(activity as? MainActivity)?.pushView(ConferenceFragment.newInstance())
}

View File

@ -28,6 +28,7 @@ class MorePresenter @Inject constructor(
luckyNumberRes?.first -> openLuckyNumberView()
mobileDevicesRes?.first -> openMobileDevicesView()
conferencesRes?.first -> openConferencesView()
schoolAnnouncementRes?.first -> openSchoolAnnouncementView()
schoolAndTeachersRes?.first -> openSchoolAndTeachersView()
settingsRes?.first -> openSettingsView()
}
@ -49,6 +50,7 @@ class MorePresenter @Inject constructor(
luckyNumberRes,
mobileDevicesRes,
conferencesRes,
schoolAnnouncementRes,
schoolAndTeachersRes,
settingsRes
))

View File

@ -17,6 +17,8 @@ interface MoreView : BaseView {
val conferencesRes: Pair<String, Drawable?>?
val schoolAnnouncementRes: Pair<String, Drawable?>?
val schoolAndTeachersRes: Pair<String, Drawable?>?
val settingsRes: Pair<String, Drawable?>?
@ -39,6 +41,8 @@ interface MoreView : BaseView {
fun openMobileDevicesView()
fun openSchoolAnnouncementView()
fun openConferencesView()
fun openSchoolAndTeachersView()

View File

@ -0,0 +1,37 @@
package io.github.wulkanowy.ui.modules.schoolannouncement
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.text.HtmlCompat
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.databinding.ItemSchoolAnnouncementBinding
import io.github.wulkanowy.utils.toFormattedString
import javax.inject.Inject
class SchoolAnnouncementAdapter @Inject constructor() :
RecyclerView.Adapter<SchoolAnnouncementAdapter.ViewHolder>() {
var items = emptyList<SchoolAnnouncement>()
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
ItemSchoolAnnouncementBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
with(holder.binding) {
schoolAnnouncementItemDate.text = item.date.toFormattedString()
schoolAnnouncementItemType.text = item.subject
schoolAnnouncementItemContent.text = HtmlCompat.fromHtml(
item.content, HtmlCompat.FROM_HTML_MODE_COMPACT
)
}
}
class ViewHolder(val binding: ItemSchoolAnnouncementBinding) :
RecyclerView.ViewHolder(binding.root)
}

View File

@ -0,0 +1,106 @@
package io.github.wulkanowy.ui.modules.schoolannouncement
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.databinding.FragmentSchoolAnnouncementBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.utils.getThemeAttrColor
import javax.inject.Inject
@AndroidEntryPoint
class SchoolAnnouncementFragment :
BaseFragment<FragmentSchoolAnnouncementBinding>(R.layout.fragment_school_announcement),
SchoolAnnouncementView, MainView.TitledView {
@Inject
lateinit var presenter: SchoolAnnouncementPresenter
@Inject
lateinit var schoolAnnouncementAdapter: SchoolAnnouncementAdapter
companion object {
fun newInstance() = SchoolAnnouncementFragment()
}
override val titleStringId: Int
get() = R.string.school_announcement_title
override val isViewEmpty: Boolean
get() = schoolAnnouncementAdapter.items.isEmpty()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentSchoolAnnouncementBinding.bind(view)
presenter.onAttachView(this)
}
override fun initView() {
with(binding.directorInformationRecycler) {
layoutManager = LinearLayoutManager(context)
adapter = schoolAnnouncementAdapter
addItemDecoration(DividerItemDecoration(context))
}
with(binding) {
directorInformationSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
directorInformationSwipe.setColorSchemeColors(requireContext().getThemeAttrColor(R.attr.colorPrimary))
directorInformationSwipe.setProgressBackgroundColorSchemeColor(
requireContext().getThemeAttrColor(R.attr.colorSwipeRefresh)
)
directorInformationErrorRetry.setOnClickListener { presenter.onRetry() }
directorInformationErrorDetails.setOnClickListener { presenter.onDetailsClick() }
}
}
override fun updateData(data: List<SchoolAnnouncement>) {
with(schoolAnnouncementAdapter) {
items = data
notifyDataSetChanged()
}
}
override fun clearData() {
with(schoolAnnouncementAdapter) {
items = listOf()
notifyDataSetChanged()
}
}
override fun showEmpty(show: Boolean) {
binding.directorInformationEmpty.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showErrorView(show: Boolean) {
binding.directorInformationError.visibility = if (show) View.VISIBLE else View.GONE
}
override fun setErrorDetails(message: String) {
binding.directorInformationErrorMessage.text = message
}
override fun showProgress(show: Boolean) {
binding.directorInformationProgress.visibility = if (show) View.VISIBLE else View.GONE
}
override fun enableSwipe(enable: Boolean) {
binding.directorInformationSwipe.isEnabled = enable
}
override fun showContent(show: Boolean) {
binding.directorInformationRecycler.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showRefresh(show: Boolean) {
binding.directorInformationSwipe.isRefreshing = show
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
}
}

View File

@ -0,0 +1,105 @@
package io.github.wulkanowy.ui.modules.schoolannouncement
import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResourceIn
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
import javax.inject.Inject
class SchoolAnnouncementPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val analytics: AnalyticsHelper,
private val schoolAnnouncementRepository: SchoolAnnouncementRepository,
) : BasePresenter<SchoolAnnouncementView>(errorHandler, studentRepository) {
private lateinit var lastError: Throwable
override fun onAttachView(view: SchoolAnnouncementView) {
super.onAttachView(view)
view.initView()
Timber.i("School announcement view was initialized")
errorHandler.showErrorMessage = ::showErrorViewOnError
loadData()
}
fun onSwipeRefresh() {
Timber.i("Force refreshing the School announcement")
loadData(true)
}
fun onRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
loadData(true)
}
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
private fun loadData(forceRefresh: Boolean = false) {
Timber.i("Loading School announcement data started")
flowWithResourceIn {
val student = studentRepository.getCurrentStudent()
schoolAnnouncementRepository.getSchoolAnnouncements(student, forceRefresh)
}.onEach {
when (it.status) {
Status.LOADING -> {
if (!it.data.isNullOrEmpty()) {
view?.run {
enableSwipe(true)
showRefresh(true)
showProgress(false)
showContent(true)
updateData(it.data)
}
}
}
Status.SUCCESS -> {
Timber.i("Loading School announcement result: Success")
view?.apply {
updateData(it.data!!.sortedByDescending { item -> item.date })
showEmpty(it.data.isEmpty())
showErrorView(false)
showContent(it.data.isNotEmpty())
}
analytics.logEvent(
"load_school_announcement",
"items" to it.data!!.size
)
}
Status.ERROR -> {
Timber.i("Loading School announcement result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
view?.run {
showRefresh(false)
showProgress(false)
enableSwipe(true)
}
}.launch()
}
private fun showErrorViewOnError(message: String, error: Throwable) {
view?.run {
if (isViewEmpty) {
lastError = error
setErrorDetails(message)
showErrorView(true)
showEmpty(false)
} else showError(message, error)
}
}
}

View File

@ -0,0 +1,29 @@
package io.github.wulkanowy.ui.modules.schoolannouncement
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.ui.base.BaseView
interface SchoolAnnouncementView : BaseView {
val isViewEmpty: Boolean
fun initView()
fun updateData(data: List<SchoolAnnouncement>)
fun clearData()
fun showEmpty(show: Boolean)
fun showErrorView(show: Boolean)
fun setErrorDetails(message: String)
fun showProgress(show: Boolean)
fun enableSwipe(enable: Boolean)
fun showContent(show: Boolean)
fun showRefresh(show: Boolean)
}

View File

@ -0,0 +1,106 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
tools:context=".ui.modules.schoolannouncement.SchoolAnnouncementFragment">
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/directorInformationProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
tools:visibility="invisible" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/directorInformationSwipe"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/directorInformationRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/item_school_announcement" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<LinearLayout
android:id="@+id/directorInformationEmpty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="10dp"
android:visibility="gone"
tools:ignore="UseCompoundDrawables">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="@string/school_announcement_no_items"
android:textSize="20sp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="100dp"
android:minHeight="100dp"
app:srcCompat="@drawable/ic_all_about"
app:tint="?android:attr/textColorPrimary"
tools:ignore="contentDescription" />
</LinearLayout>
<LinearLayout
android:id="@+id/directorInformationError"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="invisible"
tools:ignore="UseCompoundDrawables"
tools:visibility="invisible">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
app:srcCompat="@drawable/ic_error"
app:tint="?colorOnBackground"
tools:ignore="contentDescription" />
<TextView
android:id="@+id/directorInformationErrorMessage"
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/directorInformationErrorDetails"
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/directorInformationErrorRetry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/all_retry" />
</LinearLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,54 @@
<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:id="@+id/note_subitem_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
tools:context=".ui.modules.schoolannouncement.SchoolAnnouncementAdapter">
<TextView
android:id="@+id/schoolAnnouncementItemDate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:textColor="?android:textColorSecondary"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/date/ddmmyy" />
<TextView
android:id="@+id/schoolAnnouncementItemType"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:ellipsize="end"
android:maxLines="2"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="@id/schoolAnnouncementItemDate"
app:layout_constraintStart_toStartOf="@id/schoolAnnouncementItemDate"
app:layout_constraintTop_toBottomOf="@id/schoolAnnouncementItemDate"
app:layout_goneMarginEnd="0dp"
tools:text="@tools:sample/lorem" />
<TextView
android:id="@+id/schoolAnnouncementItemContent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="15dp"
android:ellipsize="end"
android:lineSpacingMultiplier="1.2"
android:maxLines="2"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/schoolAnnouncementItemType"
app:layout_constraintStart_toStartOf="@id/schoolAnnouncementItemDate"
app:layout_constraintTop_toBottomOf="@id/schoolAnnouncementItemType"
tools:text="@tools:sample/lorem/random" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -355,6 +355,11 @@
<string name="conference_no_items">No info about conferences</string>
<!--Director information-->
<string name="school_announcement_title">School announcements</string>
<string name="school_announcement_no_items">No school announcements</string>
<!--Account-->
<string name="account_add_new">Add account</string>
<string name="account_logout">Logout</string>