Add conferences (#1004)

This commit is contained in:
Mikołaj Pich 2020-11-01 16:53:31 +01:00 committed by GitHub
parent c3061e75b5
commit 8830240182
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 2491 additions and 2 deletions

File diff suppressed because it is too large Load Diff

View File

@ -154,4 +154,8 @@ internal class RepositoryModule {
@Singleton
@Provides
fun provideSchoolInfoDao(database: AppDatabase) = database.schoolDao
@Singleton
@Provides
fun provideConferenceDao(database: AppDatabase) = database.conferenceDao
}

View File

@ -10,6 +10,7 @@ import androidx.room.migration.Migration
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.ExamDao
import io.github.wulkanowy.data.db.dao.GradeDao
import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
@ -32,6 +33,7 @@ import io.github.wulkanowy.data.db.dao.TimetableDao
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.Exam
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradePointsStatistics
@ -70,6 +72,7 @@ import io.github.wulkanowy.data.db.migrations.Migration24
import io.github.wulkanowy.data.db.migrations.Migration25
import io.github.wulkanowy.data.db.migrations.Migration26
import io.github.wulkanowy.data.db.migrations.Migration27
import io.github.wulkanowy.data.db.migrations.Migration28
import io.github.wulkanowy.data.db.migrations.Migration3
import io.github.wulkanowy.data.db.migrations.Migration4
import io.github.wulkanowy.data.db.migrations.Migration5
@ -103,7 +106,8 @@ import javax.inject.Singleton
Recipient::class,
MobileDevice::class,
Teacher::class,
School::class
School::class,
Conference::class,
],
version = AppDatabase.VERSION_SCHEMA,
exportSchema = true
@ -112,7 +116,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
const val VERSION_SCHEMA = 27
const val VERSION_SCHEMA = 28
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> {
return arrayOf(
@ -142,6 +146,7 @@ abstract class AppDatabase : RoomDatabase() {
Migration25(),
Migration26(),
Migration27(),
Migration28(),
)
}
@ -198,4 +203,6 @@ abstract class AppDatabase : RoomDatabase() {
abstract val teacherDao: TeacherDao
abstract val schoolDao: SchoolDao
abstract val conferenceDao: ConferenceDao
}

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.Conference
import kotlinx.coroutines.flow.Flow
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>>
}

View File

@ -0,0 +1,35 @@
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.LocalDateTime
@Entity(tableName = "Conferences")
data class Conference(
@ColumnInfo(name = "student_id")
val studentId: Int,
@ColumnInfo(name = "diary_id")
val diaryId: Int,
val title: String,
val subject: String,
val agenda: String,
@ColumnInfo(name = "present_on_conference")
val presentOnConference: String,
@ColumnInfo(name = "conference_id")
val conferenceId: Int,
val date: LocalDateTime
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View File

@ -0,0 +1,23 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration28 : Migration(27, 28) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("""
CREATE TABLE IF NOT EXISTS Conferences (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
diary_id INTEGER NOT NULL,
title TEXT NOT NULL,
subject TEXT NOT NULL,
agenda TEXT NOT NULL,
present_on_conference TEXT NOT NULL,
conference_id INTEGER NOT NULL,
date INTEGER NOT NULL
)
""")
}
}

View File

@ -0,0 +1,25 @@
package io.github.wulkanowy.data.repositories.conference
import io.github.wulkanowy.data.db.dao.ConferenceDao
import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ConferenceLocal @Inject constructor(private val conferenceDb: ConferenceDao) {
fun getConferences(student: Student, semester: Semester): Flow<List<Conference>> {
return conferenceDb.loadAll(semester.diaryId, student.studentId)
}
suspend fun saveConferences(items: List<Conference>) {
conferenceDb.insertAll(items)
}
suspend fun deleteConferences(items: List<Conference>) {
conferenceDb.deleteAll(items)
}
}

View File

@ -0,0 +1,31 @@
package io.github.wulkanowy.data.repositories.conference
import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.init
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ConferenceRemote @Inject constructor(private val sdk: Sdk) {
suspend fun getConferences(student: Student, semester: Semester): List<Conference> {
return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getConferences()
.map {
it.agenda
Conference(
studentId = student.studentId,
diaryId = semester.diaryId,
agenda = it.agenda,
conferenceId = it.id,
date = it.date,
presentOnConference = it.presentOnConference,
subject = it.subject,
title = it.title
)
}
}
}

View File

@ -0,0 +1,25 @@
package io.github.wulkanowy.data.repositories.conference
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.uniqueSubtract
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ConferenceRepository @Inject constructor(
private val local: ConferenceLocal,
private val remote: ConferenceRemote
) {
fun getConferences(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
shouldFetch = { it.isEmpty() || forceRefresh },
query = { local.getConferences(student, semester) },
fetch = { remote.getConferences(student, semester) },
saveFetchResult = { old, new ->
local.deleteConferences(old uniqueSubtract new)
local.saveConferences(new uniqueSubtract old)
}
)
}

View File

@ -0,0 +1,36 @@
package io.github.wulkanowy.ui.modules.conference
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.databinding.ItemConferenceBinding
import io.github.wulkanowy.utils.toFormattedString
import javax.inject.Inject
class ConferenceAdapter @Inject constructor() :
RecyclerView.Adapter<ConferenceAdapter.ItemViewHolder>() {
var items = emptyList<Conference>()
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
ItemConferenceBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = items[position]
with(holder.binding) {
conferenceItemDate.text = item.date.toFormattedString("dd.MM.yyyy HH:mm")
conferenceItemName.text = item.presentOnConference
conferenceItemTitle.text = item.title
conferenceItemSubject.text = item.subject
conferenceItemContent.text = item.agenda
conferenceItemContent.visibility = if (item.agenda.isBlank()) View.GONE else View.VISIBLE
}
}
class ItemViewHolder(val binding: ItemConferenceBinding) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -0,0 +1,102 @@
package io.github.wulkanowy.ui.modules.conference
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.Conference
import io.github.wulkanowy.databinding.FragmentConferenceBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import javax.inject.Inject
@AndroidEntryPoint
class ConferenceFragment : BaseFragment<FragmentConferenceBinding>(R.layout.fragment_conference),
ConferenceView, MainView.TitledView {
@Inject
lateinit var presenter: ConferencePresenter
@Inject
lateinit var conferencesAdapter: ConferenceAdapter
companion object {
fun newInstance() = ConferenceFragment()
}
override val isViewEmpty: Boolean
get() = conferencesAdapter.items.isEmpty()
override val titleStringId: Int
get() = R.string.conferences_title
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentConferenceBinding.bind(view)
messageContainer = binding.conferenceRecycler
presenter.onAttachView(this)
}
override fun initView() {
with(binding.conferenceRecycler) {
layoutManager = LinearLayoutManager(context)
adapter = conferencesAdapter
addItemDecoration(DividerItemDecoration(context))
}
with(binding) {
conferenceSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
conferenceErrorRetry.setOnClickListener { presenter.onRetry() }
conferenceErrorDetails.setOnClickListener { presenter.onDetailsClick() }
}
}
override fun updateData(data: List<Conference>) {
with(conferencesAdapter) {
items = data
notifyDataSetChanged()
}
}
override fun clearData() {
with(conferencesAdapter) {
items = emptyList()
notifyDataSetChanged()
}
}
override fun hideRefresh() {
binding.conferenceSwipe.isRefreshing = false
}
override fun showProgress(show: Boolean) {
binding.conferenceProgress.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showEmpty(show: Boolean) {
binding.conferenceEmpty.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showErrorView(show: Boolean) {
binding.conferenceError.visibility = if (show) View.VISIBLE else View.GONE
}
override fun setErrorDetails(message: String) {
binding.conferenceErrorMessage.text = message
}
override fun enableSwipe(enable: Boolean) {
binding.conferenceSwipe.isEnabled = enable
}
override fun showContent(show: Boolean) {
binding.conferenceRecycler.visibility = if (show) View.VISIBLE else View.GONE
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
}
}

View File

@ -0,0 +1,96 @@
package io.github.wulkanowy.ui.modules.conference
import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.repositories.conference.ConferenceRepository
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.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 ConferencePresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val conferenceRepository: ConferenceRepository,
private val analytics: AnalyticsHelper
) : BasePresenter<ConferenceView>(errorHandler, studentRepository) {
private lateinit var lastError: Throwable
override fun onAttachView(view: ConferenceView) {
super.onAttachView(view)
view.initView()
Timber.i("Conferences view was initialized")
errorHandler.showErrorMessage = ::showErrorViewOnError
loadData()
}
fun onSwipeRefresh() {
loadData(true)
}
fun onRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
loadData(true)
}
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
private fun showErrorViewOnError(message: String, error: Throwable) {
view?.run {
if (isViewEmpty) {
lastError = error
setErrorDetails(message)
showErrorView(true)
showEmpty(false)
} else showError(message, error)
}
}
private fun loadData(forceRefresh: Boolean = false) {
flowWithResourceIn {
val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student)
conferenceRepository.getConferences(student, semester, forceRefresh)
}.onEach {
when (it.status) {
Status.LOADING -> Timber.i("Loading conference data started")
Status.SUCCESS -> {
Timber.i("Loading conference result: Success")
view?.run {
updateData(it.data!!.sortedByDescending { conference -> conference.date })
showContent(it.data.isNotEmpty())
showEmpty(it.data.isEmpty())
showErrorView(false)
}
analytics.logEvent(
"load_data",
"type" to "conferences",
"items" to it.data!!.size
)
}
Status.ERROR -> {
Timber.i("Loading conference result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}.afterLoading {
view?.run {
hideRefresh()
showProgress(false)
enableSwipe(true)
}
}.launch()
}
}

View File

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

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.about.AboutFragment
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
@ -53,6 +54,9 @@ class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more),
override val mobileDevicesRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.mobile_devices_title) to getCompatDrawable(R.drawable.ic_more_mobile_devices) }
override val conferencesRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.conferences_title) to getCompatDrawable(R.drawable.ic_more_conferences) }
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 openConferencesView() {
(activity as? MainActivity)?.pushView(ConferenceFragment.newInstance())
}
override fun openSchoolAndTeachersView() {
(activity as? MainActivity)?.pushView(SchoolAndTeachersFragment.newInstance())
}

View File

@ -27,6 +27,7 @@ class MorePresenter @Inject constructor(
noteRes?.first -> openNoteView()
luckyNumberRes?.first -> openLuckyNumberView()
mobileDevicesRes?.first -> openMobileDevicesView()
conferencesRes?.first -> openConferencesView()
schoolAndTeachersRes?.first -> openSchoolAndTeachersView()
settingsRes?.first -> openSettingsView()
aboutRes?.first -> openAboutView()
@ -48,6 +49,7 @@ class MorePresenter @Inject constructor(
noteRes,
luckyNumberRes,
mobileDevicesRes,
conferencesRes,
schoolAndTeachersRes,
settingsRes,
aboutRes

View File

@ -15,6 +15,8 @@ interface MoreView : BaseView {
val mobileDevicesRes: Pair<String, Drawable?>?
val conferencesRes: Pair<String, Drawable?>?
val schoolAndTeachersRes: Pair<String, Drawable?>?
val settingsRes: Pair<String, Drawable?>?
@ -41,5 +43,7 @@ interface MoreView : BaseView {
fun openMobileDevicesView()
fun openConferencesView()
fun openSchoolAndTeachersView()
}

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="#FF000000"
android:pathData="M9,13.75c-2.34,0 -7,1.17 -7,3.5L2,19h14v-1.75c0,-2.33 -4.66,-3.5 -7,-3.5zM4.34,17c0.84,-0.58 2.87,-1.25 4.66,-1.25s3.82,0.67 4.66,1.25L4.34,17zM9,12c1.93,0 3.5,-1.57 3.5,-3.5S10.93,5 9,5 5.5,6.57 5.5,8.5 7.07,12 9,12zM9,7c0.83,0 1.5,0.67 1.5,1.5S9.83,10 9,10s-1.5,-0.67 -1.5,-1.5S8.17,7 9,7zM16.04,13.81c1.16,0.84 1.96,1.96 1.96,3.44L18,19h4v-1.75c0,-2.02 -3.5,-3.17 -5.96,-3.44zM15,12c1.93,0 3.5,-1.57 3.5,-3.5S16.93,5 15,5c-0.54,0 -1.04,0.13 -1.5,0.35 0.63,0.89 1,1.98 1,3.15s-0.37,2.26 -1,3.15c0.46,0.22 0.96,0.35 1.5,0.35z" />
</vector>

View File

@ -0,0 +1,104 @@
<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.conference.ConferenceFragment">
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="@+id/conferenceProgress"
style="@style/Widget.MaterialProgressBar.ProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/conferenceSwipe"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/conferenceRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/item_conference" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<LinearLayout
android:id="@+id/conferenceEmpty"
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">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
app:srcCompat="@drawable/ic_more_conferences"
app:tint="?colorOnBackground"
tools:ignore="contentDescription" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="@string/conference_no_items"
android:textSize="20sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/conferenceError"
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/conferenceErrorMessage"
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/conferenceErrorDetails"
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/conferenceErrorRetry"
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,87 @@
<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/conference_item_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
tools:context=".ui.modules.conference.ConferenceAdapter">
<TextView
android:id="@+id/conferenceItemDate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginTop="10dp"
android:textColor="?android:textColorSecondary"
android:textSize="15sp"
app:layout_constraintRight_toLeftOf="@+id/conferenceItemName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/date/ddmmyy" />
<TextView
android:id="@+id/conferenceItemName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="15dp"
android:ellipsize="end"
android:gravity="end"
android:singleLine="true"
android:textSize="13sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/conferenceItemDate"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/full_names" />
<TextView
android:id="@+id/conferenceItemTitle"
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/conferenceItemName"
app:layout_constraintStart_toStartOf="@id/conferenceItemDate"
app:layout_constraintTop_toBottomOf="@id/conferenceItemDate"
app:layout_goneMarginEnd="0dp"
tools:text="@tools:sample/lorem" />
<TextView
android:id="@+id/conferenceItemSubject"
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_constraintBottom_toTopOf="@id/conferenceItemContent"
app:layout_constraintEnd_toEndOf="@id/conferenceItemTitle"
app:layout_constraintStart_toStartOf="@id/conferenceItemTitle"
app:layout_constraintTop_toBottomOf="@id/conferenceItemTitle"
app:layout_goneMarginBottom="15dp"
app:layout_goneMarginEnd="0dp"
tools:text="@tools:sample/lorem" />
<TextView
android:id="@+id/conferenceItemContent"
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/conferenceItemName"
app:layout_constraintStart_toStartOf="@id/conferenceItemDate"
app:layout_constraintTop_toBottomOf="@id/conferenceItemSubject"
tools:text="@tools:sample/lorem/random"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -325,6 +325,11 @@
<string name="teacher_no_subject">No subject</string>
<!--Conference-->
<string name="conferences_title">Conferences</string>
<string name="conference_no_items">No info about conferences</string>
<!--Account-->
<string name="account_add_new">Add account</string>
<string name="account_logout">Logout</string>