Add nick for student (#1119)

This commit is contained in:
Rafał Borcz 2021-02-01 23:58:44 +01:00 committed by GitHub
parent 39534aeda4
commit 3e3a080b70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 2857 additions and 313 deletions

File diff suppressed because it is too large Load Diff

View File

@ -83,6 +83,7 @@ import io.github.wulkanowy.data.db.migrations.Migration29
import io.github.wulkanowy.data.db.migrations.Migration3 import io.github.wulkanowy.data.db.migrations.Migration3
import io.github.wulkanowy.data.db.migrations.Migration30 import io.github.wulkanowy.data.db.migrations.Migration30
import io.github.wulkanowy.data.db.migrations.Migration31 import io.github.wulkanowy.data.db.migrations.Migration31
import io.github.wulkanowy.data.db.migrations.Migration32
import io.github.wulkanowy.data.db.migrations.Migration4 import io.github.wulkanowy.data.db.migrations.Migration4
import io.github.wulkanowy.data.db.migrations.Migration5 import io.github.wulkanowy.data.db.migrations.Migration5
import io.github.wulkanowy.data.db.migrations.Migration6 import io.github.wulkanowy.data.db.migrations.Migration6
@ -128,7 +129,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 31 const val VERSION_SCHEMA = 32
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> { fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> {
return arrayOf( return arrayOf(
@ -161,7 +162,8 @@ abstract class AppDatabase : RoomDatabase() {
Migration28(), Migration28(),
Migration29(), Migration29(),
Migration30(), Migration30(),
Migration31() Migration31(),
Migration32()
) )
} }

View File

@ -6,7 +6,9 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy.ABORT import androidx.room.OnConflictStrategy.ABORT
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction import androidx.room.Transaction
import androidx.room.Update
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentNick
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import javax.inject.Singleton import javax.inject.Singleton
@ -20,6 +22,9 @@ interface StudentDao {
@Delete @Delete
suspend fun delete(student: Student) suspend fun delete(student: Student)
@Update(entity = Student::class)
suspend fun update(studentNick: StudentNick)
@Query("SELECT * FROM Students WHERE is_current = 1") @Query("SELECT * FROM Students WHERE is_current = 1")
suspend fun loadCurrent(): Student? suspend fun loadCurrent(): Student?

View File

@ -79,4 +79,6 @@ data class Student(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Long = 0 var id: Long = 0
var nick = ""
} }

View File

@ -0,0 +1,16 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
@Entity
data class StudentNick(
val nick: String
) : Serializable {
@PrimaryKey
var id: Long = 0
}

View File

@ -0,0 +1,12 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration32 : Migration(31, 32) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Students ADD COLUMN nick TEXT NOT NULL DEFAULT \"\"")
}
}

View File

@ -5,6 +5,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.dao.StudentDao import io.github.wulkanowy.data.db.dao.StudentDao
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentNick
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
@ -25,49 +26,70 @@ class StudentRepository @Inject constructor(
private val sdk: Sdk private val sdk: Sdk
) { ) {
suspend fun isStudentSaved(): Boolean = getSavedStudents(false).isNotEmpty() suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty()
suspend fun isCurrentStudentSet(): Boolean = studentDb.loadCurrent()?.isCurrent ?: false suspend fun isCurrentStudentSet() = studentDb.loadCurrent()?.isCurrent ?: false
suspend fun getStudentsApi(pin: String, symbol: String, token: String): List<StudentWithSemesters> { suspend fun getStudentsApi(
return sdk.getStudentsFromMobileApi(token, pin, symbol, "").mapToEntities() pin: String,
} symbol: String,
token: String
): List<StudentWithSemesters> =
sdk.getStudentsFromMobileApi(token, pin, symbol, "").mapToEntities()
suspend fun getStudentsScrapper(email: String, password: String, scrapperBaseUrl: String, symbol: String): List<StudentWithSemesters> { suspend fun getStudentsScrapper(
return sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol).mapToEntities(password) email: String,
} password: String,
scrapperBaseUrl: String,
symbol: String
): List<StudentWithSemesters> =
sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol)
.mapToEntities(password)
suspend fun getStudentsHybrid(email: String, password: String, scrapperBaseUrl: String, symbol: String): List<StudentWithSemesters> { suspend fun getStudentsHybrid(
return sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol).mapToEntities(password) email: String,
} password: String,
scrapperBaseUrl: String,
symbol: String
): List<StudentWithSemesters> =
sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol).mapToEntities(password)
suspend fun getSavedStudents(decryptPass: Boolean = true) = withContext(dispatchers.backgroundThread) { suspend fun getSavedStudents(decryptPass: Boolean = true) =
studentDb.loadStudentsWithSemesters().map { withContext(dispatchers.backgroundThread) {
it.apply { studentDb.loadStudentsWithSemesters().map {
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) student.password = decrypt(student.password) it.apply {
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
student.password = decrypt(student.password)
}
}
} }
} }
}
suspend fun getStudentById(id: Int) = withContext(dispatchers.backgroundThread) { suspend fun getStudentById(id: Int) = withContext(dispatchers.backgroundThread) {
studentDb.loadById(id)?.apply { studentDb.loadById(id)?.apply {
if (Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password) if (Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) {
password = decrypt(password)
}
} }
} ?: throw NoCurrentStudentException() } ?: throw NoCurrentStudentException()
suspend fun getCurrentStudent(decryptPass: Boolean = true) = withContext(dispatchers.backgroundThread) { suspend fun getCurrentStudent(decryptPass: Boolean = true) =
studentDb.loadCurrent()?.apply { withContext(dispatchers.backgroundThread) {
if (decryptPass && Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password) studentDb.loadCurrent()?.apply {
} if (decryptPass && Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) {
} ?: throw NoCurrentStudentException() password = decrypt(password)
}
}
} ?: throw NoCurrentStudentException()
suspend fun saveStudents(studentsWithSemesters: List<StudentWithSemesters>): List<Long> { suspend fun saveStudents(studentsWithSemesters: List<StudentWithSemesters>): List<Long> {
semesterDb.insertSemesters(studentsWithSemesters.flatMap { it.semesters }) semesterDb.insertSemesters(studentsWithSemesters.flatMap { it.semesters })
return withContext(dispatchers.backgroundThread) { return withContext(dispatchers.backgroundThread) {
studentDb.insertAll(studentsWithSemesters.map { it.student }.map { studentDb.insertAll(studentsWithSemesters.map { it.student }.map {
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) it.copy(password = encrypt(it.password, context)) if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) {
else it it.copy(password = encrypt(it.password, context))
} else it
}) })
} }
} }
@ -79,7 +101,7 @@ class StudentRepository @Inject constructor(
} }
} }
suspend fun logoutStudent(student: Student) { suspend fun logoutStudent(student: Student) = studentDb.delete(student)
studentDb.delete(student)
} suspend fun updateStudentNick(studentNick: StudentNick) = studentDb.update(studentNick)
} }

View File

@ -27,6 +27,7 @@ import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companio
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.DispatchersProvider
import io.github.wulkanowy.utils.nickOrName
import io.github.wulkanowy.utils.toTimestamp import io.github.wulkanowy.utils.toTimestamp
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
@ -41,17 +42,23 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
private val dispatchersProvider: DispatchersProvider, private val dispatchersProvider: DispatchersProvider,
) { ) {
private fun getRequestCode(time: LocalDateTime, studentId: Int) = (time.toTimestamp() * studentId).toInt() private fun getRequestCode(time: LocalDateTime, studentId: Int) =
(time.toTimestamp() * studentId).toInt()
private fun getUpcomingLessonTime(index: Int, day: List<Timetable>, lesson: Timetable): LocalDateTime { private fun getUpcomingLessonTime(
return day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30) index: Int,
} day: List<Timetable>,
lesson: Timetable
) = day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30)
suspend fun cancelScheduled(lessons: List<Timetable>, studentId: Int = 1) { suspend fun cancelScheduled(lessons: List<Timetable>, studentId: Int = 1) {
withContext(dispatchersProvider.backgroundThread) { withContext(dispatchersProvider.backgroundThread) {
lessons.sortedBy { it.start }.forEachIndexed { index, lesson -> lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson) val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
cancelScheduledTo(upcomingTime..lesson.start, getRequestCode(upcomingTime, studentId)) cancelScheduledTo(
upcomingTime..lesson.start,
getRequestCode(upcomingTime, studentId)
)
cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId)) cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId))
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId") Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
@ -61,13 +68,18 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) { private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) {
if (now() in range) cancelNotification() if (now() in range) cancelNotification()
alarmManager.cancel(PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_UPDATE_CURRENT)) alarmManager.cancel(
PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_UPDATE_CURRENT)
)
} }
fun cancelNotification() = NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id) fun cancelNotification() =
NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
suspend fun scheduleNotifications(lessons: List<Timetable>, student: Student) { suspend fun scheduleNotifications(lessons: List<Timetable>, student: Student) {
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) return cancelScheduled(lessons, student.studentId) if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) {
return cancelScheduled(lessons, student.studentId)
}
withContext(dispatchersProvider.backgroundThread) { withContext(dispatchersProvider.backgroundThread) {
lessons.groupBy { it.date } lessons.groupBy { it.date }
@ -82,13 +94,28 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
val intent = createIntent(student, lesson, active.getOrNull(index + 1)) val intent = createIntent(student, lesson, active.getOrNull(index + 1))
if (lesson.start > now()) { if (lesson.start > now()) {
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_UPCOMING, getUpcomingLessonTime(index, active, lesson)) scheduleBroadcast(
intent,
student.studentId,
NOTIFICATION_TYPE_UPCOMING,
getUpcomingLessonTime(index, active, lesson)
)
} }
if (lesson.end > now()) { if (lesson.end > now()) {
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_CURRENT, lesson.start) scheduleBroadcast(
intent,
student.studentId,
NOTIFICATION_TYPE_CURRENT,
lesson.start
)
if (active.lastIndex == index) { if (active.lastIndex == index) {
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION, lesson.end) scheduleBroadcast(
intent,
student.studentId,
NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
lesson.end
)
} }
} }
} }
@ -99,7 +126,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
private fun createIntent(student: Student, lesson: Timetable, nextLesson: Timetable?): Intent { private fun createIntent(student: Student, lesson: Timetable, nextLesson: Timetable?): Intent {
return Intent(context, TimetableNotificationReceiver::class.java).apply { return Intent(context, TimetableNotificationReceiver::class.java).apply {
putExtra(STUDENT_ID, student.studentId) putExtra(STUDENT_ID, student.studentId)
putExtra(STUDENT_NAME, student.studentName) putExtra(STUDENT_NAME, student.nickOrName)
putExtra(LESSON_ROOM, lesson.room) putExtra(LESSON_ROOM, lesson.room)
putExtra(LESSON_START, lesson.start.toTimestamp()) putExtra(LESSON_START, lesson.start.toTimestamp())
putExtra(LESSON_END, lesson.end.toTimestamp()) putExtra(LESSON_END, lesson.end.toTimestamp())
@ -109,13 +136,23 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
} }
} }
private fun scheduleBroadcast(intent: Intent, studentId: Int, notificationType: Int, time: LocalDateTime) { private fun scheduleBroadcast(
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, RTC_WAKEUP, time.toTimestamp(), intent: Intent,
studentId: Int,
notificationType: Int,
time: LocalDateTime
) {
AlarmManagerCompat.setExactAndAllowWhileIdle(
alarmManager, RTC_WAKEUP, time.toTimestamp(),
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also { PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id) it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
it.putExtra(LESSON_TYPE, notificationType) it.putExtra(LESSON_TYPE, notificationType)
}, FLAG_UPDATE_CURRENT) }, FLAG_UPDATE_CURRENT)
) )
Timber.d("TimetableNotification scheduled: type: $notificationType, subject: ${intent.getStringExtra(LESSON_TITLE)}, start: $time, student: $studentId") Timber.d(
"TimetableNotification scheduled: type: $notificationType, subject: ${
intent.getStringExtra(LESSON_TITLE)
}, start: $time, student: $studentId"
)
} }
} }

View File

@ -9,6 +9,7 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.databinding.ItemAccountBinding import io.github.wulkanowy.databinding.ItemAccountBinding
import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.nickOrName
import javax.inject.Inject import javax.inject.Inject
class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter<WidgetConfigureAdapter.ItemViewHolder>() { class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter<WidgetConfigureAdapter.ItemViewHolder>() {
@ -28,7 +29,7 @@ class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter<Widget
val (student, isCurrent) = items[position] val (student, isCurrent) = items[position]
with(holder.binding) { with(holder.binding) {
accountItemName.text = "${student.studentName} ${student.className}" accountItemName.text = "${student.nickOrName} ${student.className}"
accountItemSchool.text = student.schoolName accountItemSchool.text = student.schoolName
with(accountItemImage) { with(accountItemImage) {

View File

@ -12,6 +12,7 @@ import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.databinding.HeaderAccountBinding import io.github.wulkanowy.databinding.HeaderAccountBinding
import io.github.wulkanowy.databinding.ItemAccountBinding import io.github.wulkanowy.databinding.ItemAccountBinding
import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.nickOrName
import javax.inject.Inject import javax.inject.Inject
class AccountAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() { class AccountAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
@ -84,7 +85,7 @@ class AccountAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.V
}.size > 1 && isAccountQuickDialogMode }.size > 1 && isAccountQuickDialogMode
with(binding) { with(binding) {
accountItemName.text = "${student.studentName} ${diary?.diaryName.orEmpty()}" accountItemName.text = "${student.nickOrName} ${diary?.diaryName.orEmpty()}"
accountItemSchool.text = studentWithSemesters.student.schoolName accountItemSchool.text = studentWithSemesters.student.schoolName
accountItemAccountType.setText(if (student.isParent) R.string.account_type_parent else R.string.account_type_student) accountItemAccountType.setText(if (student.isParent) R.string.account_type_parent else R.string.account_type_student)
accountItemAccountType.visibility = if (isDuplicatedStudent) VISIBLE else GONE accountItemAccountType.visibility = if (isDuplicatedStudent) VISIBLE else GONE

View File

@ -62,10 +62,16 @@ class AccountPresenter @Inject constructor(
} }
private fun loadData() { private fun loadData() {
flowWithResource { studentRepository.getSavedStudents() } flowWithResource { studentRepository.getSavedStudents(false) }
.onEach { .onEach {
when (it.status) { when (it.status) {
Status.LOADING -> Timber.i("Loading account data started") Status.LOADING -> {
Timber.i("Loading account data started")
view?.run {
showProgress(true)
showContent(false)
}
}
Status.SUCCESS -> { Status.SUCCESS -> {
Timber.i("Loading account result: Success") Timber.i("Loading account result: Success")
view?.updateData(createAccountItems(it.data!!)) view?.updateData(createAccountItems(it.data!!))

View File

@ -9,13 +9,16 @@ import androidx.appcompat.app.AlertDialog
import androidx.core.view.get import androidx.core.view.get
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.databinding.FragmentAccountDetailsBinding import io.github.wulkanowy.databinding.FragmentAccountDetailsBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.account.accountedit.AccountEditDialog
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
import io.github.wulkanowy.utils.nickOrName
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -43,22 +46,19 @@ class AccountDetailsFragment :
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
arguments?.let {
presenter.studentWithSemesters =
it.getSerializable(ARGUMENT_KEY) as StudentWithSemesters
}
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding = FragmentAccountDetailsBinding.bind(view) binding = FragmentAccountDetailsBinding.bind(view)
presenter.onAttachView(this) presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as StudentWithSemesters)
} }
override fun initView() { override fun initView() {
binding.accountDetailsErrorRetry.setOnClickListener { presenter.onRetry() }
binding.accountDetailsErrorDetails.setOnClickListener { presenter.onDetailsClick() }
binding.accountDetailsLogout.setOnClickListener { presenter.onRemoveSelected() } binding.accountDetailsLogout.setOnClickListener { presenter.onRemoveSelected() }
binding.accountDetailsSelect.setOnClickListener { presenter.onStudentSelect() } binding.accountDetailsSelect.setOnClickListener { presenter.onStudentSelect() }
binding.accountDetailsSelect.isEnabled = !presenter.studentWithSemesters.student.isCurrent
binding.accountDetailsPersonalData.setOnClickListener { binding.accountDetailsPersonalData.setOnClickListener {
presenter.onStudentInfoSelected(StudentInfoView.Type.PERSONAL) presenter.onStudentInfoSelected(StudentInfoView.Type.PERSONAL)
@ -76,24 +76,31 @@ class AccountDetailsFragment :
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
menu[0].isVisible = false menu[0].isVisible = false
inflater.inflate(R.menu.action_menu_account_details, menu)
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return if (item.itemId == R.id.accountDetailsMenuEdit) { return if (item.itemId == R.id.accountDetailsMenuEdit) {
showAccountEditDetailsDialog() presenter.onAccountEditSelected()
return true true
} else false } else false
} }
override fun showAccountData(studentWithSemesters: StudentWithSemesters) { override fun showAccountData(student: Student) {
with(binding) { with(binding) {
accountDetailsName.text = studentWithSemesters.student.studentName accountDetailsName.text = student.nickOrName
accountDetailsSchool.text = studentWithSemesters.student.schoolName accountDetailsSchool.text = student.schoolName
} }
} }
override fun showAccountEditDetailsDialog() { override fun enableSelectStudentButton(enable: Boolean) {
(requireActivity() as MainActivity).showDialogFragment(AccountEditDetailsDialog.newInstance()) binding.accountDetailsSelect.isEnabled = enable
}
override fun showAccountEditDetailsDialog(student: Student) {
(requireActivity() as MainActivity).showDialogFragment(
AccountEditDialog.newInstance(student)
)
} }
override fun showLogoutConfirmDialog() { override fun showLogoutConfirmDialog() {
@ -127,6 +134,22 @@ class AccountDetailsFragment :
) )
} }
override fun showErrorView(show: Boolean) {
binding.accountDetailsError.visibility = if (show) View.VISIBLE else View.GONE
}
override fun setErrorDetails(message: String) {
binding.accountDetailsErrorMessage.text = message
}
override fun showProgress(show: Boolean) {
binding.accountDetailsProgress.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showContent(show: Boolean) {
binding.accountDetailsContent.visibility = if (show) View.VISIBLE else View.GONE
}
override fun onDestroyView() { override fun onDestroyView() {
presenter.onDetachView() presenter.onDetachView()
super.onDestroyView() super.onDestroyView()

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.account.accountdetails package io.github.wulkanowy.ui.modules.account.accountdetails
import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
@ -9,6 +10,7 @@ import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.afterLoading
import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.flowWithResource
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -19,14 +21,74 @@ class AccountDetailsPresenter @Inject constructor(
private val syncManager: SyncManager private val syncManager: SyncManager
) : BasePresenter<AccountDetailsView>(errorHandler, studentRepository) { ) : BasePresenter<AccountDetailsView>(errorHandler, studentRepository) {
lateinit var studentWithSemesters: StudentWithSemesters private lateinit var studentWithSemesters: StudentWithSemesters
override fun onAttachView(view: AccountDetailsView) { private lateinit var lastError: Throwable
private var studentId: Long? = null
fun onAttachView(view: AccountDetailsView, studentWithSemesters: StudentWithSemesters) {
super.onAttachView(view) super.onAttachView(view)
view.initView() studentId = studentWithSemesters.student.id
Timber.i("Account details view was initialized")
view.showAccountData(studentWithSemesters) view.initView()
errorHandler.showErrorMessage = ::showErrorViewOnError
Timber.i("Account details view was initialized")
loadData()
}
fun onRetry() {
view?.run {
showErrorView(false)
showProgress(true)
}
loadData()
}
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
private fun loadData() {
flowWithResource { studentRepository.getSavedStudents() }
.map { studentWithSemesters ->
Resource(
data = studentWithSemesters.data?.single { it.student.id == studentId },
status = studentWithSemesters.status,
error = studentWithSemesters.error
)
}
.onEach {
when (it.status) {
Status.LOADING -> {
view?.run {
showProgress(true)
showContent(false)
}
Timber.i("Loading account details view started")
}
Status.SUCCESS -> {
Timber.i("Loading account details view result: Success")
studentWithSemesters = it.data!!
view?.run {
showAccountData(studentWithSemesters.student)
enableSelectStudentButton(!studentWithSemesters.student.isCurrent)
showContent(true)
showErrorView(false)
}
}
Status.ERROR -> {
Timber.i("Loading account details view result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}
.afterLoading { view?.showProgress(false) }
.launch()
}
fun onAccountEditSelected() {
view?.showAccountEditDetailsDialog(studentWithSemesters.student)
} }
fun onStudentInfoSelected(infoType: StudentInfoView.Type) { fun onStudentInfoSelected(infoType: StudentInfoView.Type) {
@ -97,4 +159,14 @@ class AccountDetailsPresenter @Inject constructor(
view?.popView() view?.popView()
}.launch("logout") }.launch("logout")
} }
private fun showErrorViewOnError(message: String, error: Throwable) {
view?.run {
lastError = error
setErrorDetails(message)
showErrorView(true)
showContent(false)
showProgress(false)
}
}
} }

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.account.accountdetails package io.github.wulkanowy.ui.modules.account.accountdetails
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
@ -8,9 +9,9 @@ interface AccountDetailsView : BaseView {
fun initView() fun initView()
fun showAccountData(studentWithSemesters: StudentWithSemesters) fun showAccountData(student: Student)
fun showAccountEditDetailsDialog() fun showAccountEditDetailsDialog(student: Student)
fun showLogoutConfirmDialog() fun showLogoutConfirmDialog()
@ -18,8 +19,18 @@ interface AccountDetailsView : BaseView {
fun recreateMainView() fun recreateMainView()
fun enableSelectStudentButton(enable: Boolean)
fun openStudentInfoView( fun openStudentInfoView(
infoType: StudentInfoView.Type, infoType: StudentInfoView.Type,
studentWithSemesters: StudentWithSemesters studentWithSemesters: StudentWithSemesters
) )
fun showErrorView(show: Boolean)
fun setErrorDetails(message: String)
fun showProgress(show: Boolean)
fun showContent(show: Boolean)
} }

View File

@ -1,38 +0,0 @@
package io.github.wulkanowy.ui.modules.account.accountdetails
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.databinding.DialogAccountEditDetailsBinding
import io.github.wulkanowy.utils.lifecycleAwareVariable
class AccountEditDetailsDialog : DialogFragment() {
private var binding: DialogAccountEditDetailsBinding by lifecycleAwareVariable()
companion object {
fun newInstance() = AccountEditDetailsDialog()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return DialogAccountEditDetailsBinding.inflate(inflater).apply { binding = this }.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.accountEditDetailsCancel.setOnClickListener { dismiss() }
}
}

View File

@ -0,0 +1,72 @@
package io.github.wulkanowy.ui.modules.account.accountedit
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.databinding.DialogAccountEditBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment
import javax.inject.Inject
@AndroidEntryPoint
class AccountEditDialog : BaseDialogFragment<DialogAccountEditBinding>(), AccountEditView {
@Inject
lateinit var presenter: AccountEditPresenter
companion object {
private const val ARGUMENT_KEY = "student_with_semesters"
fun newInstance(student: Student) =
AccountEditDialog().apply {
arguments = Bundle().apply {
putSerializable(ARGUMENT_KEY, student)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View = DialogAccountEditBinding.inflate(inflater).apply { binding = this }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student)
}
override fun initView() {
with(binding) {
accountEditDetailsCancel.setOnClickListener { dismiss() }
accountEditDetailsSave.setOnClickListener {
presenter.changeStudentNick(binding.accountEditDetailsNickText.text.toString())
}
}
}
override fun showCurrentNick(nick: String) {
binding.accountEditDetailsNickText.setText(nick)
}
override fun popView() {
dismiss()
}
override fun recreateMainView() {
activity?.recreate()
}
override fun onDestroyView() {
super.onDestroyView()
presenter.onDetachView()
}
}

View File

@ -0,0 +1,54 @@
package io.github.wulkanowy.ui.modules.account.accountedit
import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentNick
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.afterLoading
import io.github.wulkanowy.utils.flowWithResource
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
import javax.inject.Inject
class AccountEditPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository
) : BasePresenter<AccountEditView>(errorHandler, studentRepository) {
lateinit var student: Student
fun onAttachView(view: AccountEditView, student: Student) {
super.onAttachView(view)
this.student = student
with(view) {
initView()
showCurrentNick(student.nick.trim())
}
Timber.i("Account edit dialog view was initialized")
}
fun changeStudentNick(nick: String) {
flowWithResource {
val studentNick =
StudentNick(nick = nick.trim()).apply { id = student.id }
studentRepository.updateStudentNick(studentNick)
}.onEach {
when (it.status) {
Status.LOADING -> Timber.i("Attempt to change a student nick")
Status.SUCCESS -> {
Timber.i("Change a student nick result: Success")
view?.recreateMainView()
}
Status.ERROR -> {
Timber.i("Change a student result: An exception occurred")
errorHandler.dispatch(it.error!!)
}
}
}
.afterLoading { view?.popView() }
.launch()
}
}

View File

@ -0,0 +1,14 @@
package io.github.wulkanowy.ui.modules.account.accountedit
import io.github.wulkanowy.ui.base.BaseView
interface AccountEditView : BaseView {
fun initView()
fun popView()
fun recreateMainView()
fun showCurrentNick(nick: String)
}

View File

@ -52,9 +52,9 @@ class AccountQuickPresenter @Inject constructor(
errorHandler.dispatch(it.error!!) errorHandler.dispatch(it.error!!)
} }
} }
}.afterLoading { }
view?.popView() .afterLoading { view?.popView() }
}.launch("switch") .launch("switch")
} }
private fun loadData() { private fun loadData() {

View File

@ -109,6 +109,7 @@ class StudentInfoFragment :
listOf( listOf(
getString(R.string.student_info_first_name) to studentInfo.firstName, getString(R.string.student_info_first_name) to studentInfo.firstName,
getString(R.string.student_info_second_name) to studentInfo.secondName, getString(R.string.student_info_second_name) to studentInfo.secondName,
getString(R.string.student_info_last_name) to studentInfo.surname,
getString(R.string.student_info_gender) to getString(if (studentInfo.gender == Gender.MALE) R.string.student_info_male else R.string.student_info_female), getString(R.string.student_info_gender) to getString(if (studentInfo.gender == Gender.MALE) R.string.student_info_male else R.string.student_info_female),
getString(R.string.student_info_polish_citizenship) to getString(if (studentInfo.hasPolishCitizenship) R.string.all_yes else R.string.all_no), getString(R.string.student_info_polish_citizenship) to getString(if (studentInfo.hasPolishCitizenship) R.string.all_yes else R.string.all_no),
getString(R.string.student_info_family_name) to studentInfo.familyName, getString(R.string.student_info_family_name) to studentInfo.familyName,

View File

@ -27,6 +27,7 @@ import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.nextSchoolDay import io.github.wulkanowy.utils.nextSchoolDay
import io.github.wulkanowy.utils.nickOrName
import io.github.wulkanowy.utils.previousSchoolDay import io.github.wulkanowy.utils.previousSchoolDay
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -151,8 +152,14 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() {
val remoteView = RemoteViews(context.packageName, layoutId).apply { val remoteView = RemoteViews(context.packageName, layoutId).apply {
setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty) setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty)
setTextViewText(R.id.timetableWidgetDate, date.toFormattedString("EEEE, dd.MM").capitalize()) setTextViewText(
setTextViewText(R.id.timetableWidgetName, student?.studentName ?: context.getString(R.string.all_no_data)) R.id.timetableWidgetDate,
date.toFormattedString("EEEE, dd.MM").capitalize()
)
setTextViewText(
R.id.timetableWidgetName,
student?.nickOrName ?: context.getString(R.string.all_no_data)
)
setRemoteAdapter(R.id.timetableWidgetList, adapterIntent) setRemoteAdapter(R.id.timetableWidgetList, adapterIntent)
setOnClickPendingIntent(R.id.timetableWidgetNext, nextNavIntent) setOnClickPendingIntent(R.id.timetableWidgetNext, nextNavIntent)
setOnClickPendingIntent(R.id.timetableWidgetPrev, prevNavIntent) setOnClickPendingIntent(R.id.timetableWidgetPrev, prevNavIntent)

View File

@ -0,0 +1,5 @@
package io.github.wulkanowy.utils
import io.github.wulkanowy.data.db.entities.Student
inline val Student.nickOrName get() = if (nick.isBlank()) studentName else nick

View File

@ -18,7 +18,7 @@
android:layout_marginStart="24dp" android:layout_marginStart="24dp"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:layout_marginEnd="24dp" android:layout_marginEnd="24dp"
android:text="Modify data" android:text="@string/account_edit_header"
android:textSize="21sp" android:textSize="21sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -34,16 +34,21 @@
android:layout_marginStart="24dp" android:layout_marginStart="24dp"
android:layout_marginTop="28dp" android:layout_marginTop="28dp"
android:layout_marginEnd="24dp" android:layout_marginEnd="24dp"
android:hint="Nick" android:hint="@string/account_edit_nick_hint"
app:endIconMode="clear_text"
app:errorEnabled="true" app:errorEnabled="true"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/accountEditDetailsHeader"> app:layout_constraintTop_toBottomOf="@id/accountEditDetailsHeader">
<requestFocus />
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/accountEditDetailsNickText"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:inputType="textPersonName" /> android:inputType="textPersonName"
android:maxLength="20" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
@ -58,7 +63,7 @@
android:insetTop="0dp" android:insetTop="0dp"
android:insetRight="0dp" android:insetRight="0dp"
android:insetBottom="0dp" android:insetBottom="0dp"
android:text="Save" android:text="@string/all_save"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/accountEditDetailsNick" /> app:layout_constraintTop_toBottomOf="@id/accountEditDetailsNick" />

View File

@ -45,7 +45,7 @@
android:orientation="vertical" android:orientation="vertical"
android:visibility="invisible" android:visibility="invisible"
tools:ignore="UseCompoundDrawables" tools:ignore="UseCompoundDrawables"
tools:visibility="visible"> tools:visibility="gone">
<ImageView <ImageView
android:layout_width="100dp" android:layout_width="100dp"

View File

@ -5,202 +5,269 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<ScrollView <com.google.android.material.progressindicator.CircularProgressIndicator
android:layout_width="match_parent" android:id="@+id/account_details_progress"
android:layout_height="0dp" android:layout_width="wrap_content"
android:fillViewport="true"
app:layout_constraintBottom_toTopOf="@id/accountDetailsSelect"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/accountDetailsAvatar"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="36dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_all_account"
app:tint="?colorPrimary"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/accountDetailsName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/accountDetailsAvatar"
tools:text="Jan Kowalski" />
<TextView
android:id="@+id/accountDetailsSchool"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textColor="?android:textColorSecondary"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/accountDetailsName"
tools:text="Szkoła FakeLog" />
<LinearLayout
android:id="@+id/accountDetailsPersonalData"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginTop="16dp"
android:background="?selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/accountDetailsSchool"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:srcCompat="@drawable/ic_all_account"
app:tint="?colorOnBackground"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="32dp"
android:layout_marginEnd="16dp"
android:gravity="center_vertical"
android:text="@string/account_personal_data"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/accountDetailsAddressData"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="?selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/accountDetailsPersonalData"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:srcCompat="@drawable/ic_all_home"
app:tint="?colorOnBackground"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="32dp"
android:layout_marginEnd="16dp"
android:gravity="center_vertical"
android:text="@string/account_address"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/accountDetailsContactData"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="?selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/accountDetailsAddressData"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:srcCompat="@drawable/ic_all_phone"
app:tint="?colorOnBackground"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="32dp"
android:layout_marginEnd="16dp"
android:gravity="center_vertical"
android:text="@string/account_contact"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/accountDetailsFamilyData"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="?selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/accountDetailsContactData"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:srcCompat="@drawable/ic_account_details_family"
app:tint="?colorOnBackground"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="32dp"
android:layout_marginEnd="16dp"
android:gravity="center_vertical"
android:text="@string/account_family"
android:textSize="16sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<com.google.android.material.button.MaterialButton
android:id="@+id/accountDetailsSelect"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="10dp" android:indeterminate="true"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
android:text="@string/account_select_student"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="@id/accountDetailsLogout" /> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton <LinearLayout
android:id="@+id/accountDetailsLogout" android:id="@+id/account_details_error"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_margin="10dp" android:gravity="center"
android:insetLeft="0dp" android:orientation="vertical"
android:insetTop="0dp" android:visibility="invisible"
android:insetRight="0dp" tools:ignore="UseCompoundDrawables"
android:insetBottom="0dp" tools:visibility="visible">
android:text="@string/account_logout"
app:layout_constraintBottom_toBottomOf="parent" /> <ImageView
android:layout_width="100dp"
android:layout_height="100dp"
app:srcCompat="@drawable/ic_all_account"
app:tint="?colorOnBackground"
tools:ignore="contentDescription" />
<TextView
android:id="@+id/account_details_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/account_details_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/account_details_error_retry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/all_retry" />
</LinearLayout>
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/account_details_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:fillViewport="true"
app:layout_constraintBottom_toTopOf="@id/accountDetailsSelect"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/accountDetailsAvatar"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="36dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_all_account"
app:tint="?colorPrimary"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/accountDetailsName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/accountDetailsAvatar"
tools:text="Jan Kowalski" />
<TextView
android:id="@+id/accountDetailsSchool"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textColor="?android:textColorSecondary"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/accountDetailsName"
tools:text="Szkoła FakeLog" />
<LinearLayout
android:id="@+id/accountDetailsPersonalData"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginTop="16dp"
android:background="?selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/accountDetailsSchool"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:srcCompat="@drawable/ic_all_account"
app:tint="?colorOnBackground"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="32dp"
android:layout_marginEnd="16dp"
android:gravity="center_vertical"
android:text="@string/account_personal_data"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/accountDetailsAddressData"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="?selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/accountDetailsPersonalData"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:srcCompat="@drawable/ic_all_home"
app:tint="?colorOnBackground"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="32dp"
android:layout_marginEnd="16dp"
android:gravity="center_vertical"
android:text="@string/account_address"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/accountDetailsContactData"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="?selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/accountDetailsAddressData"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:srcCompat="@drawable/ic_all_phone"
app:tint="?colorOnBackground"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="32dp"
android:layout_marginEnd="16dp"
android:gravity="center_vertical"
android:text="@string/account_contact"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/accountDetailsFamilyData"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="?selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/accountDetailsContactData"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:srcCompat="@drawable/ic_account_details_family"
app:tint="?colorOnBackground"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="32dp"
android:layout_marginEnd="16dp"
android:gravity="center_vertical"
android:text="@string/account_family"
android:textSize="16sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<com.google.android.material.button.MaterialButton
android:id="@+id/accountDetailsSelect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
android:text="@string/account_select_student"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="@id/accountDetailsLogout" />
<com.google.android.material.button.MaterialButton
android:id="@+id/accountDetailsLogout"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
android:text="@string/account_logout"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -237,9 +237,7 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginTop="48dp" android:layout_marginTop="48dp"
android:layout_marginEnd="24dp" android:layout_marginEnd="24dp"
android:layout_marginBottom="16dp"
android:text="@string/login_sign_in" android:text="@string/login_sign_in"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/loginFormHostLayout" /> app:layout_constraintTop_toBottomOf="@+id/loginFormHostLayout" />

View File

@ -412,6 +412,12 @@
<string name="student_info_phones">Phones</string> <string name="student_info_phones">Phones</string>
<string name="student_info_male">Male</string> <string name="student_info_male">Male</string>
<string name="student_info_female">Female</string> <string name="student_info_female">Female</string>
<string name="student_info_last_name">Last name</string>
<!--Account edit-->
<string name="account_edit_nick_hint">Nick</string>
<string name="account_edit_header">Add nick</string>
<!--Log viewer--> <!--Log viewer-->
@ -444,6 +450,7 @@
<string name="all_search_hint">Search…</string> <string name="all_search_hint">Search…</string>
<string name="all_yes">Yes</string> <string name="all_yes">Yes</string>
<string name="all_no">No</string> <string name="all_no">No</string>
<string name="all_save">Save</string>
<!--Timetable Widget--> <!--Timetable Widget-->