forked from github/wulkanowy-mirror
Add nick for student (#1119)
This commit is contained in:
parent
39534aeda4
commit
3e3a080b70
2142
app/schemas/io.github.wulkanowy.data.db.AppDatabase/32.json
Normal file
2142
app/schemas/io.github.wulkanowy.data.db.AppDatabase/32.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -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.Migration30
|
||||
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.Migration5
|
||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||
@ -128,7 +129,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 31
|
||||
const val VERSION_SCHEMA = 32
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> {
|
||||
return arrayOf(
|
||||
@ -161,7 +162,8 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration28(),
|
||||
Migration29(),
|
||||
Migration30(),
|
||||
Migration31()
|
||||
Migration31(),
|
||||
Migration32()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,9 @@ import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy.ABORT
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
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 javax.inject.Singleton
|
||||
|
||||
@ -20,6 +22,9 @@ interface StudentDao {
|
||||
@Delete
|
||||
suspend fun delete(student: Student)
|
||||
|
||||
@Update(entity = Student::class)
|
||||
suspend fun update(studentNick: StudentNick)
|
||||
|
||||
@Query("SELECT * FROM Students WHERE is_current = 1")
|
||||
suspend fun loadCurrent(): Student?
|
||||
|
||||
|
@ -79,4 +79,6 @@ data class Student(
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
|
||||
var nick = ""
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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 \"\"")
|
||||
}
|
||||
}
|
||||
|
@ -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.StudentDao
|
||||
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.exceptions.NoCurrentStudentException
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
@ -25,39 +26,59 @@ class StudentRepository @Inject constructor(
|
||||
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> {
|
||||
return sdk.getStudentsFromMobileApi(token, pin, symbol, "").mapToEntities()
|
||||
}
|
||||
suspend fun getStudentsApi(
|
||||
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> {
|
||||
return sdk.getStudentsFromScrapper(email, password, scrapperBaseUrl, symbol).mapToEntities(password)
|
||||
}
|
||||
suspend fun getStudentsScrapper(
|
||||
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> {
|
||||
return sdk.getStudentsHybrid(email, password, scrapperBaseUrl, "", symbol).mapToEntities(password)
|
||||
}
|
||||
suspend fun getStudentsHybrid(
|
||||
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) =
|
||||
withContext(dispatchers.backgroundThread) {
|
||||
studentDb.loadStudentsWithSemesters().map {
|
||||
it.apply {
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) student.password = decrypt(student.password)
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
student.password = decrypt(student.password)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getStudentById(id: Int) = withContext(dispatchers.backgroundThread) {
|
||||
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()
|
||||
|
||||
suspend fun getCurrentStudent(decryptPass: Boolean = true) = withContext(dispatchers.backgroundThread) {
|
||||
suspend fun getCurrentStudent(decryptPass: Boolean = true) =
|
||||
withContext(dispatchers.backgroundThread) {
|
||||
studentDb.loadCurrent()?.apply {
|
||||
if (decryptPass && Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) password = decrypt(password)
|
||||
if (decryptPass && Sdk.Mode.valueOf(loginMode) != Sdk.Mode.API) {
|
||||
password = decrypt(password)
|
||||
}
|
||||
}
|
||||
} ?: throw NoCurrentStudentException()
|
||||
|
||||
@ -66,8 +87,9 @@ class StudentRepository @Inject constructor(
|
||||
|
||||
return withContext(dispatchers.backgroundThread) {
|
||||
studentDb.insertAll(studentsWithSemesters.map { it.student }.map {
|
||||
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) it.copy(password = encrypt(it.password, context))
|
||||
else it
|
||||
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) {
|
||||
it.copy(password = encrypt(it.password, context))
|
||||
} else it
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -79,7 +101,7 @@ class StudentRepository @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun logoutStudent(student: Student) {
|
||||
studentDb.delete(student)
|
||||
}
|
||||
suspend fun logoutStudent(student: Student) = studentDb.delete(student)
|
||||
|
||||
suspend fun updateStudentNick(studentNick: StudentNick) = studentDb.update(studentNick)
|
||||
}
|
||||
|
@ -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.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import io.github.wulkanowy.utils.toTimestamp
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
@ -41,17 +42,23 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
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 {
|
||||
return day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30)
|
||||
}
|
||||
private fun getUpcomingLessonTime(
|
||||
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) {
|
||||
withContext(dispatchersProvider.backgroundThread) {
|
||||
lessons.sortedBy { it.start }.forEachIndexed { index, 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))
|
||||
|
||||
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) {
|
||||
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) {
|
||||
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) return cancelScheduled(lessons, student.studentId)
|
||||
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) {
|
||||
return cancelScheduled(lessons, student.studentId)
|
||||
}
|
||||
|
||||
withContext(dispatchersProvider.backgroundThread) {
|
||||
lessons.groupBy { it.date }
|
||||
@ -82,13 +94,28 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
val intent = createIntent(student, lesson, active.getOrNull(index + 1))
|
||||
|
||||
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()) {
|
||||
scheduleBroadcast(intent, student.studentId, NOTIFICATION_TYPE_CURRENT, lesson.start)
|
||||
scheduleBroadcast(
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_CURRENT,
|
||||
lesson.start
|
||||
)
|
||||
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 {
|
||||
return Intent(context, TimetableNotificationReceiver::class.java).apply {
|
||||
putExtra(STUDENT_ID, student.studentId)
|
||||
putExtra(STUDENT_NAME, student.studentName)
|
||||
putExtra(STUDENT_NAME, student.nickOrName)
|
||||
putExtra(LESSON_ROOM, lesson.room)
|
||||
putExtra(LESSON_START, lesson.start.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) {
|
||||
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
||||
private fun scheduleBroadcast(
|
||||
intent: Intent,
|
||||
studentId: Int,
|
||||
notificationType: Int,
|
||||
time: LocalDateTime
|
||||
) {
|
||||
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
||||
alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
||||
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
||||
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||
it.putExtra(LESSON_TYPE, notificationType)
|
||||
}, 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"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.databinding.ItemAccountBinding
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import javax.inject.Inject
|
||||
|
||||
class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter<WidgetConfigureAdapter.ItemViewHolder>() {
|
||||
@ -28,7 +29,7 @@ class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter<Widget
|
||||
val (student, isCurrent) = items[position]
|
||||
|
||||
with(holder.binding) {
|
||||
accountItemName.text = "${student.studentName} ${student.className}"
|
||||
accountItemName.text = "${student.nickOrName} ${student.className}"
|
||||
accountItemSchool.text = student.schoolName
|
||||
|
||||
with(accountItemImage) {
|
||||
|
@ -12,6 +12,7 @@ import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.databinding.HeaderAccountBinding
|
||||
import io.github.wulkanowy.databinding.ItemAccountBinding
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
@ -84,7 +85,7 @@ class AccountAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.V
|
||||
}.size > 1 && isAccountQuickDialogMode
|
||||
|
||||
with(binding) {
|
||||
accountItemName.text = "${student.studentName} ${diary?.diaryName.orEmpty()}"
|
||||
accountItemName.text = "${student.nickOrName} ${diary?.diaryName.orEmpty()}"
|
||||
accountItemSchool.text = studentWithSemesters.student.schoolName
|
||||
accountItemAccountType.setText(if (student.isParent) R.string.account_type_parent else R.string.account_type_student)
|
||||
accountItemAccountType.visibility = if (isDuplicatedStudent) VISIBLE else GONE
|
||||
|
@ -62,10 +62,16 @@ class AccountPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
flowWithResource { studentRepository.getSavedStudents() }
|
||||
flowWithResource { studentRepository.getSavedStudents(false) }
|
||||
.onEach {
|
||||
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 -> {
|
||||
Timber.i("Loading account result: Success")
|
||||
view?.updateData(createAccountItems(it.data!!))
|
||||
|
@ -9,13 +9,16 @@ import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.get
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
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.databinding.FragmentAccountDetailsBinding
|
||||
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.MainView
|
||||
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment
|
||||
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -43,22 +46,19 @@ class AccountDetailsFragment :
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
arguments?.let {
|
||||
presenter.studentWithSemesters =
|
||||
it.getSerializable(ARGUMENT_KEY) as StudentWithSemesters
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentAccountDetailsBinding.bind(view)
|
||||
presenter.onAttachView(this)
|
||||
presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as StudentWithSemesters)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
binding.accountDetailsErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
binding.accountDetailsErrorDetails.setOnClickListener { presenter.onDetailsClick() }
|
||||
binding.accountDetailsLogout.setOnClickListener { presenter.onRemoveSelected() }
|
||||
binding.accountDetailsSelect.setOnClickListener { presenter.onStudentSelect() }
|
||||
binding.accountDetailsSelect.isEnabled = !presenter.studentWithSemesters.student.isCurrent
|
||||
|
||||
binding.accountDetailsPersonalData.setOnClickListener {
|
||||
presenter.onStudentInfoSelected(StudentInfoView.Type.PERSONAL)
|
||||
@ -76,24 +76,31 @@ class AccountDetailsFragment :
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
menu[0].isVisible = false
|
||||
inflater.inflate(R.menu.action_menu_account_details, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return if (item.itemId == R.id.accountDetailsMenuEdit) {
|
||||
showAccountEditDetailsDialog()
|
||||
return true
|
||||
presenter.onAccountEditSelected()
|
||||
true
|
||||
} else false
|
||||
}
|
||||
|
||||
override fun showAccountData(studentWithSemesters: StudentWithSemesters) {
|
||||
override fun showAccountData(student: Student) {
|
||||
with(binding) {
|
||||
accountDetailsName.text = studentWithSemesters.student.studentName
|
||||
accountDetailsSchool.text = studentWithSemesters.student.schoolName
|
||||
accountDetailsName.text = student.nickOrName
|
||||
accountDetailsSchool.text = student.schoolName
|
||||
}
|
||||
}
|
||||
|
||||
override fun showAccountEditDetailsDialog() {
|
||||
(requireActivity() as MainActivity).showDialogFragment(AccountEditDetailsDialog.newInstance())
|
||||
override fun enableSelectStudentButton(enable: Boolean) {
|
||||
binding.accountDetailsSelect.isEnabled = enable
|
||||
}
|
||||
|
||||
override fun showAccountEditDetailsDialog(student: Student) {
|
||||
(requireActivity() as MainActivity).showDialogFragment(
|
||||
AccountEditDialog.newInstance(student)
|
||||
)
|
||||
}
|
||||
|
||||
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() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
|
@ -1,5 +1,6 @@
|
||||
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.db.entities.StudentWithSemesters
|
||||
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.utils.afterLoading
|
||||
import io.github.wulkanowy.utils.flowWithResource
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
@ -19,14 +21,74 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
private val syncManager: SyncManager
|
||||
) : 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)
|
||||
view.initView()
|
||||
Timber.i("Account details view was initialized")
|
||||
studentId = studentWithSemesters.student.id
|
||||
|
||||
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) {
|
||||
@ -97,4 +159,14 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
view?.popView()
|
||||
}.launch("logout")
|
||||
}
|
||||
|
||||
private fun showErrorViewOnError(message: String, error: Throwable) {
|
||||
view?.run {
|
||||
lastError = error
|
||||
setErrorDetails(message)
|
||||
showErrorView(true)
|
||||
showContent(false)
|
||||
showProgress(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
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.ui.base.BaseView
|
||||
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
|
||||
@ -8,9 +9,9 @@ interface AccountDetailsView : BaseView {
|
||||
|
||||
fun initView()
|
||||
|
||||
fun showAccountData(studentWithSemesters: StudentWithSemesters)
|
||||
fun showAccountData(student: Student)
|
||||
|
||||
fun showAccountEditDetailsDialog()
|
||||
fun showAccountEditDetailsDialog(student: Student)
|
||||
|
||||
fun showLogoutConfirmDialog()
|
||||
|
||||
@ -18,8 +19,18 @@ interface AccountDetailsView : BaseView {
|
||||
|
||||
fun recreateMainView()
|
||||
|
||||
fun enableSelectStudentButton(enable: Boolean)
|
||||
|
||||
fun openStudentInfoView(
|
||||
infoType: StudentInfoView.Type,
|
||||
studentWithSemesters: StudentWithSemesters
|
||||
)
|
||||
|
||||
fun showErrorView(show: Boolean)
|
||||
|
||||
fun setErrorDetails(message: String)
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
}
|
||||
|
@ -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() }
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -52,9 +52,9 @@ class AccountQuickPresenter @Inject constructor(
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}.afterLoading {
|
||||
view?.popView()
|
||||
}.launch("switch")
|
||||
}
|
||||
.afterLoading { view?.popView() }
|
||||
.launch("switch")
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
|
@ -109,6 +109,7 @@ class StudentInfoFragment :
|
||||
listOf(
|
||||
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_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_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,
|
||||
|
@ -27,6 +27,7 @@ import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
||||
import io.github.wulkanowy.utils.nextSchoolDay
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import io.github.wulkanowy.utils.previousSchoolDay
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
@ -151,8 +152,14 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() {
|
||||
|
||||
val remoteView = RemoteViews(context.packageName, layoutId).apply {
|
||||
setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty)
|
||||
setTextViewText(R.id.timetableWidgetDate, date.toFormattedString("EEEE, dd.MM").capitalize())
|
||||
setTextViewText(R.id.timetableWidgetName, student?.studentName ?: context.getString(R.string.all_no_data))
|
||||
setTextViewText(
|
||||
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)
|
||||
setOnClickPendingIntent(R.id.timetableWidgetNext, nextNavIntent)
|
||||
setOnClickPendingIntent(R.id.timetableWidgetPrev, prevNavIntent)
|
||||
|
@ -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
|
@ -18,7 +18,7 @@
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:text="Modify data"
|
||||
android:text="@string/account_edit_header"
|
||||
android:textSize="21sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@ -34,16 +34,21 @@
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="28dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:hint="Nick"
|
||||
android:hint="@string/account_edit_nick_hint"
|
||||
app:endIconMode="clear_text"
|
||||
app:errorEnabled="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountEditDetailsHeader">
|
||||
|
||||
<requestFocus />
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/accountEditDetailsNickText"
|
||||
android:layout_width="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.button.MaterialButton
|
||||
@ -58,7 +63,7 @@
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:text="Save"
|
||||
android:text="@string/all_save"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountEditDetailsNick" />
|
@ -45,7 +45,7 @@
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible"
|
||||
tools:ignore="UseCompoundDrawables"
|
||||
tools:visibility="visible">
|
||||
tools:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="100dp"
|
||||
|
@ -5,6 +5,72 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/account_details_progress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/account_details_error"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible"
|
||||
tools:ignore="UseCompoundDrawables"
|
||||
tools:visibility="visible">
|
||||
|
||||
<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"
|
||||
@ -203,4 +269,5 @@
|
||||
android:insetBottom="0dp"
|
||||
android:text="@string/account_logout"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -237,9 +237,7 @@
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginTop="48dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/login_sign_in"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginFormHostLayout" />
|
||||
|
||||
|
@ -412,6 +412,12 @@
|
||||
<string name="student_info_phones">Phones</string>
|
||||
<string name="student_info_male">Male</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-->
|
||||
@ -444,6 +450,7 @@
|
||||
<string name="all_search_hint">Search…</string>
|
||||
<string name="all_yes">Yes</string>
|
||||
<string name="all_no">No</string>
|
||||
<string name="all_save">Save</string>
|
||||
|
||||
|
||||
<!--Timetable Widget-->
|
||||
|
Loading…
x
Reference in New Issue
Block a user