[UI] Refactor app dialogs. (#103)

* [UI] Add new base dialog classes.

* [UI] Migrate dialogs to the new base classes.
This commit is contained in:
Kuba Szczodrzyński 2021-10-22 16:57:10 +02:00 committed by GitHub
parent fd62653d79
commit f3e2d21b89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
71 changed files with 1812 additions and 2058 deletions

View File

@ -420,7 +420,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
App.db.profileDao().getByIdNow(profile.identifier.toInt())
} ?: return@launch
drawer.close()
ProfileConfigDialog(this@MainActivity, appProfile)
ProfileConfigDialog(this@MainActivity, appProfile).show()
}
true
} else {
@ -529,7 +529,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
if (app.config.appVersion < BuildConfig.VERSION_CODE) {
// force an AppSync after update
app.config.sync.lastAppSync = 0L
ChangelogDialog(this)
ChangelogDialog(this).show()
if (app.config.appVersion < 170) {
//Intent intent = new Intent(this, ChangelogIntroActivity.class);
//startActivity(intent);
@ -587,7 +587,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
.withIcon(CommunityMaterial.Icon.cmd_download_outline)
.withOnClickListener {
bottomSheet.close()
SyncViewListDialog(this, navTargetId)
SyncViewListDialog(this, navTargetId).show()
},
BottomSheetSeparatorItem(false),
BottomSheetPrimaryItem(false)
@ -690,7 +690,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
Type.NOT_AVAILABLE -> {
swipeRefreshLayout.isRefreshing = false
loadTarget(DRAWER_ITEM_HOME)
RegisterUnavailableDialog(this, error.status!!)
RegisterUnavailableDialog(this, error.status!!).show()
return
}
Type.API_ERROR -> {
@ -722,7 +722,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onUpdateEvent(event: Update) {
EventBus.getDefault().removeStickyEvent(event)
UpdateAvailableDialog(this, event)
UpdateAvailableDialog(this, event).show()
}
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
@ -730,7 +730,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
EventBus.getDefault().removeStickyEvent(event)
val error = app.availabilityManager.check(app.profile, cacheOnly = true)
if (error != null) {
RegisterUnavailableDialog(this, error.status!!)
RegisterUnavailableDialog(this, error.status!!).show()
}
}
@ -797,7 +797,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
if (event.error.errorCode == ERROR_VULCAN_API_DEPRECATED) {
if (event.error.profileId != App.profileId)
return
ErrorDetailsDialog(this, listOf(event.error))
ErrorDetailsDialog(this, listOf(event.error)).show()
}
navView.toolbar.apply {
subtitleFormat = R.string.toolbar_subtitle
@ -894,7 +894,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
this,
extras.getString("serverMessageTitle") ?: getString(R.string.app_name),
extras.getString("serverMessageText") ?: ""
)
).show()
true
}
"feedbackMessage" -> {
@ -917,7 +917,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
this,
App.profileId,
defaultDate = date
)
).show()
true
}
else -> false

View File

@ -103,7 +103,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
activity,
listOf(apiError),
R.string.error_occured
)
).show()
null
}
null

View File

@ -77,14 +77,14 @@ class AgendaFragment : Fragment(), CoroutineScope {
activity,
app.profileId,
defaultDate = AgendaFragmentDefault.selectedDate
)
).show()
},
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_agenda_config)
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
.withOnClickListener {
activity.bottomSheet.close()
AgendaConfigDialog(activity, true, null, null)
AgendaConfigDialog(activity, true, null, null).show()
},
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_agenda_change_view)
@ -124,7 +124,7 @@ class AgendaFragment : Fragment(), CoroutineScope {
activity,
app.profileId,
defaultDate = AgendaFragmentDefault.selectedDate
)
).show()
}
activity.gainAttention()
@ -188,7 +188,7 @@ class AgendaFragment : Fragment(), CoroutineScope {
unreadEventDates.remove(date.value)
}
DayDialog(activity, app.profileId, date)
DayDialog(activity, app.profileId, date).show()
}}
b.progressBar.visibility = View.GONE

View File

@ -139,7 +139,7 @@ class AgendaFragmentDefault(
val c = Calendar.getInstance()
c.time = dayItem.date
if (c.timeInMillis == selectedDate.inMillis) {
DayDialog(activity, app.profileId, selectedDate)
DayDialog(activity, app.profileId, selectedDate).show()
}
}
@ -147,16 +147,25 @@ class AgendaFragmentDefault(
val date = Date.fromCalendar(event.instanceDay)
when (event) {
is AgendaEvent -> EventDetailsDialog(activity, event.event)
is LessonChangesEvent -> LessonChangesDialog(activity, app.profileId, date)
is AgendaEvent -> EventDetailsDialog(activity, event.event).show()
is LessonChangesEvent -> LessonChangesDialog(
activity = activity,
profileId = app.profileId,
defaultDate = date
).show()
is TeacherAbsenceEvent -> TeacherAbsenceDialog(
activity,
app.profileId,
date
)
is AgendaEventGroup -> DayDialog(activity, app.profileId, date, eventTypeId = event.typeId)
activity = activity,
profileId = app.profileId,
date = date
).show()
is AgendaEventGroup -> DayDialog(
activity = activity,
profileId = app.profileId,
date = date,
eventTypeId = event.typeId,
).show()
is BaseCalendarEvent -> if (event.isPlaceHolder)
DayDialog(activity, app.profileId, date)
DayDialog(activity, app.profileId, date).show()
}
if (event is BaseEvent && event.showItemBadge) {

View File

@ -4,12 +4,11 @@
package pl.szczodrzynski.edziennik.ui.agenda
import android.view.LayoutInflater
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
@ -23,6 +22,7 @@ import pl.szczodrzynski.edziennik.ui.agenda.lessonchanges.LessonChangesEventRend
import pl.szczodrzynski.edziennik.ui.agenda.teacherabsence.TeacherAbsenceDialog
import pl.szczodrzynski.edziennik.ui.agenda.teacherabsence.TeacherAbsenceEvent
import pl.szczodrzynski.edziennik.ui.agenda.teacherabsence.TeacherAbsenceEventRenderer
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.ui.event.EventDetailsDialog
import pl.szczodrzynski.edziennik.ui.event.EventListAdapter
import pl.szczodrzynski.edziennik.ui.event.EventManualDialog
@ -30,87 +30,67 @@ import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.edziennik.utils.models.Week
import kotlin.coroutines.CoroutineContext
class DayDialog(
val activity: AppCompatActivity,
val profileId: Int,
val date: Date,
val eventTypeId: Long? = null,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "DayDialog"
}
activity: AppCompatActivity,
private val profileId: Int,
private val date: Date,
private val eventTypeId: Long? = null,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<DialogDayBinding>(activity, onShowListener, onDismissListener) {
private lateinit var app: App
private lateinit var b: DialogDayBinding
private lateinit var dialog: AlertDialog
override val TAG = "DayDialog"
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun getTitleRes(): Int? = null
override fun inflate(layoutInflater: LayoutInflater) =
DialogDayBinding.inflate(layoutInflater)
override fun getPositiveButtonText() = R.string.close
override fun getNeutralButtonText() = R.string.add
private lateinit var adapter: EventListAdapter
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
b = DialogDayBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setView(b.root)
.setPositiveButton(R.string.close) { dialog, _ ->
dialog.dismiss()
}
.setNeutralButton(R.string.add, null)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
override suspend fun onNeutralClick(): Boolean {
EventManualDialog(
activity,
profileId,
defaultDate = date,
onShowListener = onShowListener,
onDismissListener = onDismissListener
).show()
return NO_DISMISS
}
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.onClick {
EventManualDialog(
activity,
profileId,
defaultDate = date,
onShowListener = onShowListener,
onDismissListener = onDismissListener
)
}
update()
}}
private fun update() { launch {
override suspend fun onShow() {
b.dayDate.setText(
R.string.dialog_day_date_format,
Week.getFullDayName(date.weekDay),
date.formattedString
R.string.dialog_day_date_format,
Week.getFullDayName(date.weekDay),
date.formattedString
)
val lessons = withContext(Dispatchers.Default) {
app.db.timetableDao().getAllForDateNow(profileId, date)
}.filter { it.type != Lesson.TYPE_NO_LESSONS }
if (lessons.isNotEmpty()) { run {
val startTime = lessons.first().startTime ?: return@run
val endTime = lessons.last().endTime ?: return@run
val diff = Time.diff(startTime, endTime)
if (lessons.isNotEmpty()) {
run {
val startTime = lessons.first().startTime ?: return@run
val endTime = lessons.last().endTime ?: return@run
val diff = Time.diff(startTime, endTime)
b.lessonsInfo.setText(
b.lessonsInfo.setText(
R.string.dialog_day_lessons_info,
startTime.stringHM,
endTime.stringHM,
lessons.size.toString(),
diff.hour.toString(),
diff.minute.toString()
)
)
b.lessonsInfo.visibility = View.VISIBLE
}}
b.lessonsInfo.visibility = View.VISIBLE
}
}
val lessonChanges = withContext(Dispatchers.Default) {
app.db.timetableDao().getChangesForDateNow(profileId, date)
@ -133,7 +113,7 @@ class DayDialog(
date,
onShowListener = onShowListener,
onDismissListener = onDismissListener
)
).show()
}
}
b.lessonChangesFrame.isVisible = lessonChanges.isNotEmpty()
@ -158,36 +138,36 @@ class DayDialog(
date,
onShowListener = onShowListener,
onDismissListener = onDismissListener
)
).show()
}
}
b.teacherAbsenceFrame.isVisible = teacherAbsences.isNotEmpty()
adapter = EventListAdapter(
activity = activity,
showWeekDay = false,
showDate = false,
showType = true,
showTime = true,
showSubject = true,
markAsSeen = true,
onItemClick = {
EventDetailsDialog(
activity,
it,
onShowListener = onShowListener,
onDismissListener = onDismissListener
)
},
onEventEditClick = {
EventManualDialog(
activity,
it.profileId,
editingEvent = it,
onShowListener = onShowListener,
onDismissListener = onDismissListener
)
}
activity = activity,
showWeekDay = false,
showDate = false,
showType = true,
showTime = true,
showSubject = true,
markAsSeen = true,
onItemClick = {
EventDetailsDialog(
activity,
it,
onShowListener = onShowListener,
onDismissListener = onDismissListener
).show()
},
onEventEditClick = {
EventManualDialog(
activity,
it.profileId,
editingEvent = it,
onShowListener = onShowListener,
onDismissListener = onDismissListener
).show()
}
)
app.db.eventDao().getAllByDate(profileId, date).observe(activity) { events ->
@ -217,5 +197,5 @@ class DayDialog(
b.eventsNoData.visibility = View.VISIBLE
}
}
}}
}
}

View File

@ -1,76 +1,53 @@
package pl.szczodrzynski.edziennik.ui.agenda.lessonchanges
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.DialogLessonChangeListBinding
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.ui.timetable.LessonDetailsDialog
import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.coroutines.CoroutineContext
class LessonChangesDialog(
val activity: AppCompatActivity,
val profileId: Int,
private val defaultDate: Date,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
const val TAG = "LessonChangeDialog"
}
activity: AppCompatActivity,
private val profileId: Int,
private val defaultDate: Date,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<DialogLessonChangeListBinding>(activity, onShowListener, onDismissListener) {
private val app by lazy { activity.application as App }
override val TAG = "LessonChangesDialog"
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun getTitle(): String = defaultDate.formattedString
override fun getTitleRes(): Int? = null
override fun inflate(layoutInflater: LayoutInflater) =
DialogLessonChangeListBinding.inflate(layoutInflater)
private lateinit var b: DialogLessonChangeListBinding
private lateinit var dialog: AlertDialog
override fun getPositiveButtonText() = R.string.close
init { run {
if (activity.isFinishing)
return@run
job = Job()
onShowListener?.invoke(TAG)
b = DialogLessonChangeListBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(defaultDate.formattedString)
.setView(b.root)
.setPositiveButton(R.string.close) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.create()
loadLessonChanges()
}}
private fun loadLessonChanges() { launch {
override suspend fun onShow() {
val lessonChanges = withContext(Dispatchers.Default) {
app.db.timetableDao().getChangesForDateNow(profileId, defaultDate)
}
val adapter = LessonChangesAdapter(
activity,
onItemClick = {
LessonDetailsDialog(
activity,
it,
onShowListener = onShowListener,
onDismissListener = onDismissListener
)
}
activity,
onItemClick = {
LessonDetailsDialog(
activity,
it,
onShowListener = onShowListener,
onDismissListener = onDismissListener
).show()
}
).apply {
items = lessonChanges
}
b.lessonChangeView.adapter = adapter
b.lessonChangeView.layoutManager = LinearLayoutManager(activity)
dialog.show()
}}
}
}

View File

@ -1,57 +1,42 @@
package pl.szczodrzynski.edziennik.ui.agenda.teacherabsence
import android.view.LayoutInflater
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.DialogTeacherAbsenceListBinding
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.utils.models.Date
class TeacherAbsenceDialog(
val activity: AppCompatActivity,
val profileId: Int,
val date: Date,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) {
companion object {
private const val TAG = "TeacherAbsenceDialog"
}
activity: AppCompatActivity,
private val profileId: Int,
private val date: Date,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<DialogTeacherAbsenceListBinding>(activity, onShowListener, onDismissListener) {
private val app by lazy { activity.application as App }
override val TAG = "TeacherAbsenceDialog"
private lateinit var b: DialogTeacherAbsenceListBinding
private lateinit var dialog: AlertDialog
override fun getTitle(): String = date.formattedString
override fun getTitleRes(): Int? = null
override fun inflate(layoutInflater: LayoutInflater) =
DialogTeacherAbsenceListBinding.inflate(layoutInflater)
init { run {
if (activity.isFinishing)
return@run
b = DialogTeacherAbsenceListBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(date.formattedString)
.setView(b.root)
.setPositiveButton(R.string.close) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.create()
override fun getPositiveButtonText() = R.string.close
override suspend fun onShow() {
b.teacherAbsenceView.setHasFixedSize(true)
b.teacherAbsenceView.layoutManager = LinearLayoutManager(activity)
app.db.teacherAbsenceDao().getAllByDate(profileId, date).observe(activity as LifecycleOwner, Observer { absenceList ->
app.db.teacherAbsenceDao().getAllByDate(profileId, date).observe(
activity as LifecycleOwner
) { absenceList ->
val adapter = TeacherAbsenceAdapter(activity, date, absenceList)
b.teacherAbsenceView.adapter = adapter
b.teacherAbsenceView.visibility = View.VISIBLE
})
dialog.show()
}}
}
}
}

View File

@ -4,54 +4,33 @@
package pl.szczodrzynski.edziennik.ui.attendance
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.ColorUtils
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
import pl.szczodrzynski.edziennik.databinding.AttendanceDetailsDialogBinding
import pl.szczodrzynski.edziennik.ext.setTintColor
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.utils.BetterLink
import kotlin.coroutines.CoroutineContext
class AttendanceDetailsDialog(
val activity: AppCompatActivity,
val attendance: AttendanceFull,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "AttendanceDetailsDialog"
}
activity: AppCompatActivity,
private val attendance: AttendanceFull,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<AttendanceDetailsDialogBinding>(activity, onShowListener, onDismissListener) {
private lateinit var app: App
private lateinit var b: AttendanceDetailsDialogBinding
private lateinit var dialog: AlertDialog
override val TAG = "AttendanceDetailsDialog"
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun getTitleRes(): Int? = null
override fun inflate(layoutInflater: LayoutInflater) =
AttendanceDetailsDialogBinding.inflate(layoutInflater)
// local variables go here
override fun getPositiveButtonText() = R.string.close
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
b = AttendanceDetailsDialogBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setView(b.root)
.setPositiveButton(R.string.close, null)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
override suspend fun onShow() {
val manager = app.attendanceManager
val attendanceColor = manager.getAttendanceColor(attendance)
@ -69,5 +48,5 @@ class AttendanceDetailsDialog(
onActionSelected = dialog::dismiss
)
}
}}
}
}

View File

@ -67,7 +67,7 @@ class AttendanceFragment : Fragment(), CoroutineScope {
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
.withOnClickListener(View.OnClickListener {
activity.bottomSheet.close()
AttendanceConfigDialog(activity, true, null, null)
AttendanceConfigDialog(activity, true, null, null).show()
}),
BottomSheetSeparatorItem(true),
BottomSheetPrimaryItem(true)

View File

@ -96,7 +96,7 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope {
}})
adapter.onAttendanceClick = {
AttendanceDetailsDialog(activity, it)
AttendanceDetailsDialog(activity, it).show()
}
}; return true}

View File

@ -108,7 +108,7 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope {
}})
adapter.onAttendanceClick = {
AttendanceDetailsDialog(activity, it)
AttendanceDetailsDialog(activity, it).show()
}
b.toggleGroup.check(when (periodSelection) {

View File

@ -5,58 +5,34 @@
package pl.szczodrzynski.edziennik.ui.captcha
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.RecaptchaViewBinding
import pl.szczodrzynski.edziennik.ext.onClick
import kotlin.coroutines.CoroutineContext
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
class LibrusCaptchaDialog(
val activity: AppCompatActivity,
val onSuccess: (recaptchaCode: String) -> Unit,
val onFailure: (() -> Unit)?,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "LibrusCaptchaDialog"
}
activity: AppCompatActivity,
private val onSuccess: (recaptchaCode: String) -> Unit,
private val onFailure: (() -> Unit)?,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<RecaptchaViewBinding>(activity, onShowListener, onDismissListener) {
override val TAG = "LibrusCaptchaDialog"
private lateinit var app: App
private lateinit var b: RecaptchaViewBinding
private lateinit var dialog: AlertDialog
override fun getTitleRes(): Int? = null
override fun inflate(layoutInflater: LayoutInflater) =
RecaptchaViewBinding.inflate(layoutInflater)
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun getNegativeButtonText() = R.string.cancel
private lateinit var checkboxBackground: Drawable
private lateinit var checkboxForeground: Drawable
private var success = false
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
b = RecaptchaViewBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setView(b.root)
.setNegativeButton(R.string.cancel, null)
.setOnDismissListener {
if (!success)
onFailure?.invoke()
onDismissListener?.invoke(TAG)
}
.show()
override suspend fun onShow() {
checkboxBackground = b.checkbox.background
checkboxForeground = b.checkbox.foreground
success = false
@ -69,23 +45,28 @@ class LibrusCaptchaDialog(
b.checkbox.foreground = null
b.progress.visibility = View.VISIBLE
RecaptchaDialog(
activity,
siteKey = "6Lf48moUAAAAAB9ClhdvHr46gRWR-CN31CXQPG2U",
referer = "https://portal.librus.pl/rodzina/login",
onSuccess = { recaptchaCode ->
b.checkbox.background = checkboxBackground
b.checkbox.foreground = checkboxForeground
b.progress.visibility = View.GONE
success = true
onSuccess(recaptchaCode)
dialog.dismiss()
},
onFailure = {
b.checkbox.background = checkboxBackground
b.checkbox.foreground = checkboxForeground
b.progress.visibility = View.GONE
}
)
activity,
siteKey = "6Lf48moUAAAAAB9ClhdvHr46gRWR-CN31CXQPG2U",
referer = "https://portal.librus.pl/rodzina/login",
onSuccess = { recaptchaCode ->
b.checkbox.background = checkboxBackground
b.checkbox.foreground = checkboxForeground
b.progress.visibility = View.GONE
success = true
onSuccess(recaptchaCode)
dialog.dismiss()
},
onFailure = {
b.checkbox.background = checkboxBackground
b.checkbox.foreground = checkboxForeground
b.progress.visibility = View.GONE
}
).show()
}
}}
}
override fun onDismiss() {
if (!success)
onFailure?.invoke()
}
}

View File

@ -6,129 +6,56 @@ package pl.szczodrzynski.edziennik.ui.captcha
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Handler
import android.util.Log
import android.view.LayoutInflater
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.*
import okhttp3.*
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.RecaptchaDialogBinding
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import java.io.IOException
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class RecaptchaDialog(
val activity: AppCompatActivity,
val siteKey: String,
val referer: String,
val autoRetry: Boolean = true,
val onSuccess: (recaptchaCode: String) -> Unit,
val onFailure: (() -> Unit)? = null,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "RecaptchaDialog"
}
activity: AppCompatActivity,
private val siteKey: String,
private val referer: String,
private val autoRetry: Boolean = true,
private val onSuccess: (recaptchaCode: String) -> Unit,
private val onFailure: (() -> Unit)? = null,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<RecaptchaDialogBinding>(activity, onShowListener, onDismissListener) {
private lateinit var app: App
private val b by lazy { RecaptchaDialogBinding.inflate(LayoutInflater.from(activity)) }
private var dialog: AlertDialog? = null
override val TAG = "RecaptchaDialog"
override fun getTitleRes(): Int? = null
override fun inflate(layoutInflater: LayoutInflater) =
RecaptchaDialogBinding.inflate(layoutInflater)
override fun getPositiveButtonText() = R.string.ok
private val captchaUrl = "https://www.google.com/recaptcha/api/fallback?k=$siteKey"
private var success = false
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private var code = ""
private var payload = ""
init { run {
if (activity.isFinishing)
return@run
app = activity.applicationContext as App
onShowListener?.invoke(TAG)
success = false
launch { initCaptcha() }
}}
private suspend fun initCaptcha() {
withContext(Dispatchers.Default) {
val request = Request.Builder()
.url(captchaUrl)
.addHeader("Referer", referer)
.addHeader("Accept-Language", "pl")
.build()
app.http.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
val html = response.body()?.string() ?: return
Log.d(TAG, html)
parseHtml(html)
}
override fun onFailure(call: Call, e: IOException) {
}
})
override suspend fun onBeforeShow(): Boolean {
val (title, text, bitmap) = withContext(Dispatchers.Default) {
val html = loadCaptchaHtml() ?: return@withContext null
return@withContext loadCaptchaData(html)
} ?: run {
onFailure?.invoke()
return false
}
initViews(title, text, bitmap)
return true
}
private fun parseHtml(html: String) {
launch {
"class=\"rc-imageselect-desc(?:-no-canonical)?\">(.+?) <strong>(.+?)</strong>".toRegex().find(html)?.let {
b.descTitle.text = it.groupValues[1]
b.descText.text = it.groupValues[2]
}
code = "name=\"c\" value=\"([A-z0-9-_]+)\"".toRegex().find(html)?.let { it.groupValues[1] } ?: return@launch
payload = "https://www.google.com/recaptcha/api2/payload?c=$code&k=$siteKey"
withContext(Dispatchers.Default) {
val request = Request.Builder()
.url(payload)
.addHeader("Referer", captchaUrl)
.addHeader("Accept-Language", "pl")
.build()
app.http.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
val bitmap: Bitmap? = BitmapFactory.decodeStream(response.body()?.byteStream())
Handler(activity.mainLooper).post {
if (bitmap == null) {
onFailure?.invoke()
Toast.makeText(activity, "Nie udało się załadować reCAPTCHA.", Toast.LENGTH_SHORT).show()
return@post
}
b.payload.setImageBitmap(bitmap)
showDialog()
}
}
override fun onFailure(call: Call, e: IOException) {
onFailure?.invoke()
}
})
}
}
}
private fun showDialog() {
if (dialog == null) {
dialog = MaterialAlertDialogBuilder(activity)
.setView(b.root)
.setPositiveButton("OK") { _, _ ->
validateAnswer()
}
.setOnDismissListener {
if (!success)
onFailure?.invoke()
onDismissListener?.invoke(TAG)
}
.create()
}
override suspend fun onShow() {
b.image0.isChecked = false
b.image1.isChecked = false
b.image2.isChecked = false
@ -138,51 +65,156 @@ class RecaptchaDialog(
b.image6.isChecked = false
b.image7.isChecked = false
b.image8.isChecked = false
dialog!!.show()
}
private fun validateAnswer() {
launch {
val list = mutableListOf(
"c=$code"
)
if (b.image0.isChecked) list += "response=0"
if (b.image1.isChecked) list += "response=1"
if (b.image2.isChecked) list += "response=2"
if (b.image3.isChecked) list += "response=3"
if (b.image4.isChecked) list += "response=4"
if (b.image5.isChecked) list += "response=5"
if (b.image6.isChecked) list += "response=6"
if (b.image7.isChecked) list += "response=7"
if (b.image8.isChecked) list += "response=8"
val request = Request.Builder()
.url(captchaUrl)
.addHeader("Referer", captchaUrl)
.addHeader("Accept-Language", "pl")
.addHeader("Origin", "https://www.google.com")
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.post(RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), list.joinToString("&")))
.build()
withContext(Dispatchers.Default) {
override fun onDismiss() {
if (!success)
onFailure?.invoke()
}
private fun initViews(title: String, text: String, bitmap: Bitmap) {
b.descTitle.text = title
b.descText.text = text
b.payload.setImageBitmap(bitmap)
}
private suspend fun loadCaptchaHtml(): String? {
val request = Request.Builder()
.url(captchaUrl)
.addHeader("Referer", referer)
.addHeader("Accept-Language", "pl")
.build()
return suspendCoroutine { cont ->
app.http.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
val html = response.body()?.string()
cont.resume(html)
}
override fun onFailure(call: Call, e: IOException) {
cont.resume(null)
}
})
}
}
private suspend fun loadCaptchaData(html: String): Triple<String, String, Bitmap>? {
var title = ""
var text = ""
"class=\"rc-imageselect-desc(?:-no-canonical)?\">(.+?) <strong>(.+?)</strong>"
.toRegex()
.find(html)
?.let {
title = it.groupValues[1]
text = it.groupValues[2]
}
code = "name=\"c\" value=\"([A-z0-9-_]+)\""
.toRegex()
.find(html)
?.let { it.groupValues[1] }
?: return null
payload = "https://www.google.com/recaptcha/api2/payload?c=$code&k=$siteKey"
val request = Request.Builder()
.url(payload)
.addHeader("Referer", captchaUrl)
.addHeader("Accept-Language", "pl")
.build()
val bitmap = suspendCoroutine<Bitmap?> { cont ->
app.http.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
val bitmap: Bitmap? = BitmapFactory.decodeStream(response.body()?.byteStream())
if (bitmap == null) {
Toast.makeText(
activity,
"Nie udało się załadować reCAPTCHA.",
Toast.LENGTH_SHORT
).show()
}
cont.resume(bitmap)
}
override fun onFailure(call: Call, e: IOException) {
cont.resume(null)
}
})
} ?: return null
return Triple(title, text, bitmap)
}
override suspend fun onPositiveClick(): Boolean {
val list = mutableListOf(
"c=$code"
)
if (b.image0.isChecked) list += "response=0"
if (b.image1.isChecked) list += "response=1"
if (b.image2.isChecked) list += "response=2"
if (b.image3.isChecked) list += "response=3"
if (b.image4.isChecked) list += "response=4"
if (b.image5.isChecked) list += "response=5"
if (b.image6.isChecked) list += "response=6"
if (b.image7.isChecked) list += "response=7"
if (b.image8.isChecked) list += "response=8"
val request = Request.Builder()
.url(captchaUrl)
.addHeader("Referer", captchaUrl)
.addHeader("Accept-Language", "pl")
.addHeader("Origin", "https://www.google.com")
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.post(RequestBody.create(
MediaType.parse("application/x-www-form-urlencoded"),
list.joinToString("&"),
))
.build()
val (code, html) = withContext(Dispatchers.Default) {
return@withContext suspendCoroutine<Pair<String?, String?>> { cont ->
app.http.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
val html = response.body()?.string() ?: return
val match = "<textarea.+?>([A-z0-9-_]+)</textarea>".toRegex().find(html)
if (match == null) {
parseHtml(html)
val html = response.body()?.string() ?: run {
cont.resume(null to null)
return
}
Handler(activity.mainLooper).post {
success = true
onSuccess(match.groupValues[1])
val match = "<textarea.+?>([A-z0-9-_]+)</textarea>".toRegex().find(html)
if (match == null) {
cont.resume(null to html)
} else {
cont.resume(match.groupValues[1] to null)
}
}
override fun onFailure(call: Call, e: IOException) {
cont.resume(null to null)
}
})
}
}
when {
code != null -> {
success = true
onSuccess(code)
return DISMISS
}
html != null -> {
val (title, text, bitmap) = withContext(Dispatchers.Default) {
return@withContext loadCaptchaData(html)
} ?: run {
onFailure?.invoke()
return DISMISS
}
initViews(title, text, bitmap)
return NO_DISMISS
}
else -> {
onFailure?.invoke()
return DISMISS
}
}
}
}

View File

@ -72,7 +72,7 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
}
b.clearProfile.onClick {
ProfileRemoveDialog(activity, App.profileId, "FAKE", noProfileRemoval = true)
ProfileRemoveDialog(activity, App.profileId, "FAKE", noProfileRemoval = true).show()
}
b.removeHomework.onClick {

View File

@ -4,41 +4,32 @@
package pl.szczodrzynski.edziennik.ui.dialogs
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.DialogBellSyncBinding
import pl.szczodrzynski.edziennik.ext.resolveDrawable
import pl.szczodrzynski.edziennik.ext.startCoroutineTimer
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.utils.models.Time
import kotlin.coroutines.CoroutineContext
class BellSyncDialog(
val activity: AppCompatActivity,
private val bellTime: Time,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
activity: AppCompatActivity,
private val bellTime: Time,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<DialogBellSyncBinding>(activity, onShowListener, onDismissListener) {
companion object {
const val TAG = "BellSyncDialog"
}
override val TAG = "BellSyncDialog"
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun getTitleRes() = R.string.bell_sync_title
override fun inflate(layoutInflater: LayoutInflater) =
DialogBellSyncBinding.inflate(layoutInflater)
private lateinit var dialog: AlertDialog
private lateinit var b: DialogBellSyncBinding
private val app by lazy { activity.application as App }
override fun getNeutralButtonText() = R.string.cancel
private var counterJob: Job? = null
@ -50,25 +41,7 @@ class BellSyncDialog(
return Pair(bellDiff, multiplier)
}
init { apply {
if (activity.isFinishing)
return@apply
job = Job()
b = DialogBellSyncBinding.inflate(activity.layoutInflater)
onShowListener?.invoke(TAG)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.bell_sync_title)
.setView(b.root)
.setNeutralButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener {
counterJob?.cancel()
onDismissListener?.invoke(TAG)
}
.show()
initView()
}}
private fun initView() {
override suspend fun onShow() {
b.bellSyncButton.setOnClickListener {
val (bellDiff, multiplier) = actualBellDiff
val bellDiffText = (if (multiplier == -1) '-' else '+') + bellDiff.stringHMS
@ -76,26 +49,29 @@ class BellSyncDialog(
app.config.timetable.bellSyncMultiplier = multiplier
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.bell_sync_title)
.setMessage(app.getString(R.string.bell_sync_results, bellDiffText))
.setPositiveButton(R.string.ok) { resultsDialog, _ ->
resultsDialog.dismiss()
dialog.dismiss()
if (activity is MainActivity) activity.reloadTarget()
}
.show()
.setTitle(R.string.bell_sync_title)
.setMessage(app.getString(R.string.bell_sync_results, bellDiffText))
.setPositiveButton(R.string.ok) { resultsDialog, _ ->
resultsDialog.dismiss()
dialog.dismiss()
if (activity is MainActivity) activity.reloadTarget()
}
.show()
}
if (Time.diff(Time.getNow(), bellTime) > Time(2, 0, 0)) { // Easter egg ^^
b.bellSyncButton.setImageDrawable(R.drawable.ic_bell_wtf.resolveDrawable(app)) // wtf
}
launch {
counterJob = startCoroutineTimer(repeatMillis = 500) {
val (bellDiff, multiplier) = actualBellDiff
val bellDiffText = (if (multiplier == -1) '-' else '+') + bellDiff.stringHMS
b.bellSyncHowto.text = app.getString(R.string.bell_sync_howto, bellTime.stringHM, bellDiffText)
}
counterJob = startCoroutineTimer(repeatMillis = 500) {
val (bellDiff, multiplier) = actualBellDiff
val bellDiffText = (if (multiplier == -1) '-' else '+') + bellDiff.stringHMS
b.bellSyncHowto.text =
app.getString(R.string.bell_sync_howto, bellTime.stringHM, bellDiffText)
}
}
override fun onDismiss() {
counterJob?.cancel()
}
}

View File

@ -4,7 +4,7 @@
package pl.szczodrzynski.edziennik.ui.dialogs
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.*
@ -13,78 +13,74 @@ import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.databinding.DialogBellSyncTimeChooseBinding
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import kotlin.coroutines.CoroutineContext
class BellSyncTimeChooseDialog(
val activity: AppCompatActivity,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
activity: AppCompatActivity,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<DialogBellSyncTimeChooseBinding>(activity, onShowListener, onDismissListener) {
companion object {
const val TAG = "BellSyncTimeChooseDialog"
private const val MAX_DIFF_MINUTES = 10
}
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override val TAG = "BellSyncTimeChooseDialog"
private lateinit var dialog: AlertDialog
private lateinit var b: DialogBellSyncTimeChooseBinding
override fun getTitleRes() = R.string.bell_sync_title
override fun inflate(layoutInflater: LayoutInflater) =
DialogBellSyncTimeChooseBinding.inflate(layoutInflater)
private val app by lazy { activity.application as App }
override fun getPositiveButtonText() = R.string.ok
override fun getNeutralButtonText() = R.string.reset
override fun getNegativeButtonText() = R.string.cancel
override suspend fun onShow() = Unit
private val today = Date.getToday()
private val selectedTime: Time?
get() = b.timeDropdown.selected?.tag as Time?
init { apply {
if (activity.isFinishing)
return@apply
job = Job()
b = DialogBellSyncTimeChooseBinding.inflate(activity.layoutInflater)
onShowListener?.invoke(TAG)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.bell_sync_title)
.setView(b.root)
.setPositiveButton(R.string.ok) { dialog, _ ->
dialog.dismiss()
selectedTime?.let {
BellSyncDialog(activity, it)
}
}
.setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.create()
.apply {
setButton(AlertDialog.BUTTON_NEUTRAL, app.getString(R.string.reset)) { _, _ ->
showResetDialog()
}
}
override suspend fun onPositiveClick(): Boolean {
selectedTime?.let {
BellSyncDialog(activity, it).show()
}
return DISMISS
}
initView()
}}
override suspend fun onNeutralClick(): Boolean {
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.bell_sync_title)
.setMessage(R.string.bell_sync_reset_confirm)
.setPositiveButton(R.string.yes) { dialog, _ ->
app.config.timetable.bellSyncDiff = null
app.config.timetable.bellSyncMultiplier = 0
private fun initView() {
dialog.dismiss()
reload()
if (activity is MainActivity)
activity.reloadTarget()
}
.setNegativeButton(R.string.no, null)
.show()
return NO_DISMISS
}
override suspend fun onBeforeShow(): Boolean {
b.bellSyncHowto.text = app.getString(R.string.bell_sync_choose_howto)
app.config.timetable.bellSyncDiff?.let { bellDiff ->
val multiplier = app.config.timetable.bellSyncMultiplier
val bellDiffText = (if (multiplier == -1) '-' else '+') + bellDiff.stringHMS
b.bellSyncHowto.text = app.getString(R.string.concat_2_strings,
app.getString(R.string.bell_sync_choose_howto),
app.getString(R.string.bell_sync_current_dialog, bellDiffText)
app.getString(R.string.bell_sync_choose_howto),
app.getString(R.string.bell_sync_current_dialog, bellDiffText)
)
}
loadTimeList()
return loadTimeList()
}
private fun checkForLessons(timeList: List<Time>): Boolean {
@ -98,26 +94,30 @@ class BellSyncTimeChooseDialog(
} else false
}
private fun loadTimeList() { launch {
private suspend fun loadTimeList(): Boolean {
val timeItems = withContext(Dispatchers.Default) {
val lessons = app.db.timetableDao().getAllForDateNow(App.profileId, today)
val items = mutableListOf<TextInputDropDown.Item>()
lessons.forEach {
if (it.type != Lesson.TYPE_NO_LESSONS &&
it.type != Lesson.TYPE_CANCELLED &&
it.type != Lesson.TYPE_SHIFTED_SOURCE) {
it.type != Lesson.TYPE_CANCELLED &&
it.type != Lesson.TYPE_SHIFTED_SOURCE
) {
items += TextInputDropDown.Item(
it.displayStartTime?.value?.toLong() ?: return@forEach,
app.getString(R.string.bell_sync_lesson_item, it.displaySubjectName, it.displayStartTime?.stringHM),
tag = it.displayStartTime
it.displayStartTime?.value?.toLong() ?: return@forEach,
app.getString(R.string.bell_sync_lesson_item,
it.displaySubjectName,
it.displayStartTime?.stringHM),
tag = it.displayStartTime
)
items += TextInputDropDown.Item(
it.displayEndTime?.value?.toLong() ?: return@forEach,
app.getString(R.string.bell_sync_break_item, it.displayEndTime?.stringHM),
tag = it.displayEndTime
it.displayEndTime?.value?.toLong() ?: return@forEach,
app.getString(R.string.bell_sync_break_item,
it.displayEndTime?.stringHM),
tag = it.displayEndTime
)
}
}
@ -128,10 +128,11 @@ class BellSyncTimeChooseDialog(
if (!checkForLessons(timeItems.map { it.tag as Time })) {
/* Synchronization not possible */
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.bell_sync_title)
.setMessage(R.string.bell_sync_cannot_now)
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
.show()
.setTitle(R.string.bell_sync_title)
.setMessage(R.string.bell_sync_cannot_now)
.setPositiveButton(R.string.ok, null)
.show()
return false
} else {
b.timeDropdown.clear()
b.timeDropdown.append(timeItems)
@ -144,24 +145,7 @@ class BellSyncTimeChooseDialog(
b.timeDropdown.isEnabled = true
// TODO Fix popup cutting off
dialog.show()
}
}}
private fun showResetDialog() {
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.bell_sync_title)
.setMessage(R.string.bell_sync_reset_confirm)
.setPositiveButton(R.string.yes) { confirmDialog, _ ->
app.config.timetable.bellSyncDiff = null
app.config.timetable.bellSyncMultiplier = 0
confirmDialog.dismiss()
initView()
if (activity is MainActivity) activity.reloadTarget()
}
.setNegativeButton(R.string.no) { dialog, _ -> dialog.dismiss() }
.show()
return true
}
}

View File

@ -4,43 +4,29 @@
package pl.szczodrzynski.edziennik.ui.dialogs
import android.os.Build
import android.widget.ScrollView
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.ext.dp
import pl.szczodrzynski.edziennik.ui.dialogs.base.ViewDialog
import pl.szczodrzynski.edziennik.utils.BetterLinkMovementMethod
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
import kotlin.coroutines.CoroutineContext
class ChangelogDialog(
val activity: AppCompatActivity,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "ChangelogDialog"
}
activity: AppCompatActivity,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : ViewDialog<ScrollView>(activity, onShowListener, onDismissListener) {
private lateinit var app: App
private lateinit var dialog: AlertDialog
override val TAG = "ChangelogDialog"
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun getTitleRes() = R.string.whats_new
override fun getPositiveButtonText() = R.string.close
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
override suspend fun onShow() = Unit
override fun getRootView(): ScrollView {
val textView = TextView(activity)
textView.setPadding(24.dp, 24.dp, 24.dp, 0)
@ -49,29 +35,22 @@ class ChangelogDialog(
}
val commitsUrlPrefix = "https://github.com/szkolny-eu/szkolny-android/commits?author="
text = text.replace("""\[(.+?)]\(@([A-z0-9-]+)\)""".toRegex(), "<a href=\"$commitsUrlPrefix$2\">$1</a>")
text = text.replace("""\s@([A-z0-9-]+)""".toRegex(), " <a href=\"$commitsUrlPrefix$1\">@$1</a>")
text = text.replace(
regex = """\[(.+?)]\(@([A-z0-9-]+)\)""".toRegex(),
replacement = "<a href=\"$commitsUrlPrefix$2\">$1</a>"
)
text = text.replace(
regex = """\s@([A-z0-9-]+)""".toRegex(),
replacement = " <a href=\"$commitsUrlPrefix$1\">@$1</a>"
)
val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
text
else
text.replace("<li>", "<br><li> - ")
textView.text = BetterHtml.fromHtml(activity, html)
textView.text = BetterHtml.fromHtml(activity, text)
textView.movementMethod = BetterLinkMovementMethod.getInstance()
val scrollView = ScrollView(activity)
scrollView.addView(textView)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.whats_new)
.setView(scrollView)
.setPositiveButton(R.string.close) { dialog, _ ->
dialog.dismiss()
}
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
}}
return scrollView
}
}

View File

@ -5,51 +5,41 @@
package pl.szczodrzynski.edziennik.ui.dialogs
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding
import kotlin.coroutines.CoroutineContext
import pl.szczodrzynski.edziennik.ui.dialogs.base.BaseDialog
class ProfileRemoveDialog(
val activity: MainActivity,
val profileId: Int,
val profileName: String,
val noProfileRemoval: Boolean = false,
val onRemove: (() -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "ProfileRemoveDialog"
}
activity: AppCompatActivity,
val profileId: Int,
val profileName: String,
val noProfileRemoval: Boolean = false,
val onRemove: (() -> Unit)? = null,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BaseDialog(activity, onShowListener, onDismissListener) {
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override val TAG = "ProfileRemoveDialog"
private val app by lazy { activity.application as App }
private lateinit var b: DialogLessonDetailsBinding
private lateinit var dialog: AlertDialog
override fun getTitleRes() = R.string.profile_menu_remove_confirm
override fun getMessageFormat() =
R.string.profile_menu_remove_confirm_text_format to listOf(
profileName,
profileName
)
init { run {
job = Job()
override fun isCancelable() = false
override fun getPositiveButtonText() = R.string.remove
override fun getNeutralButtonText() = R.string.cancel
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.profile_menu_remove_confirm)
.setMessage(activity.getString(R.string.profile_menu_remove_confirm_text_format, profileName, profileName))
.setPositiveButton(R.string.remove) { _, _ ->
removeProfile()
}
.setNeutralButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
.setCancelable(false)
.show()
}}
override suspend fun onShow() = Unit
private fun removeProfile() { launch {
val deferred = async(Dispatchers.Default) {
val profileObject = app.db.profileDao().getByIdNow(profileId) ?: return@async
override suspend fun onPositiveClick(): Boolean {
withContext(Dispatchers.Default) {
val profileObject = app.db.profileDao().getByIdNow(profileId) ?: return@withContext
app.db.announcementDao().clear(profileId)
app.db.attendanceDao().clear(profileId)
app.db.attendanceTypeDao().clear(profileId)
@ -77,12 +67,13 @@ class ProfileRemoveDialog(
app.db.metadataDao().deleteAll(profileId)
if (noProfileRemoval)
return@async
return@withContext
app.db.configDao().clear(profileId)
val loginStoreId = profileObject.loginStoreId
val profilesUsingLoginStore = app.db.profileDao().getIdsByLoginStoreIdNow(loginStoreId)
val profilesUsingLoginStore =
app.db.profileDao().getIdsByLoginStoreIdNow(loginStoreId)
if (profilesUsingLoginStore.size == 1) {
app.db.loginStoreDao().remove(loginStoreId)
}
@ -92,10 +83,13 @@ class ProfileRemoveDialog(
app.profileLoadLast { }
}
}
deferred.await()
dialog.dismiss()
activity.reloadTarget()
if (activity is MainActivity)
activity.reloadTarget()
Toast.makeText(activity, R.string.dialog_profile_remove_success, Toast.LENGTH_LONG).show()
onRemove?.invoke()
}}
return DISMISS
}
}

View File

@ -4,59 +4,36 @@
package pl.szczodrzynski.edziennik.ui.dialogs
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import me.dm7.barcodescanner.zxing.ZXingScannerView
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.ext.dp
import kotlin.coroutines.CoroutineContext
import pl.szczodrzynski.edziennik.ui.dialogs.base.ViewDialog
class QrScannerDialog(
val activity: AppCompatActivity,
val onCodeScanned: (text: String) -> Unit,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "QrScannerDialog"
activity: AppCompatActivity,
val onCodeScanned: (text: String) -> Unit,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : ViewDialog<ZXingScannerView>(activity, onShowListener, onDismissListener) {
override val TAG = "QrScannerDialog"
override fun getTitleRes() = R.string.qr_scanner_dialog_title
override fun getPositiveButtonText() = R.string.close
override fun getRootView(): ZXingScannerView {
val scannerView = ZXingScannerView(activity)
scannerView.setPadding(0, 16.dp, 2.dp, 0)
return scannerView
}
private lateinit var app: App
private lateinit var scannerView: ZXingScannerView
private lateinit var dialog: AlertDialog
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
scannerView = ZXingScannerView(activity)
scannerView.setPadding(0, 16.dp, 2.dp, 0)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.qr_scanner_dialog_title)
.setView(scannerView)
.setPositiveButton(R.string.close) { dialog, _ ->
dialog.dismiss()
}
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
scannerView.setResultHandler {
scannerView.stopCamera()
override suspend fun onShow() {
root.setResultHandler {
root.stopCamera()
dialog.dismiss()
onCodeScanned(it.text)
}
scannerView.startCamera()
}}
root.startCamera()
}
}

View File

@ -7,59 +7,45 @@ package pl.szczodrzynski.edziennik.ui.dialogs
import android.content.res.ColorStateList
import android.text.Editable
import android.text.SpannableStringBuilder
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.StyledTextDialogBinding
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.utils.DefaultTextStyles
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.HtmlMode.SIMPLE
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.StylingConfig
class StyledTextDialog(
val activity: AppCompatActivity,
activity: AppCompatActivity,
val initialText: Editable?,
val onSuccess: (text: Editable) -> Unit,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) {
companion object {
private const val TAG = "StyledTextDialog"
}
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<StyledTextDialogBinding>(activity, onShowListener, onDismissListener) {
override val TAG = "StyledTextDialog"
private lateinit var app: App
private lateinit var b: StyledTextDialogBinding
private lateinit var dialog: AlertDialog
private lateinit var config: StylingConfig
private val manager
get() = app.textStylingManager
init {
show()
override fun getTitleRes() = R.string.styled_text_dialog_title
override fun inflate(layoutInflater: LayoutInflater) =
StyledTextDialogBinding.inflate(layoutInflater)
override fun getPositiveButtonText() = R.string.save
override fun getNeutralButtonText() = R.string.cancel
override suspend fun onPositiveClick(): Boolean {
onSuccess(b.editText.text ?: SpannableStringBuilder(""))
return DISMISS
}
fun show() {
if (activity.isFinishing)
return
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
b = StyledTextDialogBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.styled_text_dialog_title)
.setView(b.root)
.setPositiveButton(R.string.save) { _, _ ->
onSuccess(b.editText.text ?: SpannableStringBuilder(""))
}
.setNeutralButton(R.string.cancel, null)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
override suspend fun onShow() {
config = StylingConfig(
editText = b.editText,
fontStyleGroup = b.fontStyle.styles,

View File

@ -0,0 +1,199 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-18.
*/
package pl.szczodrzynski.edziennik.ui.dialogs.base
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AlertDialog.*
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.ext.onClick
import pl.szczodrzynski.edziennik.ext.setMessage
import kotlin.coroutines.CoroutineContext
abstract class BaseDialog(
protected val activity: AppCompatActivity,
protected val onShowListener: ((tag: String) -> Unit)? = null,
protected val onDismissListener: ((tag: String) -> Unit)? = null,
) : CoroutineScope {
companion object {
const val DISMISS = true
const val NO_DISMISS = false
}
@Suppress("PropertyName")
abstract val TAG: String
protected lateinit var app: App
protected lateinit var dialog: AlertDialog
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private var items = emptyList<Any>()
private var itemSelected: Any? = null
private var itemStates = BooleanArray(0)
protected open fun getTitle(): CharSequence? = null
protected abstract fun getTitleRes(): Int?
protected open fun getMessage(): CharSequence? = null
protected open fun getMessageRes(): Int? = null
protected open fun getMessageFormat(): Pair<Int, List<CharSequence>>? = null
protected open fun getView(): View? = null
open fun isCancelable() = true
open fun getPositiveButtonText(): Int? = null
open fun getNeutralButtonText(): Int? = null
open fun getNegativeButtonText(): Int? = null
protected open fun getSingleChoiceItems(): Map<CharSequence, Any>? = null
protected open fun getMultiChoiceItems(): Map<CharSequence, Any>? = null
protected open fun getDefaultSelectedItem(): Any? = null
protected open fun getDefaultSelectedItems(): Set<Any> = emptySet()
open suspend fun onPositiveClick() = true
open suspend fun onNeutralClick() = true
open suspend fun onNegativeClick() = true
open suspend fun onSingleSelectionChanged(item: Any?) = Unit
open suspend fun onMultiSelectionChanged(items: Set<Any>) = Unit
protected open suspend fun onBeforeShow() = true
protected abstract suspend fun onShow()
protected open fun onDismiss() = Unit
fun show() {
if (activity.isFinishing)
return
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
dialog = MaterialAlertDialogBuilder(activity)
.also(this::configure)
.setCancelable(isCancelable())
.setOnDismissListener {
onDismiss()
onDismissListener?.invoke(TAG)
}
.create()
reload()
}
protected fun reload() {
launch {
if (activity.isFinishing)
return@launch
if (!onBeforeShow()) {
dialog.dismiss()
return@launch
}
dialog.show()
setButtons()
onShow()
}
}
private fun configure(md: MaterialAlertDialogBuilder) {
getTitle()?.let {
md.setTitle(it)
}
getTitleRes()?.let {
md.setTitle(it)
}
getPositiveButtonText()?.let {
md.setPositiveButton(it, null)
}
getNeutralButtonText()?.let {
md.setNeutralButton(it, null)
}
getNegativeButtonText()?.let {
md.setNegativeButton(it, null)
}
getMessage()?.let {
md.setMessage(it)
}
getMessageRes()?.let {
md.setMessage(it)
}
getMessageFormat()?.let { (stringId, formatArgs) ->
md.setMessage(stringId, *formatArgs.toTypedArray())
}
getView()?.let {
md.setView(it)
}
getSingleChoiceItems()?.let { map ->
val default = getDefaultSelectedItem()
val defaultIndex = map.values.indexOf(default)
md.setSingleChoiceItems(map.keys.toTypedArray(), defaultIndex) { _, which ->
launch {
itemSelected = items[which]
onSingleSelectionChanged(getSingleSelection())
}
}
items = map.values.toList()
itemSelected = default
md.setMessage(null)
}
getMultiChoiceItems()?.let { map ->
val default = getDefaultSelectedItems()
val defaultStates = map.values.map {
it in default
}.toBooleanArray()
md.setMultiChoiceItems(map.keys.toTypedArray(),
defaultStates) { _, position, isChecked ->
launch {
itemStates[position] = isChecked
onMultiSelectionChanged(getMultiSelection())
}
}
items = map.values.toList()
itemStates = defaultStates
md.setMessage(null)
}
}
private fun setButtons() {
dialog.getButton(BUTTON_POSITIVE)?.onClick {
launch {
if (onPositiveClick())
dialog.dismiss()
}
}
dialog.getButton(BUTTON_NEUTRAL)?.onClick {
launch {
if (onNeutralClick())
dialog.dismiss()
}
}
dialog.getButton(BUTTON_NEGATIVE)?.onClick {
launch {
if (onNegativeClick())
dialog.dismiss()
}
}
}
protected fun getSingleSelection() = itemSelected
protected fun getMultiSelection(): Set<Any> {
return itemStates.mapIndexed { position, isChecked ->
if (isChecked)
items[position]
else
null
}.filterNotNull().toSet()
}
protected fun dismiss() {
dialog.dismiss()
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-18.
*/
package pl.szczodrzynski.edziennik.ui.dialogs.base
import android.view.LayoutInflater
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding
abstract class BindingDialog<B : ViewBinding>(
activity: AppCompatActivity,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : ViewDialog<View>(activity, onShowListener, onDismissListener) {
protected lateinit var b: B
protected abstract fun inflate(layoutInflater: LayoutInflater): B
final override fun getRootView(): View {
b = inflate(activity.layoutInflater)
return b.root
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-18.
*/
package pl.szczodrzynski.edziennik.ui.dialogs.base
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
abstract class ConfigDialog<B : ViewBinding>(
activity: AppCompatActivity,
private val reloadOnDismiss: Boolean = true,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<B>(activity, onShowListener, onDismissListener) {
final override fun getPositiveButtonText() = R.string.ok
final override suspend fun onShow() = Unit
protected val config by lazy { app.config.grades }
protected open suspend fun loadConfig() = Unit
protected open suspend fun saveConfig() = Unit
protected open fun initView() = Unit
final override suspend fun onBeforeShow(): Boolean {
initView()
loadConfig()
return true
}
final override fun onDismiss() {
launch {
saveConfig()
}
if (reloadOnDismiss && activity is MainActivity)
activity.reloadTarget()
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-18.
*/
package pl.szczodrzynski.edziennik.ui.dialogs.base
import android.view.View
import androidx.appcompat.app.AppCompatActivity
abstract class ViewDialog<V : View>(
activity: AppCompatActivity,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null
) : BaseDialog(activity, onShowListener, onDismissListener) {
protected lateinit var root: V
protected abstract fun getRootView(): V
final override fun getMessage() = null
final override fun getMessageFormat() = null
final override fun getView(): View {
root = getRootView()
return root
}
}

View File

@ -4,7 +4,7 @@
package pl.szczodrzynski.edziennik.ui.dialogs.settings
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import pl.szczodrzynski.edziennik.*
@ -12,57 +12,48 @@ import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED
import pl.szczodrzynski.edziennik.databinding.DialogConfigAgendaBinding
import pl.szczodrzynski.edziennik.ext.onChange
import pl.szczodrzynski.edziennik.ui.dialogs.base.ConfigDialog
import java.util.*
class AgendaConfigDialog(
private val activity: AppCompatActivity,
private val reloadOnDismiss: Boolean = true,
private val onShowListener: ((tag: String) -> Unit)? = null,
private val onDismissListener: ((tag: String) -> Unit)? = null
activity: AppCompatActivity,
reloadOnDismiss: Boolean = true,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : ConfigDialog<DialogConfigAgendaBinding>(
activity,
reloadOnDismiss,
onShowListener,
onDismissListener,
) {
companion object {
const val TAG = "AgendaConfigDialog"
}
private val app by lazy { activity.application as App }
private val config by lazy { app.config.ui }
override val TAG = "AgendaConfigDialog"
override fun getTitleRes() = R.string.menu_agenda_config
override fun inflate(layoutInflater: LayoutInflater) =
DialogConfigAgendaBinding.inflate(layoutInflater)
private val profileConfig by lazy { app.config.forProfile().ui }
private lateinit var b: DialogConfigAgendaBinding
private lateinit var dialog: AlertDialog
init { run {
if (activity.isFinishing)
return@run
b = DialogConfigAgendaBinding.inflate(activity.layoutInflater)
onShowListener?.invoke(TAG)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.menu_agenda_config)
.setView(b.root)
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener {
saveConfig()
onDismissListener?.invoke(TAG)
if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget()
}
.create()
loadConfig()
dialog.show()
}}
private fun loadConfig() {
override suspend fun loadConfig() {
b.config = profileConfig
b.isAgendaMode = profileConfig.agendaViewType == Profile.AGENDA_DEFAULT
b.eventSharingEnabled.isChecked = app.profile.enableSharedEvents
&& app.profile.registration == REGISTRATION_ENABLED
b.eventSharingEnabled.isChecked =
app.profile.enableSharedEvents && app.profile.registration == REGISTRATION_ENABLED
b.eventSharingEnabled.onChange { _, isChecked ->
if (isChecked && app.profile.registration != REGISTRATION_ENABLED) {
b.eventSharingEnabled.isChecked = false
val dialog = RegistrationConfigDialog(activity, app.profile, onChangeListener = { enabled ->
b.eventSharingEnabled.isChecked = enabled
setEventSharingEnabled(enabled)
}, onShowListener, onDismissListener)
val dialog = RegistrationConfigDialog(
activity,
app.profile,
onChangeListener = { enabled ->
b.eventSharingEnabled.isChecked = enabled
setEventSharingEnabled(enabled)
},
onShowListener,
onDismissListener,
)
dialog.showEnableDialog()
return@onChange
}
@ -86,8 +77,4 @@ class AgendaConfigDialog(
.setPositiveButton(R.string.ok, null)
.show()
}
private fun saveConfig() {
}
}

View File

@ -4,68 +4,40 @@
package pl.szczodrzynski.edziennik.ui.dialogs.settings
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.*
import kotlin.coroutines.CoroutineContext
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.ui.dialogs.base.BaseDialog
class AppLanguageDialog(
val activity: AppCompatActivity,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "AppLanguageDialog"
activity: AppCompatActivity,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BaseDialog(activity, onShowListener, onDismissListener) {
override val TAG = "AppLanguageDialog"
override fun getTitleRes() = R.string.app_language_dialog_title
override fun getMessage() = activity.getString(R.string.app_language_dialog_text)
override fun getPositiveButtonText() = R.string.ok
override fun getNegativeButtonText() = R.string.cancel
override fun getSingleChoiceItems(): Map<CharSequence, Any> = mapOf(
R.string.language_system to "",
R.string.language_polish to "pl",
R.string.language_english to "en",
R.string.language_german to "de",
).mapKeys { (resId, _) -> activity.getString(resId) }
override fun getDefaultSelectedItem() = app.config.ui.language
override suspend fun onShow() = Unit
override suspend fun onPositiveClick(): Boolean {
val language = getSingleSelection() as? String ?: return DISMISS
if (language.isEmpty())
app.config.ui.language = null
else
app.config.ui.language = language
activity.recreate()
return NO_DISMISS
}
private lateinit var app: App
private lateinit var dialog: AlertDialog
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
// local variables go here
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
val languages = mapOf(
null to R.string.language_system,
"pl" to R.string.language_polish,
"en" to R.string.language_english,
"de" to R.string.language_german
)
val languageIds = languages.map { it.key }
val languageNames = languages.map {
activity.getString(it.value)
}
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.app_language_dialog_title)
//.setMessage(R.string.settings_about_language_dialog_text)
.setSingleChoiceItems(
languageNames.toTypedArray(),
languageIds.indexOf(app.config.ui.language),
null
)
.setPositiveButton(R.string.ok) { _, _ ->
val which = dialog.listView.checkedItemPosition
app.config.ui.language = languageIds[which]
activity.recreate()
}
.setNegativeButton(R.string.cancel, null)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
}}
}

View File

@ -4,64 +4,40 @@
package pl.szczodrzynski.edziennik.ui.dialogs.settings
import android.annotation.SuppressLint
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.AttendanceConfigDialogBinding
import pl.szczodrzynski.edziennik.ext.onChange
import pl.szczodrzynski.edziennik.ui.dialogs.base.ConfigDialog
class AttendanceConfigDialog(
val activity: AppCompatActivity,
private val reloadOnDismiss: Boolean = true,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
activity: AppCompatActivity,
reloadOnDismiss: Boolean = true,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : ConfigDialog<AttendanceConfigDialogBinding>(
activity,
reloadOnDismiss,
onShowListener,
onDismissListener,
) {
companion object {
const val TAG = "GradesConfigDialog"
}
private val app by lazy { activity.application as App }
override val TAG = "AttendanceConfigDialog"
override fun getTitleRes() = R.string.menu_attendance_config
override fun inflate(layoutInflater: LayoutInflater) =
AttendanceConfigDialogBinding.inflate(layoutInflater)
private val profileConfig by lazy { app.config.getFor(app.profileId).attendance }
private lateinit var b: AttendanceConfigDialogBinding
private lateinit var dialog: AlertDialog
init { run {
if (activity.isFinishing)
return@run
b = AttendanceConfigDialogBinding.inflate(activity.layoutInflater)
onShowListener?.invoke(TAG)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.menu_attendance_config)
.setView(b.root)
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener {
saveConfig()
onDismissListener?.invoke(TAG)
if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget()
}
.create()
initView()
loadConfig()
dialog.show()
}}
@SuppressLint("SetTextI18n")
private fun loadConfig() {
override suspend fun loadConfig() {
b.useSymbols.isChecked = profileConfig.useSymbols
b.groupConsecutiveDays.isChecked = profileConfig.groupConsecutiveDays
b.showPresenceInMonth.isChecked = profileConfig.showPresenceInMonth
}
private fun saveConfig() {
// nothing to do here, yet
}
private fun initView() {
override fun initView() {
b.useSymbols.onChange { _, isChecked ->
profileConfig.useSymbols = isChecked
}

View File

@ -4,40 +4,31 @@
package pl.szczodrzynski.edziennik.ui.dialogs.settings
import android.widget.TextView
import android.view.LayoutInflater
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.addTextChangedListener
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.ext.onClick
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.DialogEditTextBinding
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.utils.models.Time
import kotlin.coroutines.CoroutineContext
class BellSyncConfigDialog(
val activity: AppCompatActivity,
val onChangeListener: (() -> Unit)? = null,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "BellSyncConfigDialog"
}
activity: AppCompatActivity,
private val onChangeListener: (() -> Unit)? = null,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<DialogEditTextBinding>(activity, onShowListener, onDismissListener) {
private lateinit var app: App
private lateinit var dialog: AlertDialog
override val TAG = "BellSyncConfigDialog"
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun getTitleRes() = R.string.bell_sync_title
override fun inflate(layoutInflater: LayoutInflater) =
DialogEditTextBinding.inflate(layoutInflater)
// local variables go here
override fun getPositiveButtonText() = R.string.ok
override fun getNeutralButtonText() = R.string.reset
override fun getNegativeButtonText() = R.string.cancel
private fun parse(input: String): Pair<Time, Int>? {
if (input.length < 8) {
@ -56,63 +47,46 @@ class BellSyncConfigDialog(
return time to multiplier
}
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.bell_sync_title)
.setView(R.layout.dialog_edit_text)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.setNeutralButton(R.string.reset) { _, _ ->
app.config.timetable.bellSyncDiff = null
app.config.timetable.bellSyncMultiplier = 0
onChangeListener?.invoke()
}
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
val message = dialog.findViewById<TextView>(android.R.id.title)
val editText = dialog.findViewById<TextInputEditText>(android.R.id.text1)
val textLayout = dialog.findViewById<TextInputLayout>(R.id.text_input_layout)
message?.setText(R.string.bell_sync_adjust_content)
editText?.hint = "±H:MM:SS"
editText?.setText(app.config.timetable.bellSyncDiff?.let {
override suspend fun onShow() {
b.title.setText(R.string.bell_sync_adjust_content)
b.text1.hint = "±H:MM:SS"
b.text1.setText(app.config.timetable.bellSyncDiff?.let {
(if (app.config.timetable.bellSyncMultiplier == -1) "-" else "+") + it.stringHMS
} ?: "+0:00:00")
editText?.addTextChangedListener { text ->
b.text1.addTextChangedListener { text ->
val input = text?.toString()
textLayout?.error =
b.textInputLayout.error =
if (input != null && parse(input) == null)
activity.getString(R.string.bell_sync_adjust_error)
else
null
}
}
dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.onClick {
val input = editText?.text?.toString() ?: return@onClick
val parsed = parse(input)
if (parsed == null) {
Toast.makeText(activity, R.string.bell_sync_adjust_error, Toast.LENGTH_SHORT).show()
return@onClick
}
val (time, multiplier) = parsed
app.config.timetable.bellSyncDiff =
if (time.value == 0)
null
else
time
app.config.timetable.bellSyncMultiplier = multiplier
onChangeListener?.invoke()
dialog.dismiss()
override suspend fun onPositiveClick(): Boolean {
val input = b.text1.text?.toString() ?: return NO_DISMISS
val parsed = parse(input)
if (parsed == null) {
Toast.makeText(activity, R.string.bell_sync_adjust_error, Toast.LENGTH_SHORT).show()
return NO_DISMISS
}
}}
val (time, multiplier) = parsed
app.config.timetable.bellSyncDiff =
if (time.value == 0)
null
else
time
app.config.timetable.bellSyncMultiplier = multiplier
onChangeListener?.invoke()
return DISMISS
}
override suspend fun onNeutralClick(): Boolean {
app.config.timetable.bellSyncDiff = null
app.config.timetable.bellSyncMultiplier = 0
onChangeListener?.invoke()
return DISMISS
}
}

View File

@ -5,17 +5,18 @@
package pl.szczodrzynski.edziennik.ui.dialogs.settings
import android.annotation.SuppressLint
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import it.sephiroth.android.library.numberpicker.doOnStopTrackingTouch
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.DialogConfigGradesBinding
import pl.szczodrzynski.edziennik.ext.join
import pl.szczodrzynski.edziennik.ext.onChange
import pl.szczodrzynski.edziennik.ext.onClick
import pl.szczodrzynski.edziennik.ext.setOnSelectedListener
import pl.szczodrzynski.edziennik.ui.dialogs.base.ConfigDialog
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_DEFAULT
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_DATE_DESC
@ -25,47 +26,29 @@ import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_AVG
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_SEM
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
import java.util.*
class GradesConfigDialog(
val activity: AppCompatActivity,
private val reloadOnDismiss: Boolean = true,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
activity: AppCompatActivity,
reloadOnDismiss: Boolean = true,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : ConfigDialog<DialogConfigGradesBinding>(
activity,
reloadOnDismiss,
onShowListener,
onDismissListener,
) {
companion object {
const val TAG = "GradesConfigDialog"
}
private val app by lazy { activity.application as App }
private val config by lazy { app.config.grades }
override val TAG = "GradesConfigDialog"
override fun getTitleRes() = R.string.menu_grades_config
override fun inflate(layoutInflater: LayoutInflater) =
DialogConfigGradesBinding.inflate(layoutInflater)
private val profileConfig by lazy { app.config.getFor(app.profileId).grades }
private lateinit var b: DialogConfigGradesBinding
private lateinit var dialog: AlertDialog
init { run {
if (activity.isFinishing)
return@run
b = DialogConfigGradesBinding.inflate(activity.layoutInflater)
onShowListener?.invoke(TAG)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.menu_grades_config)
.setView(b.root)
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener {
saveConfig()
onDismissListener?.invoke(TAG)
if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget()
}
.create()
initView()
loadConfig()
dialog.show()
}}
@SuppressLint("SetTextI18n")
private fun loadConfig() {
override suspend fun loadConfig() {
b.customPlusCheckBox.isChecked = profileConfig.plusValue != null
b.customPlusValue.isVisible = b.customPlusCheckBox.isChecked
b.customMinusCheckBox.isChecked = profileConfig.minusValue != null
@ -95,37 +78,39 @@ class GradesConfigDialog(
else -> null
}?.isChecked = true
b.dontCountGrades.isChecked = profileConfig.dontCountEnabled && profileConfig.dontCountGrades.isNotEmpty()
b.dontCountGrades.isChecked =
profileConfig.dontCountEnabled && profileConfig.dontCountGrades.isNotEmpty()
b.hideImproved.isChecked = profileConfig.hideImproved
b.averageWithoutWeight.isChecked = profileConfig.averageWithoutWeight
if (profileConfig.dontCountGrades.isEmpty()) {
b.dontCountGradesText.setText("nb, 0, bz, bd")
}
else {
} else {
b.dontCountGradesText.setText(profileConfig.dontCountGrades.join(", "))
}
}
private fun saveConfig() {
profileConfig.plusValue = if (b.customPlusCheckBox.isChecked) b.customPlusValue.progress else null
profileConfig.minusValue = if (b.customMinusCheckBox.isChecked) b.customMinusValue.progress else null
override suspend fun saveConfig() {
profileConfig.plusValue =
if (b.customPlusCheckBox.isChecked) b.customPlusValue.progress else null
profileConfig.minusValue =
if (b.customMinusCheckBox.isChecked) b.customMinusValue.progress else null
b.dontCountGradesText.setText(
b.dontCountGradesText
.text
?.toString()
?.lowercase()
?.replace(", ", ",")
b.dontCountGradesText
.text
?.toString()
?.lowercase()
?.replace(", ", ",")
)
profileConfig.dontCountEnabled = b.dontCountGrades.isChecked
profileConfig.dontCountGrades = b.dontCountGradesText.text
?.split(",")
?.map { it.trim() }
?: listOf()
?.split(",")
?.map { it.trim() }
?: listOf()
}
private fun initView() {
override fun initView() {
b.customPlusCheckBox.onChange { _, isChecked ->
b.customPlusValue.isVisible = isChecked
}
@ -145,24 +130,38 @@ class GradesConfigDialog(
b.sortGradesByDateRadio.setOnSelectedListener { config.orderBy = ORDER_BY_DATE_DESC }
b.sortGradesBySubjectRadio.setOnSelectedListener { config.orderBy = ORDER_BY_SUBJECT_ASC }
b.gradeColorFromERegister.setOnSelectedListener { profileConfig.colorMode = COLOR_MODE_DEFAULT }
b.gradeColorFromERegister.setOnSelectedListener {
profileConfig.colorMode = COLOR_MODE_DEFAULT
}
b.gradeColorByValue.setOnSelectedListener { profileConfig.colorMode = COLOR_MODE_WEIGHTED }
b.gradeAverageMode4.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_ALL_GRADES }
b.gradeAverageMode0.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_AVG_2_AVG }
b.gradeAverageMode1.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_SEM_2_AVG }
b.gradeAverageMode2.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_AVG_2_SEM }
b.gradeAverageMode3.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_SEM_2_SEM }
b.gradeAverageMode4.setOnSelectedListener {
profileConfig.yearAverageMode = YEAR_ALL_GRADES
}
b.gradeAverageMode0.setOnSelectedListener {
profileConfig.yearAverageMode = YEAR_1_AVG_2_AVG
}
b.gradeAverageMode1.setOnSelectedListener {
profileConfig.yearAverageMode = YEAR_1_SEM_2_AVG
}
b.gradeAverageMode2.setOnSelectedListener {
profileConfig.yearAverageMode = YEAR_1_AVG_2_SEM
}
b.gradeAverageMode3.setOnSelectedListener {
profileConfig.yearAverageMode = YEAR_1_SEM_2_SEM
}
b.hideImproved.onChange { _, isChecked -> profileConfig.hideImproved = isChecked }
b.averageWithoutWeight.onChange { _, isChecked -> profileConfig.averageWithoutWeight = isChecked }
b.averageWithoutWeight.onChange { _, isChecked ->
profileConfig.averageWithoutWeight = isChecked
}
b.averageWithoutWeightHelp.onClick {
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.grades_config_average_without_weight)
.setMessage(R.string.grades_config_average_without_weight_message)
.setPositiveButton(R.string.ok, null)
.show()
.setTitle(R.string.grades_config_average_without_weight)
.setMessage(R.string.grades_config_average_without_weight_message)
.setPositiveButton(R.string.ok, null)
.show()
}
}
}

View File

@ -4,59 +4,42 @@
package pl.szczodrzynski.edziennik.ui.dialogs.settings
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.MessagesConfigDialogBinding
import pl.szczodrzynski.edziennik.ui.dialogs.base.ConfigDialog
class MessagesConfigDialog(
private val activity: AppCompatActivity,
private val reloadOnDismiss: Boolean = true,
private val onShowListener: ((tag: String) -> Unit)? = null,
private val onDismissListener: ((tag: String) -> Unit)? = null
activity: AppCompatActivity,
reloadOnDismiss: Boolean = true,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : ConfigDialog<MessagesConfigDialogBinding>(
activity,
reloadOnDismiss,
onShowListener,
onDismissListener,
) {
companion object {
const val TAG = "MessagesConfigDialog"
}
private val app by lazy { activity.application as App }
private val config by lazy { app.config.ui }
private val profileConfig by lazy { app.config.forProfile().ui }
override val TAG = "MessagesConfigDialog"
private lateinit var b: MessagesConfigDialogBinding
private lateinit var dialog: AlertDialog
override fun getTitleRes() = R.string.menu_messages_config
override fun inflate(layoutInflater: LayoutInflater) =
MessagesConfigDialogBinding.inflate(layoutInflater)
init { run {
if (activity.isFinishing)
return@run
b = MessagesConfigDialogBinding.inflate(activity.layoutInflater)
onShowListener?.invoke(TAG)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.menu_messages_config)
.setView(b.root)
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener {
saveConfig()
onDismissListener?.invoke(TAG)
if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget()
}
.create()
loadConfig()
dialog.show()
}}
private val profileConfig by lazy { app.config.getFor(app.profileId).ui }
private fun loadConfig() {
override suspend fun loadConfig() {
b.config = profileConfig
b.greetingText.setText(
profileConfig.messagesGreetingText ?: "\n\nZ poważaniem\n${app.profile.accountOwnerName}"
profileConfig.messagesGreetingText
?: "\n\nZ poważaniem\n${app.profile.accountOwnerName}"
)
}
private fun saveConfig() {
override suspend fun saveConfig() {
val greetingText = b.greetingText.text?.toString()?.trim()
if (greetingText.isNullOrEmpty())
profileConfig.messagesGreetingText = null

View File

@ -4,13 +4,7 @@
package pl.szczodrzynski.edziennik.ui.dialogs.settings
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ANNOUNCEMENTS
@ -24,73 +18,45 @@ import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_NOTIFICATIO
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_SETTINGS
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
import pl.szczodrzynski.edziennik.R
import kotlin.coroutines.CoroutineContext
import pl.szczodrzynski.edziennik.ui.dialogs.base.BaseDialog
class MiniMenuConfigDialog(
val activity: AppCompatActivity,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "MiniMenuConfigDialog"
activity: AppCompatActivity,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BaseDialog(activity, onShowListener, onDismissListener) {
override val TAG = "BellSyncTimeChooseDialog"
override fun getTitleRes() = R.string.settings_theme_mini_drawer_buttons_dialog_title
override fun getMessageRes() = R.string.settings_theme_mini_drawer_buttons_dialog_text
override fun getPositiveButtonText() = R.string.ok
override fun getNegativeButtonText() = R.string.cancel
override fun getMultiChoiceItems(): Map<CharSequence, Any> = mapOf(
R.string.menu_home_page to DRAWER_ITEM_HOME,
R.string.menu_timetable to DRAWER_ITEM_TIMETABLE,
R.string.menu_agenda to DRAWER_ITEM_AGENDA,
R.string.menu_grades to DRAWER_ITEM_GRADES,
R.string.menu_messages to DRAWER_ITEM_MESSAGES,
R.string.menu_homework to DRAWER_ITEM_HOMEWORK,
R.string.menu_notices to DRAWER_ITEM_BEHAVIOUR,
R.string.menu_attendance to DRAWER_ITEM_ATTENDANCE,
R.string.menu_announcements to DRAWER_ITEM_ANNOUNCEMENTS,
R.string.menu_notifications to DRAWER_ITEM_NOTIFICATIONS,
R.string.menu_settings to DRAWER_ITEM_SETTINGS,
).mapKeys { (resId, _) -> activity.getString(resId) }
override fun getDefaultSelectedItems() = app.config.ui.miniMenuButtons.toSet()
override suspend fun onShow() = Unit
override suspend fun onPositiveClick(): Boolean {
app.config.ui.miniMenuButtons = getMultiSelection().filterIsInstance<Int>()
if (activity is MainActivity) {
activity.setDrawerItems()
activity.drawer.updateBadges()
}
return DISMISS
}
private lateinit var app: App
private lateinit var dialog: AlertDialog
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
// local variables go here
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
val buttons = mapOf(
DRAWER_ITEM_HOME to R.string.menu_home_page,
DRAWER_ITEM_TIMETABLE to R.string.menu_timetable,
DRAWER_ITEM_AGENDA to R.string.menu_agenda,
DRAWER_ITEM_GRADES to R.string.menu_grades,
DRAWER_ITEM_MESSAGES to R.string.menu_messages,
DRAWER_ITEM_HOMEWORK to R.string.menu_homework,
DRAWER_ITEM_BEHAVIOUR to R.string.menu_notices,
DRAWER_ITEM_ATTENDANCE to R.string.menu_attendance,
DRAWER_ITEM_ANNOUNCEMENTS to R.string.menu_announcements,
DRAWER_ITEM_NOTIFICATIONS to R.string.menu_notifications,
DRAWER_ITEM_SETTINGS to R.string.menu_settings
)
val miniMenuButtons = app.config.ui.miniMenuButtons
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.settings_theme_mini_drawer_buttons_dialog_title)
//.setMessage(R.string.settings_theme_mini_drawer_buttons_dialog_text)
.setMultiChoiceItems(
buttons.map { activity.getString(it.value) }.toTypedArray(),
buttons.map { it.key in miniMenuButtons }.toBooleanArray(),
null
)
.setPositiveButton(R.string.ok) { _, _ ->
app.config.ui.miniMenuButtons =
buttons.keys.mapIndexedNotNull { index, id ->
if (dialog.listView.checkedItemPositions[index])
id
else
null
}
if (activity is MainActivity) {
activity.setDrawerItems()
activity.drawer.updateBadges()
}
}
.setNegativeButton(R.string.cancel, null)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
}}
}

View File

@ -4,96 +4,71 @@
package pl.szczodrzynski.edziennik.ui.dialogs.settings
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.ext.onClick
import kotlin.coroutines.CoroutineContext
import pl.szczodrzynski.edziennik.ui.dialogs.base.BaseDialog
// TODO refactor dialog to allow configuring other profiles
// than the selected one in UI
class NotificationFilterDialog(
val activity: AppCompatActivity,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "NotificationFilterDialog"
private val notificationTypes = listOf(
Notification.TYPE_TIMETABLE_LESSON_CHANGE to R.string.notification_type_timetable_lesson_change,
Notification.TYPE_NEW_GRADE to R.string.notification_type_new_grade,
Notification.TYPE_NEW_EVENT to R.string.notification_type_new_event,
Notification.TYPE_NEW_HOMEWORK to R.string.notification_type_new_homework,
Notification.TYPE_NEW_MESSAGE to R.string.notification_type_new_message,
Notification.TYPE_LUCKY_NUMBER to R.string.notification_type_lucky_number,
Notification.TYPE_NEW_NOTICE to R.string.notification_type_notice,
Notification.TYPE_NEW_ATTENDANCE to R.string.notification_type_attendance,
Notification.TYPE_NEW_ANNOUNCEMENT to R.string.notification_type_new_announcement,
Notification.TYPE_NEW_SHARED_EVENT to R.string.notification_type_new_shared_event,
Notification.TYPE_NEW_SHARED_HOMEWORK to R.string.notification_type_new_shared_homework,
Notification.TYPE_REMOVED_SHARED_EVENT to R.string.notification_type_removed_shared_event,
Notification.TYPE_TEACHER_ABSENCE to R.string.notification_type_new_teacher_absence
)
activity: AppCompatActivity,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BaseDialog(activity, onShowListener, onDismissListener) {
override val TAG = "NotificationFilterDialog"
override fun getTitleRes() = R.string.dialog_notification_filter_title
override fun getMessageRes() = R.string.dialog_notification_filter_text
override fun getPositiveButtonText() = R.string.ok
override fun getNegativeButtonText() = R.string.cancel
override fun getMultiChoiceItems(): Map<CharSequence, Any> {
notificationTypes = mapOf(
R.string.notification_type_timetable_lesson_change to Notification.TYPE_TIMETABLE_LESSON_CHANGE,
R.string.notification_type_new_grade to Notification.TYPE_NEW_GRADE,
R.string.notification_type_new_event to Notification.TYPE_NEW_EVENT,
R.string.notification_type_new_homework to Notification.TYPE_NEW_HOMEWORK,
R.string.notification_type_new_message to Notification.TYPE_NEW_MESSAGE,
R.string.notification_type_lucky_number to Notification.TYPE_LUCKY_NUMBER,
R.string.notification_type_notice to Notification.TYPE_NEW_NOTICE,
R.string.notification_type_attendance to Notification.TYPE_NEW_ATTENDANCE,
R.string.notification_type_new_announcement to Notification.TYPE_NEW_ANNOUNCEMENT,
R.string.notification_type_new_shared_event to Notification.TYPE_NEW_SHARED_EVENT,
R.string.notification_type_new_shared_homework to Notification.TYPE_NEW_SHARED_HOMEWORK,
R.string.notification_type_removed_shared_event to Notification.TYPE_REMOVED_SHARED_EVENT,
R.string.notification_type_new_teacher_absence to Notification.TYPE_TEACHER_ABSENCE,
).mapKeys { (resId, _) -> activity.getString(resId) }
return notificationTypes
}
private lateinit var app: App
private lateinit var dialog: AlertDialog
override fun getDefaultSelectedItems() =
notificationTypes.values.subtract(app.config.forProfile().sync.notificationFilter)
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override suspend fun onShow() = Unit
private val notificationFilter = mutableListOf<Int>()
private lateinit var notificationTypes: Map<CharSequence, Int>
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
override suspend fun onPositiveClick(): Boolean {
val enabledTypes = getMultiSelection().filterIsInstance<Int>()
val disabledTypes = notificationTypes.values.subtract(enabledTypes).toList()
notificationFilter.clear()
notificationFilter += app.config.forProfile().sync.notificationFilter
val items = notificationTypes.map { app.getString(it.second) }.toTypedArray()
val checkedItems = notificationTypes.map { !notificationFilter.contains(it.first) }.toBooleanArray()
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.dialog_notification_filter_title)
//.setMessage(R.string.dialog_notification_filter_text)
.setMultiChoiceItems(items, checkedItems) { _, which, isChecked ->
val type = notificationTypes[which].first
notificationFilter.remove(type)
if (!isChecked)
notificationFilter += type
}
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.onClick {
if (notificationFilter.isEmpty()) {
app.config.forProfile().sync.notificationFilter = notificationFilter
dialog.dismiss()
return@onClick
}
if (disabledTypes.isNotEmpty()) {
// warn user when he tries to disable some notifications
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.are_you_sure)
.setMessage(R.string.notification_filter_warning)
.setPositiveButton(R.string.ok) { _, _ ->
app.config.forProfile().sync.notificationFilter = notificationFilter
dialog.dismiss()
}
.setNegativeButton(R.string.cancel, null)
.show()
.setTitle(R.string.are_you_sure)
.setMessage(R.string.notification_filter_warning)
.setPositiveButton(R.string.ok) { _, _ ->
app.config.forProfile().sync.notificationFilter = disabledTypes
dismiss()
}
.setNegativeButton(R.string.cancel, null)
.show()
return NO_DISMISS
}
}}
app.config.forProfile().sync.notificationFilter = disabledTypes
return DISMISS
}
}

View File

@ -5,14 +5,10 @@
package pl.szczodrzynski.edziennik.ui.dialogs.settings
import android.content.res.ColorStateList
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import androidx.core.widget.addTextChangedListener
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.shape.MaterialShapeDrawable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.databinding.DialogProfileConfigBinding
@ -20,54 +16,38 @@ import pl.szczodrzynski.edziennik.ext.dp
import pl.szczodrzynski.edziennik.ext.onChange
import pl.szczodrzynski.edziennik.ext.onClick
import pl.szczodrzynski.edziennik.ui.dialogs.ProfileRemoveDialog
import kotlin.coroutines.CoroutineContext
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
class ProfileConfigDialog(
val activity: MainActivity,
val profile: Profile,
val onProfileSaved: ((profile: Profile) -> Unit)? = null,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "ProfileConfigDialog"
}
activity: MainActivity,
private val profile: Profile,
private val onProfileSaved: ((profile: Profile) -> Unit)? = null,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<DialogProfileConfigBinding>(activity, onShowListener, onDismissListener) {
private lateinit var app: App
private lateinit var b: DialogProfileConfigBinding
private lateinit var dialog: AlertDialog
override val TAG = "ProfileConfigDialog"
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun getTitleRes(): Int? = null
override fun inflate(layoutInflater: LayoutInflater) =
DialogProfileConfigBinding.inflate(layoutInflater)
override fun getPositiveButtonText() = R.string.close
// local variables go here
private var profileChanged = false
private var profileRemoved = false
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
b = DialogProfileConfigBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setView(b.root)
.setPositiveButton(R.string.close, null)
.setOnDismissListener {
if (!profileRemoved && profileChanged) {
app.profileSave(profile)
onProfileSaved?.invoke(profile)
}
onDismissListener?.invoke(TAG)
}
.show()
override suspend fun onShow() {
b.profile = profile
profile.applyImageTo(b.image)
// I can't believe how simple it is to get the dialog's background color !!
val shape = MaterialShapeDrawable(activity, null, R.attr.alertDialogStyle, R.style.MaterialAlertDialog_MaterialComponents)
val shape = MaterialShapeDrawable(
activity,
null,
R.attr.alertDialogStyle,
R.style.MaterialAlertDialog_MaterialComponents
)
val surface = MaterialColors.getColor(activity, R.attr.colorSurface, TAG)
shape.setCornerSize(18.dp.toFloat())
shape.initializeElevationOverlay(activity)
@ -84,6 +64,8 @@ class ProfileConfigDialog(
}
b.imageButton.onClick {
if (activity !is MainActivity)
return@onClick
activity.requestHandler.requestProfileImage(profile) {
val profile = it as? Profile ?: return@requestProfileImage
if (this@ProfileConfigDialog.profile == profile) {
@ -98,7 +80,14 @@ class ProfileConfigDialog(
ProfileRemoveDialog(activity, profile.id, profile.name) {
profileRemoved = true
dialog.dismiss()
}
}.show()
}
}}
}
override fun onDismiss() {
if (!profileRemoved && profileChanged) {
app.profileSave(profile)
onProfileSaved?.invoke(profile)
}
}
}

View File

@ -4,77 +4,47 @@
package pl.szczodrzynski.edziennik.ui.dialogs.settings
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.ext.HOUR
import pl.szczodrzynski.edziennik.ext.MINUTE
import pl.szczodrzynski.edziennik.ext.getSyncInterval
import kotlin.coroutines.CoroutineContext
import pl.szczodrzynski.edziennik.ui.dialogs.base.BaseDialog
class SyncIntervalDialog(
val activity: AppCompatActivity,
val onChangeListener: (() -> Unit)? = null,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "SyncIntervalDialog"
activity: AppCompatActivity,
private val onChangeListener: (() -> Unit)? = null,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BaseDialog(activity, onShowListener, onDismissListener) {
override val TAG = "SyncIntervalDialog"
override fun getTitleRes() = R.string.settings_sync_sync_interval_dialog_title
override fun getMessageRes() = R.string.settings_sync_sync_interval_dialog_text
override fun getPositiveButtonText() = R.string.ok
override fun getNegativeButtonText() = R.string.cancel
override fun getSingleChoiceItems(): Map<CharSequence, Any> = listOf(
30 * MINUTE,
45 * MINUTE,
60 * MINUTE,
90 * MINUTE,
2 * HOUR,
3 * HOUR,
4 * HOUR,
6 * HOUR,
10 * HOUR,
).associateBy { activity.getSyncInterval(it.toInt()) }
override fun getDefaultSelectedItem() = app.config.sync.interval.toLong()
override suspend fun onShow() = Unit
override suspend fun onPositiveClick(): Boolean {
val interval = getSingleSelection() as? Long ?: return DISMISS
app.config.sync.interval = interval.toInt()
onChangeListener?.invoke()
return DISMISS
}
private lateinit var app: App
private lateinit var dialog: AlertDialog
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
// local variables go here
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
val intervals = listOf(
30 * MINUTE,
45 * MINUTE,
60 * MINUTE,
90 * MINUTE,
2 * HOUR,
3 * HOUR,
4 * HOUR,
6 * HOUR,
10 * HOUR
)
val intervalNames = intervals.map {
activity.getSyncInterval(it.toInt())
}
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.settings_sync_sync_interval_dialog_title)
//.setMessage(R.string.settings_sync_sync_interval_dialog_text)
.setSingleChoiceItems(
intervalNames.toTypedArray(),
intervals.indexOf(app.config.sync.interval.toLong()),
null
)
.setPositiveButton(R.string.ok) { _, _ ->
val which = dialog.listView.checkedItemPosition
val interval = intervals[which]
app.config.sync.interval = interval.toInt()
onChangeListener?.invoke()
}
.setNegativeButton(R.string.cancel, null)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
}}
}

View File

@ -4,62 +4,38 @@
package pl.szczodrzynski.edziennik.ui.dialogs.settings
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.ui.dialogs.base.BaseDialog
import pl.szczodrzynski.edziennik.utils.Themes
import kotlin.coroutines.CoroutineContext
class ThemeChooserDialog(
val activity: AppCompatActivity,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "ThemeChooserDialog"
activity: AppCompatActivity,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BaseDialog(activity, onShowListener, onDismissListener) {
override val TAG = "ThemeChooserDialog"
override fun getTitleRes() = R.string.settings_theme_theme_text
override fun getPositiveButtonText() = R.string.ok
override fun getNegativeButtonText() = R.string.cancel
override fun getSingleChoiceItems(): Map<CharSequence, Any> = Themes.themeList.associate {
activity.getString(it.name) to it.id
}
private lateinit var app: App
private lateinit var dialog: AlertDialog
override fun getDefaultSelectedItem() = Themes.theme.id
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override suspend fun onShow() = Unit
// local variables go here
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.settings_theme_theme_text)
.setSingleChoiceItems(
Themes.getThemeNames(activity).toTypedArray(),
Themes.themeIndex,
null
)
.setPositiveButton(R.string.ok) { _, _ ->
val which = dialog.listView.checkedItemPosition
val theme = Themes.themeList[which]
if (app.config.ui.theme == theme.id)
return@setPositiveButton
app.config.ui.theme = theme.id
Themes.themeIndex = which
activity.recreate()
}
.setNegativeButton(R.string.cancel, null)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
}}
override suspend fun onPositiveClick(): Boolean {
val themeId = getSingleSelection() as? Int ?: return DISMISS
if (app.config.ui.theme != themeId) {
app.config.ui.theme = themeId
Themes.themeInt = themeId
activity.recreate()
}
return DISMISS
}
}

View File

@ -6,89 +6,60 @@ package pl.szczodrzynski.edziennik.ui.dialogs.sync
import android.text.method.LinkMovementMethod
import android.view.LayoutInflater
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import coil.load
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus
import pl.szczodrzynski.edziennik.databinding.DialogRegisterUnavailableBinding
import pl.szczodrzynski.edziennik.ext.onClick
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.utils.Utils
import kotlin.coroutines.CoroutineContext
class RegisterUnavailableDialog(
val activity: AppCompatActivity,
val status: RegisterAvailabilityStatus,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "RegisterUnavailableDialog"
}
activity: AppCompatActivity,
private val status: RegisterAvailabilityStatus,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<DialogRegisterUnavailableBinding>(activity, onShowListener, onDismissListener) {
private lateinit var app: App
private lateinit var dialog: AlertDialog
override val TAG = "RegisterUnavailableDialog"
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun getTitleRes(): Int? = null
override fun inflate(layoutInflater: LayoutInflater) =
DialogRegisterUnavailableBinding.inflate(layoutInflater)
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
override fun getPositiveButtonText() = R.string.close
if (!status.available && status.userMessage != null) {
val b = DialogRegisterUnavailableBinding.inflate(LayoutInflater.from(activity), null, false)
b.message = status.userMessage
if (status.userMessage.image != null)
b.image.load(status.userMessage.image)
if (status.userMessage.url != null) {
b.readMore.onClick {
Utils.openUrl(activity, status.userMessage.url)
}
}
b.text.movementMethod = LinkMovementMethod.getInstance()
dialog = MaterialAlertDialogBuilder(activity)
.setView(b.root)
.setPositiveButton(R.string.close) { dialog, _ ->
dialog.dismiss()
}
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
return@run
}
override suspend fun onBeforeShow(): Boolean {
if (!status.available && status.userMessage != null)
return true
if (status.minVersionCode <= BuildConfig.VERSION_CODE)
return false
val update = app.config.update
if (status.minVersionCode > BuildConfig.VERSION_CODE) {
if (update != null && update.versionCode >= status.minVersionCode) {
UpdateAvailableDialog(activity, update, true, onShowListener, onDismissListener)
}
else {
// this *should* never happen
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.update_available_title)
.setMessage(R.string.update_available_fallback)
.setPositiveButton(R.string.update_available_button) { dialog, _ ->
Utils.openGooglePlay(activity)
dialog.dismiss()
}
.setCancelable(false)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
}
return@run
UpdateAvailableDialog(
activity = activity,
update = update,
mandatory = update != null && update.versionCode >= status.minVersionCode,
onShowListener = onShowListener,
onDismissListener = onDismissListener
).show()
return false
}
override suspend fun onShow() {
b.message = status.userMessage ?: return
b.text.movementMethod = LinkMovementMethod.getInstance()
if (status.userMessage.image != null) {
b.image.load(status.userMessage.image)
}
}}
if (status.userMessage.url != null) {
b.readMore.onClick {
Utils.openUrl(activity, status.userMessage.url)
}
}
}
}

View File

@ -4,48 +4,24 @@
package pl.szczodrzynski.edziennik.ui.dialogs.sync
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import kotlin.coroutines.CoroutineContext
import pl.szczodrzynski.edziennik.ui.dialogs.base.BaseDialog
class ServerMessageDialog(
val activity: AppCompatActivity,
val title: String,
val message: CharSequence,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "ServerMessageDialog"
}
activity: AppCompatActivity,
private val titleText: String,
private val messageText: CharSequence,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BaseDialog(activity, onShowListener, onDismissListener) {
private lateinit var app: App
private lateinit var dialog: AlertDialog
override val TAG = "ServerMessageDialog"
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun getTitle() = titleText
override fun getTitleRes(): Int? = null
override fun getMessage() = messageText
override fun getPositiveButtonText() = R.string.close
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(title)
.setMessage(message)
.setPositiveButton(R.string.close) { dialog, _ ->
dialog.dismiss()
}
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
}}
override suspend fun onShow() = Unit
}

View File

@ -4,113 +4,93 @@
package pl.szczodrzynski.edziennik.ui.dialogs.sync
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ANNOUNCEMENTS
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ATTENDANCE
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_BEHAVIOUR
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_GRADES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOME
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding
import pl.szczodrzynski.edziennik.ui.dialogs.base.BaseDialog
import pl.szczodrzynski.edziennik.ui.messages.list.MessagesFragment
import kotlin.coroutines.CoroutineContext
class SyncViewListDialog(
val activity: MainActivity,
val currentViewId: Int? = null
) : CoroutineScope {
companion object {
private const val TAG = "SyncViewListDialog"
activity: MainActivity,
private val currentViewId: Int,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BaseDialog(activity, onShowListener, onDismissListener) {
override val TAG = "SyncViewListDialog"
override fun getTitleRes() = R.string.dialog_sync_view_list_title
override fun getPositiveButtonText() = R.string.ok
override fun getNeutralButtonText() = R.string.sync_feature_all
override fun getNegativeButtonText() = R.string.cancel
override fun getMultiChoiceItems(): Map<CharSequence, Any> {
items = mapOf(
R.string.menu_timetable to DRAWER_ITEM_TIMETABLE,
R.string.menu_agenda to DRAWER_ITEM_AGENDA,
R.string.menu_grades to DRAWER_ITEM_GRADES,
R.string.menu_homework to DRAWER_ITEM_HOMEWORK,
R.string.menu_notices to DRAWER_ITEM_BEHAVIOUR,
R.string.menu_attendance to DRAWER_ITEM_ATTENDANCE,
R.string.title_messages_inbox_single to (DRAWER_ITEM_MESSAGES to 0),
R.string.title_messages_sent_single to (DRAWER_ITEM_MESSAGES to 1),
R.string.menu_announcements to DRAWER_ITEM_ANNOUNCEMENTS,
).mapKeys { (resId, _) -> activity.getString(resId) }
return items
}
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun getDefaultSelectedItems(): Set<Any> {
val everything = currentViewId == DRAWER_ITEM_HOME
return when {
everything -> items.values.toSet()
currentViewId == DRAWER_ITEM_MESSAGES -> when (MessagesFragment.pageSelection) {
1 -> setOf(DRAWER_ITEM_MESSAGES to 1)
else -> setOf(DRAWER_ITEM_MESSAGES to 0)
}
else -> setOf(currentViewId)
}
}
private val app by lazy { activity.application as App }
private lateinit var b: DialogLessonDetailsBinding
private lateinit var dialog: AlertDialog
override suspend fun onShow() = Unit
init { run {
job = Job()
private lateinit var items: Map<CharSequence, Any>
val viewIds = arrayOf(
MainActivity.DRAWER_ITEM_TIMETABLE,
MainActivity.DRAWER_ITEM_AGENDA,
MainActivity.DRAWER_ITEM_GRADES,
MainActivity.DRAWER_ITEM_HOMEWORK,
MainActivity.DRAWER_ITEM_BEHAVIOUR,
MainActivity.DRAWER_ITEM_ATTENDANCE,
MainActivity.DRAWER_ITEM_MESSAGES,
MainActivity.DRAWER_ITEM_MESSAGES,
MainActivity.DRAWER_ITEM_ANNOUNCEMENTS
)
@Suppress("UNCHECKED_CAST")
override suspend fun onPositiveClick(): Boolean {
val selected = getMultiSelection().mapNotNull {
when (it) {
is Int -> it to 0
is Pair<*, *> -> it as Pair<Int, Int>
else -> null
}
}
val items = arrayOf<String>(
app.getString(R.string.menu_timetable),
app.getString(R.string.menu_agenda),
app.getString(R.string.menu_grades),
app.getString(R.string.menu_homework),
app.getString(R.string.menu_notices),
app.getString(R.string.menu_attendance),
app.getString(R.string.title_messages_inbox_single),
app.getString(R.string.title_messages_sent_single),
app.getString(R.string.menu_announcements)
)
if (selected.isEmpty())
return DISMISS
val everything = currentViewId == MainActivity.DRAWER_ITEM_HOME
val checkedItems = booleanArrayOf(
everything || currentViewId == MainActivity.DRAWER_ITEM_TIMETABLE,
everything || currentViewId == MainActivity.DRAWER_ITEM_AGENDA,
everything || currentViewId == MainActivity.DRAWER_ITEM_GRADES,
everything || currentViewId == MainActivity.DRAWER_ITEM_HOMEWORK,
everything || currentViewId == MainActivity.DRAWER_ITEM_BEHAVIOUR,
everything || currentViewId == MainActivity.DRAWER_ITEM_ATTENDANCE,
everything || currentViewId == MainActivity.DRAWER_ITEM_MESSAGES && MessagesFragment.pageSelection != 1,
everything || currentViewId == MainActivity.DRAWER_ITEM_MESSAGES && MessagesFragment.pageSelection == 1,
everything || currentViewId == MainActivity.DRAWER_ITEM_ANNOUNCEMENTS
)
val userChooses = checkedItems.toMutableList()
if (activity is MainActivity)
activity.swipeRefreshLayout.isRefreshing = true
EdziennikTask.syncProfile(
App.profileId,
selected
).enqueue(activity)
return DISMISS
}
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.dialog_sync_view_list_title)
.setMultiChoiceItems(items, checkedItems) { _, which, isChecked ->
userChooses[which] = isChecked
}
.setPositiveButton(R.string.ok) { _, _ ->
dialog.dismiss()
val selectedViewIds = userChooses.mapIndexed { index, it ->
if (it)
viewIds[index] to when (index) {
7 -> 1
else -> 0
}
else
null
}.let {
listOfNotNull(*it.toTypedArray())
}
if (selectedViewIds.isNotEmpty()) {
activity.swipeRefreshLayout.isRefreshing = true
EdziennikTask.syncProfile(
App.profileId,
selectedViewIds
).enqueue(activity)
}
}
.setNeutralButton(R.string.sync_feature_all) { _, _ ->
dialog.dismiss()
activity.swipeRefreshLayout.isRefreshing = true
EdziennikTask.syncProfile(App.profileId).enqueue(activity)
}
.setNegativeButton(R.string.cancel) { _, _ ->
dialog.dismiss()
}
.show()
}}
override suspend fun onNeutralClick(): Boolean {
if (activity is MainActivity)
activity.swipeRefreshLayout.isRefreshing = true
EdziennikTask.syncProfile(App.profileId).enqueue(activity)
return DISMISS
}
}

View File

@ -4,66 +4,54 @@
package pl.szczodrzynski.edziennik.ui.dialogs.sync
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
import pl.szczodrzynski.edziennik.ext.Intent
import pl.szczodrzynski.edziennik.ext.setMessage
import pl.szczodrzynski.edziennik.sync.UpdateDownloaderService
import pl.szczodrzynski.edziennik.ui.dialogs.base.BaseDialog
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
import kotlin.coroutines.CoroutineContext
class UpdateAvailableDialog(
val activity: AppCompatActivity,
val update: Update,
val mandatory: Boolean = update.updateMandatory,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "UpdateAvailableDialog"
activity: AppCompatActivity,
private val update: Update?,
private val mandatory: Boolean = update?.updateMandatory ?: false,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BaseDialog(activity, onShowListener, onDismissListener) {
override val TAG = "UpdateAvailableDialog"
override fun getTitleRes() = R.string.update_available_title
override fun getMessageFormat(): Pair<Int, List<CharSequence>> {
if (update != null) {
return R.string.update_available_format to listOf(
BuildConfig.VERSION_NAME,
update.versionName,
update.releaseNotes?.let { BetterHtml.fromHtml(activity, it) } ?: "---",
)
}
return R.string.update_available_fallback to emptyList()
}
private lateinit var app: App
private lateinit var dialog: AlertDialog
override fun isCancelable() = !mandatory
override fun getPositiveButtonText() = R.string.update_available_button
override fun getNeutralButtonText() = if (mandatory) null else R.string.update_available_later
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override suspend fun onShow() = Unit
init { run {
if (activity.isFinishing)
return@run
if (update.versionCode <= BuildConfig.VERSION_CODE)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
override suspend fun onPositiveClick(): Boolean {
if (update == null)
Utils.openGooglePlay(activity)
else
activity.startService(Intent(app, UpdateDownloaderService::class.java))
return NO_DISMISS
}
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.update_available_title)
.setMessage(
R.string.update_available_format,
BuildConfig.VERSION_NAME,
update.versionName,
update.releaseNotes?.let { BetterHtml.fromHtml(activity, it) } ?: "---"
)
.setPositiveButton(R.string.update_available_button) { dialog, _ ->
activity.startService(Intent(app, UpdateDownloaderService::class.java))
dialog.dismiss()
}
.also {
if (!mandatory)
it.setNeutralButton(R.string.update_available_later, null)
}
.setCancelable(!mandatory)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
}}
override suspend fun onBeforeShow(): Boolean {
// show only if app is older than available
return update == null || update.versionCode > BuildConfig.VERSION_CODE
}
}

View File

@ -5,78 +5,64 @@
package pl.szczodrzynski.edziennik.ui.error
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.ext.*
import kotlin.coroutines.CoroutineContext
import pl.szczodrzynski.edziennik.ui.dialogs.base.BaseDialog
class ErrorDetailsDialog(
val activity: AppCompatActivity,
val errors: List<ApiError>,
val titleRes: Int = R.string.dialog_error_details_title,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "ApiErrorDialog"
}
activity: AppCompatActivity,
private val errors: List<ApiError>,
private val titleRes: Int = R.string.dialog_error_details_title,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BaseDialog(activity, onShowListener, onDismissListener) {
private lateinit var app: App
private lateinit var dialog: AlertDialog
override val TAG = "ErrorDetailsDialog"
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun getTitleRes() = titleRes
override fun getMessage() = errors.map {
listOf(
it.getStringReason(activity)
.asBoldSpannable()
.asColoredSpannable(R.attr.colorOnBackground.resolveAttr(activity)),
activity.getString(R.string.error_unknown_format, it.errorCode, it.tag),
if (App.devMode)
it.throwable?.stackTraceString ?: it.throwable?.localizedMessage
else
it.throwable?.localizedMessage
).concat("\n")
}.concat("\n\n")
override fun isCancelable() = false
override fun getPositiveButtonText() = R.string.close
override fun getNeutralButtonText() = R.string.report
override suspend fun onShow() = Unit
private val api by lazy { SzkolnyApi(activity.applicationContext as App) }
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
override suspend fun onBeforeShow(): Boolean {
return errors.isNotEmpty()
}
if (errors.isNotEmpty()) {
val message = errors.map {
listOf(
it.getStringReason(activity).asBoldSpannable().asColoredSpannable(R.attr.colorOnBackground.resolveAttr(activity)),
activity.getString(R.string.error_unknown_format, it.errorCode, it.tag),
if (App.devMode)
it.throwable?.stackTraceString ?: it.throwable?.localizedMessage
else
it.throwable?.localizedMessage
).concat("\n")
}.concat("\n\n")
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(titleRes)
.setMessage(message)
.setPositiveButton(R.string.ok) { _, _ ->
dialog.dismiss()
}
.setNeutralButton(R.string.report) { _, _ ->
launch {
api.runCatching({
withContext(Dispatchers.Default) {
errorReport(errors.map { it.toReportableError(activity) })
}
}, {
Toast.makeText(activity, activity.getString(R.string.crash_report_cannot_send) + it, Toast.LENGTH_LONG).show()
}) ?: return@launch
dialog.dismiss()
}
}
.setCancelable(false)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
}
}}
override suspend fun onNeutralClick(): Boolean {
api.runCatching({
withContext(Dispatchers.Default) {
errorReport(errors.map { it.toReportableError(activity) })
}
}, {
Toast.makeText(
activity,
activity.getString(R.string.crash_report_cannot_send) + it,
Toast.LENGTH_LONG
).show()
})
return DISMISS
}
}

View File

@ -34,7 +34,7 @@ class ErrorSnackbar(val activity: AppCompatActivity) : CoroutineScope {
this.coordinator = coordinatorLayout
snackbar = Snackbar.make(coordinator, R.string.snackbar_error_text, Snackbar.LENGTH_INDEFINITE)
snackbar?.setAction(R.string.more) {
ErrorDetailsDialog(activity, errors)
ErrorDetailsDialog(activity, errors).show()
errors = mutableListOf()
}
val bgColor = ColorUtils.compositeColors(

View File

@ -8,6 +8,7 @@ import android.content.ActivityNotFoundException
import android.content.Intent
import android.provider.CalendarContract
import android.provider.CalendarContract.Events
import android.view.LayoutInflater
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
@ -25,74 +26,63 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.databinding.DialogEventDetailsBinding
import pl.szczodrzynski.edziennik.ext.*
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.ui.timetable.TimetableFragment
import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.coroutines.CoroutineContext
// TODO: 2021-10-19 rewrite to the new dialog style
class EventDetailsDialog(
val activity: AppCompatActivity,
// this event is observed for changes
var event: EventFull,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "EventDetailsDialog"
}
activity: AppCompatActivity,
// this event is observed for changes
private var event: EventFull,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<DialogEventDetailsBinding>(activity, onShowListener, onDismissListener) {
override val TAG = "EventDetailsDialog"
override fun getTitleRes(): Int? = null
override fun inflate(layoutInflater: LayoutInflater) =
DialogEventDetailsBinding.inflate(layoutInflater)
override fun getPositiveButtonText() = R.string.close
override fun getNeutralButtonText() = if (event.addedManually) R.string.remove else null
private lateinit var app: App
private lateinit var b: DialogEventDetailsBinding
private lateinit var dialog: AlertDialog
private var removeEventDialog: AlertDialog? = null
private val eventShared = event.sharedBy != null
private val eventOwn = event.sharedBy == "self"
private val manager
get() = app.eventManager
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private val api by lazy {
SzkolnyApi(app)
}
private var progressDialog: AlertDialog? = null
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
override suspend fun onNeutralClick(): Boolean {
showRemoveEventDialog()
return NO_DISMISS
}
override suspend fun onBeforeShow(): Boolean {
EventBus.getDefault().register(this)
app = activity.applicationContext as App
b = DialogEventDetailsBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setView(b.root)
.setPositiveButton(R.string.close) { dialog, _ ->
dialog.dismiss()
}
.apply {
if (event.addedManually)
setNeutralButton(R.string.remove, null)
}
.setOnDismissListener {
onDismissListener?.invoke(TAG)
EventBus.getDefault().unregister(this@EventDetailsDialog)
progressDialog?.dismiss()
}
.show()
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.onClick {
showRemoveEventDialog()
}
return true
}
override suspend fun onShow() {
// watch the event for changes
app.db.eventDao().getById(event.profileId, event.id).observe(activity) {
event = it ?: return@observe
update()
}
}}
}
override fun onDismiss() {
EventBus.getDefault().unregister(this@EventDetailsDialog)
progressDialog?.dismiss()
}
private fun update() {
b.event = event
@ -185,7 +175,7 @@ class EventDetailsDialog(
},
onShowListener = onShowListener,
onDismissListener = onDismissListener
)
).show()
}
b.editButton.attachToastHint(R.string.hint_edit_event)

View File

@ -4,10 +4,10 @@
package pl.szczodrzynski.edziennik.ui.event
import android.view.LayoutInflater
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL
import androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -32,6 +32,7 @@ import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
import pl.szczodrzynski.edziennik.ext.*
import pl.szczodrzynski.edziennik.ui.dialogs.StyledTextDialog
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.ui.dialogs.settings.RegistrationConfigDialog
import pl.szczodrzynski.edziennik.ui.views.TimeDropdown.Companion.DISPLAY_LESSONS
import pl.szczodrzynski.edziennik.utils.Anim
@ -40,32 +41,32 @@ import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.HtmlMode.SIM
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.StylingConfigBase
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import kotlin.coroutines.CoroutineContext
// TODO: 2021-10-19 rewrite to the new dialog style
class EventManualDialog(
val activity: AppCompatActivity,
val profileId: Int,
val defaultLesson: LessonFull? = null,
val defaultDate: Date? = null,
val defaultTime: Time? = null,
val defaultType: Long? = null,
val editingEvent: EventFull? = null,
val onSaveListener: ((event: EventFull?) -> Unit)? = null,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
activity: AppCompatActivity,
private val profileId: Int,
private val defaultLesson: LessonFull? = null,
private val defaultDate: Date? = null,
private val defaultTime: Time? = null,
private val defaultType: Long? = null,
private val editingEvent: EventFull? = null,
private val onSaveListener: ((event: EventFull?) -> Unit)? = null,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<DialogEventManualV2Binding>(activity, onShowListener, onDismissListener) {
companion object {
private const val TAG = "EventManualDialog"
}
override val TAG = "EventManualDialog"
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun getTitleRes() = R.string.dialog_event_manual_title
override fun inflate(layoutInflater: LayoutInflater) =
DialogEventManualV2Binding.inflate(layoutInflater)
override fun isCancelable() = false
override fun getPositiveButtonText() = R.string.save
override fun getNeutralButtonText() = if (editingEvent != null) R.string.remove else null
override fun getNegativeButtonText() = R.string.cancel
private val app by lazy { activity.application as App }
private lateinit var b: DialogEventManualV2Binding
private lateinit var dialog: AlertDialog
private lateinit var profile: Profile
private lateinit var stylingConfig: StylingConfigBase
@ -86,46 +87,28 @@ class EventManualDialog(
private var progressDialog: AlertDialog? = null
init { launch {
if (activity.isFinishing)
return@launch
onShowListener?.invoke(TAG)
override suspend fun onPositiveClick(): Boolean {
saveEvent()
return NO_DISMISS
}
override suspend fun onNeutralClick(): Boolean {
showRemoveEventDialog()
return NO_DISMISS
}
override suspend fun onBeforeShow(): Boolean {
EventBus.getDefault().register(this@EventManualDialog)
b = DialogEventManualV2Binding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.dialog_event_manual_title)
.setView(b.root)
.setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
.setPositiveButton(R.string.save, null)
.apply {
if (editingEvent != null) {
setNeutralButton(R.string.remove, null)
}
}
.setOnDismissListener {
onDismissListener?.invoke(TAG)
EventBus.getDefault().unregister(this@EventManualDialog)
enqueuedWeekDialog?.dismiss()
progressDialog?.dismiss()
}
.setCancelable(false)
.create()
.apply {
setOnShowListener { dialog ->
val positiveButton = (dialog as AlertDialog).getButton(BUTTON_POSITIVE)
positiveButton?.setOnClickListener {
saveEvent()
}
return true
}
val neutralButton = dialog.getButton(BUTTON_NEUTRAL)
neutralButton?.setOnClickListener {
showRemoveEventDialog()
}
}
show()
}
override fun onDismiss() {
EventBus.getDefault().unregister(this@EventManualDialog)
enqueuedWeekDialog?.dismiss()
progressDialog?.dismiss()
}
override suspend fun onShow() {
b.shareSwitch.isChecked = editingShared
b.shareSwitch.isEnabled = !editingShared || (editingShared && editingOwn)
@ -152,7 +135,7 @@ class EventManualDialog(
},
onShowListener,
onDismissListener
)
).show()
}
stylingConfig = StylingConfigBase(editText = b.topic, htmlMode = SIMPLE)
@ -163,7 +146,7 @@ class EventManualDialog(
}
loadLists()
}}
}
private fun updateShareText(checked: Boolean = b.shareSwitch.isChecked) {
b.shareDetails.visibility = if (checked || editingShared)
@ -259,13 +242,13 @@ class EventManualDialog(
progressDialog?.dismiss()
}
private fun loadLists() = launch {
private suspend fun loadLists() {
val profile = withContext(Dispatchers.Default) {
app.db.profileDao().getByIdNow(profileId)
}
if (profile == null) {
Toast.makeText(activity, R.string.event_manual_no_profile, Toast.LENGTH_SHORT).show()
return@launch
return
}
this@EventManualDialog.profile = profile
@ -611,7 +594,7 @@ class EventManualDialog(
it.teamName = b.teamDropdown.getSelected()?.name
it.typeName = b.typeDropdown.getSelected()?.name
})
dialog.dismiss()
dismiss()
Toast.makeText(activity, R.string.saved, Toast.LENGTH_SHORT).show()
}
private fun finishRemoving() {
@ -624,7 +607,7 @@ class EventManualDialog(
removeEventDialog?.dismiss()
onSaveListener?.invoke(null)
dialog.dismiss()
dismiss()
Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show()
}
}

View File

@ -1,56 +1,39 @@
package pl.szczodrzynski.edziennik.ui.grades
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.ColorUtils
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.databinding.DialogGradeDetailsBinding
import pl.szczodrzynski.edziennik.ext.onClick
import pl.szczodrzynski.edziennik.ext.setTintColor
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog
import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import kotlin.coroutines.CoroutineContext
class GradeDetailsDialog(
val activity: AppCompatActivity,
val grade: GradeFull,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "GradeDetailsDialog"
}
activity: AppCompatActivity,
private val grade: GradeFull,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<DialogGradeDetailsBinding>(activity, onShowListener, onDismissListener) {
private lateinit var app: App
private lateinit var b: DialogGradeDetailsBinding
private lateinit var dialog: AlertDialog
override val TAG = "GradeDetailsDialog"
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun getTitleRes(): Int? = null
override fun inflate(layoutInflater: LayoutInflater) =
DialogGradeDetailsBinding.inflate(layoutInflater)
// local variables go here
override fun getPositiveButtonText() = R.string.close
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
b = DialogGradeDetailsBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setView(b.root)
.setPositiveButton(R.string.close, null)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
override suspend fun onShow() {
val manager = app.gradesManager
val gradeColor = manager.getGradeColor(grade)
@ -58,15 +41,23 @@ class GradeDetailsDialog(
b.weightText = manager.getWeightString(app, grade)
b.commentVisible = false
b.devMode = App.devMode
b.gradeName.setTextColor(if (ColorUtils.calculateLuminance(gradeColor) > 0.3) 0xaa000000.toInt() else 0xccffffff.toInt())
b.gradeName.setTextColor(
if (ColorUtils.calculateLuminance(gradeColor) > 0.3)
0xaa000000.toInt()
else
0xccffffff.toInt()
)
b.gradeName.background.setTintColor(gradeColor)
b.gradeValue = if (grade.weight == 0f || grade.value < 0f) -1f else manager.getGradeValue(grade)
b.gradeValue = if (grade.weight == 0f || grade.value < 0f)
-1f
else
manager.getGradeValue(grade)
b.customValueDivider.isVisible = manager.plusValue != null || manager.minusValue != null
b.customValueLayout.isVisible = b.customValueDivider.isVisible
b.customValueButton.onClick {
GradesConfigDialog(activity, reloadOnDismiss = true)
GradesConfigDialog(activity, reloadOnDismiss = true).show()
}
grade.teacherName?.let { name ->
@ -77,24 +68,26 @@ class GradeDetailsDialog(
)
}
launch {
val historyList = withContext(Dispatchers.Default) {
app.db.gradeDao().getByParentIdNow(App.profileId, grade.id)
}
if (historyList.isEmpty()) {
b.historyVisible = false
return@launch
}
b.historyVisible = true
//b.gradeHistoryNest.isNestedScrollingEnabled = false
b.gradeHistoryList.adapter = GradesAdapter(activity, {
GradeDetailsDialog(activity, it)
}).also { it.items = historyList.toMutableList() }
b.gradeHistoryList.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
addItemDecoration(SimpleDividerItemDecoration(context))
}
val historyList = withContext(Dispatchers.Default) {
app.db.gradeDao().getByParentIdNow(App.profileId, grade.id)
}
}}
if (historyList.isEmpty()) {
b.historyVisible = false
return
}
b.historyVisible = true
//b.gradeHistoryNest.isNestedScrollingEnabled = false
b.gradeHistoryList.adapter = GradesAdapter(activity, {
GradeDetailsDialog(activity, it).show()
}).also {
it.items = historyList.toMutableList()
}
b.gradeHistoryList.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
addItemDecoration(SimpleDividerItemDecoration(context))
}
}
}

View File

@ -110,7 +110,7 @@ class GradesListFragment : Fragment(), CoroutineScope {
}})
adapter.onGradeClick = {
GradeDetailsDialog(activity, it)
GradeDetailsDialog(activity, it).show()
}
adapter.onGradesEditorClick = { subject, semester ->
@ -140,7 +140,7 @@ class GradesListFragment : Fragment(), CoroutineScope {
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
.withOnClickListener(View.OnClickListener {
activity.bottomSheet.close()
GradesConfigDialog(activity, true, null, null)
GradesConfigDialog(activity, true, null, null).show()
}),
BottomSheetSeparatorItem(true),
BottomSheetPrimaryItem(true)

View File

@ -105,7 +105,7 @@ class StatsViewHolder(
b.customValueDivider.isVisible = manager.dontCountEnabled || manager.plusValue != null || manager.minusValue != null
b.customValueLayout.isVisible = b.customValueDivider.isVisible
b.customValueButton.onClick {
GradesConfigDialog(activity, reloadOnDismiss = true)
GradesConfigDialog(activity, reloadOnDismiss = true).show()
}
}

View File

@ -70,7 +70,7 @@ class CounterActivity : AppCompatActivity(), CoroutineScope {
}
)
b.bellSync.onClick {
BellSyncTimeChooseDialog(activity = this@CounterActivity)
BellSyncTimeChooseDialog(activity = this@CounterActivity).show()
}
app.config.timetable.bellSyncDiff?.let {

View File

@ -4,77 +4,66 @@
package pl.szczodrzynski.edziennik.ui.home
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.ui.dialogs.base.BaseDialog
import pl.szczodrzynski.edziennik.ui.home.HomeCard.Companion.CARD_EVENTS
import pl.szczodrzynski.edziennik.ui.home.HomeCard.Companion.CARD_GRADES
import pl.szczodrzynski.edziennik.ui.home.HomeCard.Companion.CARD_LUCKY_NUMBER
import pl.szczodrzynski.edziennik.ui.home.HomeCard.Companion.CARD_TIMETABLE
import kotlin.collections.set
class HomeConfigDialog(
val activity: AppCompatActivity,
private val reloadOnDismiss: Boolean = true,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) {
companion object {
const val TAG = "HomeConfigDialog"
activity: AppCompatActivity,
private val reloadOnDismiss: Boolean = true,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BaseDialog(activity, onShowListener, onDismissListener) {
override val TAG = "HomeConfigDialog"
override fun getTitleRes() = R.string.home_configure_add_remove
override fun getPositiveButtonText() = R.string.ok
override fun getNegativeButtonText() = R.string.cancel
override fun getMultiChoiceItems(): Map<CharSequence, Any> = mapOf(
R.string.card_type_lucky_number to CARD_LUCKY_NUMBER,
R.string.card_type_timetable to CARD_TIMETABLE,
R.string.card_type_grades to CARD_GRADES,
R.string.card_type_events to CARD_EVENTS,
).mapKeys { (resId, _) -> activity.getString(resId) }
override fun getDefaultSelectedItems() =
profileConfig.homeCards
.filter { it.profileId == App.profileId }
.map { it.cardId }
.toSet()
override suspend fun onShow() = Unit
private val profileConfig by lazy { app.config.getFor(app.profileId).ui }
private var configChanged = false
override suspend fun onPositiveClick(): Boolean {
val homeCards = profileConfig.homeCards.toMutableList()
homeCards.removeAll { it.profileId == App.profileId }
homeCards += getMultiSelection().mapNotNull {
HomeCardModel(
profileId = App.profileId,
cardId = it as? Int ?: return@mapNotNull null
)
}
profileConfig.homeCards = homeCards
return DISMISS
}
private val app by lazy { activity.application as App }
private val profileConfig by lazy { app.config.getFor(app.profileId).ui }
override suspend fun onMultiSelectionChanged(items: Set<Any>) {
configChanged = true
}
private lateinit var dialog: AlertDialog
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
val ids = listOf(
CARD_LUCKY_NUMBER,
CARD_TIMETABLE,
CARD_GRADES,
CARD_EVENTS
)
val items = listOf(
app.getString(R.string.card_type_lucky_number),
app.getString(R.string.card_type_timetable),
app.getString(R.string.card_type_grades),
app.getString(R.string.card_type_events)
)
val checkedItems = ids.map { it to false }.toMap().toMutableMap()
val profileId = App.profileId
val homeCards = profileConfig.homeCards
.filter { it.profileId == profileId }
.toMutableList()
homeCards.forEach {
checkedItems[it.cardId] = true
}
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.home_configure_add_remove)
.setMultiChoiceItems(items.toTypedArray(), checkedItems.values.toBooleanArray()) { _, which, isChecked ->
if (isChecked) {
homeCards += HomeCardModel(profileId, ids[which])
}
else {
homeCards.removeAll { it.profileId == profileId && it.cardId == ids[which] }
}
}
.setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
.setOnDismissListener {
profileConfig.homeCards = homeCards
onDismissListener?.invoke(TAG)
if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget()
}
.show()
}}
override fun onDismiss() {
if (configChanged && reloadOnDismiss && activity is MainActivity)
activity.reloadTarget()
}
}

View File

@ -102,7 +102,7 @@ class HomeFragment : Fragment(), CoroutineScope {
.withIcon(Icon.cmd_card_bulleted_settings_outline)
.withOnClickListener(OnClickListener {
activity.bottomSheet.close()
HomeConfigDialog(activity, reloadOnDismiss = true)
HomeConfigDialog(activity, reloadOnDismiss = true).show()
}),
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_set_student_number)
@ -131,7 +131,7 @@ class HomeFragment : Fragment(), CoroutineScope {
})
)
b.configureCards.onClick {
HomeConfigDialog(activity, reloadOnDismiss = true)
HomeConfigDialog(activity, reloadOnDismiss = true).show()
}
b.scrollView.setOnScrollChangeListener { _: NestedScrollView?, _: Int, scrollY: Int, _: Int, _: Int ->

View File

@ -72,7 +72,7 @@ class HomeAvailabilityCard(
if (status.userMessage.icon != null)
b.homeAvailabilityIcon.load(status.userMessage.icon)
onInfoClick = {
RegisterUnavailableDialog(activity, status)
RegisterUnavailableDialog(activity, status).show()
}
}
// show "update available" when available OR version too old for the register
@ -82,7 +82,7 @@ class HomeAvailabilityCard(
b.homeAvailabilityUpdate.isVisible = true
b.homeAvailabilityIcon.setImageResource(R.drawable.ic_update)
onInfoClick = {
UpdateAvailableDialog(activity, update)
UpdateAvailableDialog(activity, update).show()
}
}
else {

View File

@ -87,7 +87,7 @@ class HomeDebugCard(
b.librusCaptchaButton.onClick {
//app.startActivity(Intent(activity, LoginLibrusCaptchaActivity::class.java))
LibrusCaptchaDialog(activity, onSuccess = {}, onFailure = {})
LibrusCaptchaDialog(activity, onSuccess = {}, onFailure = {}).show()
}
b.getLogs.onClick {

View File

@ -70,14 +70,14 @@ class HomeEventsCard(
EventDetailsDialog(
activity,
it
)
).show()
},
onEventEditClick = {
EventManualDialog(
activity,
it.profileId,
editingEvent = it
)
).show()
}
)

View File

@ -112,7 +112,7 @@ class HomeTimetableCard(
b.bellSync.setOnClickListener {
BellSyncTimeChooseDialog(
activity
)
).show()
}
b.showCounter.setOnClickListener {

View File

@ -65,7 +65,7 @@ class HomeworkFragment : Fragment(), CoroutineScope {
.withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline)
.withOnClickListener(View.OnClickListener {
activity.bottomSheet.close()
EventManualDialog(activity, App.profileId, defaultType = Event.TYPE_HOMEWORK)
EventManualDialog(activity, App.profileId, defaultType = Event.TYPE_HOMEWORK).show()
}),
BottomSheetSeparatorItem(true),
BottomSheetPrimaryItem(true)
@ -108,7 +108,7 @@ class HomeworkFragment : Fragment(), CoroutineScope {
}
setFabOnClickListener(View.OnClickListener {
EventManualDialog(activity, App.profileId, defaultType = Event.TYPE_HOMEWORK)
EventManualDialog(activity, App.profileId, defaultType = Event.TYPE_HOMEWORK).show()
})
}

View File

@ -68,14 +68,14 @@ class HomeworkListFragment : LazyFragment(), CoroutineScope {
EventDetailsDialog(
activity,
it
)
).show()
},
onEventEditClick = {
EventManualDialog(
activity,
it.profileId,
editingEvent = it
)
).show()
}
)

View File

@ -275,7 +275,7 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
return when (error.type) {
Type.NOT_AVAILABLE -> {
RegisterUnavailableDialog(activity, error.status!!)
RegisterUnavailableDialog(activity, error.status!!).show()
false
}
Type.API_ERROR -> {

View File

@ -141,7 +141,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
.withOnClickListener {
activity.bottomSheet.close()
MessagesConfigDialog(activity, false, null, null)
MessagesConfigDialog(activity, false, null, null).show()
}
)

View File

@ -118,7 +118,7 @@ class MessagesFragment : Fragment(), CoroutineScope {
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
.withOnClickListener {
activity.bottomSheet.close()
MessagesConfigDialog(activity, false, null, null)
MessagesConfigDialog(activity, false, null, null).show()
}
)

View File

@ -74,7 +74,7 @@ class MessageFragment : Fragment(), CoroutineScope {
.withIcon(CommunityMaterial.Icon.cmd_cog_outline)
.withOnClickListener {
activity.bottomSheet.close()
MessagesConfigDialog(activity, false, null, null)
MessagesConfigDialog(activity, false, null, null).show()
}
)

View File

@ -104,7 +104,7 @@ class SettingsAboutCard(util: SettingsUtil) : SettingsCard(util), CoroutineScope
text = R.string.settings_about_changelog_text,
icon = CommunityMaterial.Icon3.cmd_radar
) {
ChangelogDialog(activity)
ChangelogDialog(activity).show()
},
util.createActionItem(

View File

@ -31,7 +31,7 @@ class SettingsProfileCard(util: SettingsUtil) : SettingsCard(util) {
card.items.remove(item)
card.items.add(index, getProfileItem())
util.refresh()
})
}).show()
}
override fun getItems() = listOf(

View File

@ -59,28 +59,28 @@ class SettingsRegisterCard(util: SettingsUtil) : SettingsCard(util) {
text = R.string.menu_agenda_config,
icon = CommunityMaterial.Icon.cmd_calendar_outline
) {
AgendaConfigDialog(activity, reloadOnDismiss = false)
AgendaConfigDialog(activity, reloadOnDismiss = false).show()
},
util.createActionItem(
text = R.string.menu_grades_config,
icon = CommunityMaterial.Icon3.cmd_numeric_5_box_outline
) {
GradesConfigDialog(activity, reloadOnDismiss = false)
GradesConfigDialog(activity, reloadOnDismiss = false).show()
},
util.createActionItem(
text = R.string.menu_messages_config,
icon = CommunityMaterial.Icon.cmd_calendar_outline
) {
MessagesConfigDialog(activity, reloadOnDismiss = false)
MessagesConfigDialog(activity, reloadOnDismiss = false).show()
},
util.createActionItem(
text = R.string.menu_attendance_config,
icon = CommunityMaterial.Icon.cmd_calendar_remove_outline
) {
AttendanceConfigDialog(activity, reloadOnDismiss = false)
AttendanceConfigDialog(activity, reloadOnDismiss = false).show()
},
util.createPropertyItem(
@ -126,7 +126,7 @@ class SettingsRegisterCard(util: SettingsUtil) : SettingsCard(util) {
BellSyncConfigDialog(activity, onChangeListener = {
it.subText = getBellSync()
util.refresh()
})
}).show()
}
).also {
it.subText = getBellSync()

View File

@ -108,7 +108,7 @@ class SettingsSyncCard(util: SettingsUtil) : SettingsCard(util) {
item.onCheckedChangedAction.onCheckedChanged(item, true)
if (configGlobal.sync.enabled)
util.refresh()
})
}).show()
}
).also {
it.subTextChecked = activity.getSyncInterval(configGlobal.sync.interval)
@ -124,7 +124,7 @@ class SettingsSyncCard(util: SettingsUtil) : SettingsCard(util) {
subText = R.string.settings_profile_notifications_subtext,
icon = CommunityMaterial.Icon2.cmd_filter_outline
) {
NotificationFilterDialog(activity)
NotificationFilterDialog(activity).show()
},
util.createPropertyActionItem(

View File

@ -54,7 +54,7 @@ class SettingsThemeCard(util: SettingsUtil) : SettingsCard(util) {
subText = Themes.getThemeNameRes(),
icon = CommunityMaterial.Icon3.cmd_palette_outline
) {
ThemeChooserDialog(activity)
ThemeChooserDialog(activity).show()
},
util.createActionItem(
@ -62,7 +62,7 @@ class SettingsThemeCard(util: SettingsUtil) : SettingsCard(util) {
subText = R.string.settings_about_language_subtext,
icon = CommunityMaterial.Icon3.cmd_translate
) {
AppLanguageDialog(activity)
AppLanguageDialog(activity).show()
},
util.createPropertyItem(
@ -81,7 +81,7 @@ class SettingsThemeCard(util: SettingsUtil) : SettingsCard(util) {
text = R.string.settings_theme_mini_drawer_buttons_text,
icon = CommunityMaterial.Icon2.cmd_format_list_checks
) {
MiniMenuConfigDialog(activity)
MiniMenuConfigDialog(activity).show()
},
util.createActionItem(

View File

@ -4,63 +4,77 @@
package pl.szczodrzynski.edziennik.ui.template
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.DialogTemplateBinding
import pl.szczodrzynski.edziennik.ext.onClick
import kotlin.coroutines.CoroutineContext
import pl.szczodrzynski.edziennik.ui.dialogs.base.BaseDialog
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.ui.dialogs.base.ViewDialog
/**
* This class represents a sample dialog using the new style.
*
* A dialog may subclass the [BaseDialog], [ViewDialog] or [BindingDialog].
*
* Fields and methods have the preferred order which should be used when writing new code.
* The position of the first occurrence of duplicated methods should be used.
* Multi-line methods should be followed by a blank line, one-liners may be just joined together.
*
* Constructor properties should be private.
*
* [onShow], when not used, should be placed just before the local variables, as a one-liner.
* All other multi-line methods go below the local variables part.
*/
class TemplateDialog(
val activity: AppCompatActivity,
val onActionPerformed: (() -> Unit)? = null,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "TemplateDialog"
}
activity: AppCompatActivity,
private val onActionPerformed: (() -> Unit)? = null,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<DialogTemplateBinding>(activity, onShowListener, onDismissListener) {
private lateinit var app: App
private lateinit var b: DialogTemplateBinding
private lateinit var dialog: AlertDialog
override val TAG = "TemplateDialog"
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun getTitle(): CharSequence = "Template"
override fun getTitleRes() = R.string.menu_template
override fun inflate(layoutInflater: LayoutInflater) =
DialogTemplateBinding.inflate(layoutInflater)
// override fun getTitle(): CharSequence = "Template"
// override fun getTitleRes() = R.string.menu_template
// override fun getMessage() = ""
// override fun getMessageRes() = R.string.edziennik_progress_login_template_api
// override fun getMessageFormat() =
// R.string.card_update_text_format to listOf(
// BuildConfig.VERSION_BASE,
// "5.0",
// )
// override fun getTitleRes() = R.string.menu_template
override fun isCancelable() = true
override fun getPositiveButtonText() = R.string.ok
override fun getNeutralButtonText() = R.string.reset
override fun getNegativeButtonText() = R.string.cancel
// getSingleChoiceItem / getMultiChoiceItems
// getDefaultSelectedItem / getDefaultSelectedItems
// to convert a map of StringIDs to CharSequences
// .mapKeys { (resId, _) -> activity.getString(resId) }
override suspend fun onShow() = Unit
// local variables go here
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
b = DialogTemplateBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setView(b.root)
.setPositiveButton(R.string.close) { dialog, _ ->
dialog.dismiss()
}
.setNeutralButton(R.string.add, null)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
// onPositiveClick
// onNeutralClick
// onNegativeClick
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.onClick {
// do custom action on neutral button click
// (does not dismiss the dialog)
}
// onSingleSelectionChanged
// onMultiSelectionChanged
b.clickMe.onClick {
onActionPerformed?.invoke()
dialog.dismiss()
}
}}
// getRootView
// onBeforeShow
// onShow
// onDismiss
}

View File

@ -5,19 +5,14 @@
package pl.szczodrzynski.edziennik.ui.timetable
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
@ -27,6 +22,7 @@ import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding
import pl.szczodrzynski.edziennik.ext.onClick
import pl.szczodrzynski.edziennik.ext.setText
import pl.szczodrzynski.edziennik.ui.attendance.AttendanceDetailsDialog
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.ui.event.EventDetailsDialog
import pl.szczodrzynski.edziennik.ui.event.EventListAdapter
import pl.szczodrzynski.edziennik.ui.event.EventManualDialog
@ -34,26 +30,23 @@ import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week
import kotlin.coroutines.CoroutineContext
class LessonDetailsDialog(
val activity: AppCompatActivity,
val lesson: LessonFull,
val attendance: AttendanceFull? = null,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
private const val TAG = "LessonDetailsDialog"
}
activity: AppCompatActivity,
private val lesson: LessonFull,
private val attendance: AttendanceFull? = null,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<DialogLessonDetailsBinding>(activity, onShowListener, onDismissListener) {
private lateinit var app: App
private lateinit var b: DialogLessonDetailsBinding
private lateinit var dialog: AlertDialog
override val TAG = "LessonDetailsDialog"
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun getTitleRes(): Int? = null
override fun inflate(layoutInflater: LayoutInflater) =
DialogLessonDetailsBinding.inflate(layoutInflater)
override fun getPositiveButtonText() = R.string.close
override fun getNeutralButtonText() = R.string.add
private lateinit var adapter: EventListAdapter
private val manager
@ -61,44 +54,26 @@ class LessonDetailsDialog(
private val attendanceManager
get() = app.attendanceManager
init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
b = DialogLessonDetailsBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setView(b.root)
.setPositiveButton(R.string.close) { dialog, _ ->
dialog.dismiss()
}
.setNeutralButton(R.string.add, null)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.onClick {
EventManualDialog(
activity,
lesson.profileId,
defaultLesson = lesson,
onShowListener = onShowListener,
onDismissListener = onDismissListener
)
}
override suspend fun onNeutralClick(): Boolean {
EventManualDialog(
activity,
lesson.profileId,
defaultLesson = lesson,
onShowListener = onShowListener,
onDismissListener = onDismissListener
).show()
return NO_DISMISS
}
override suspend fun onShow() {
if (App.devMode)
b.lessonId.visibility = View.VISIBLE
update()
}}
private fun update() {
b.lesson = lesson
val lessonDate = lesson.displayDate ?: return
val lessonTime = lesson.displayStartTime ?: return
b.lessonDate.text = Week.getFullDayName(lessonDate.weekDay) + ", " + lessonDate.formattedString
b.lessonDate.text =
Week.getFullDayName(lessonDate.weekDay) + ", " + lessonDate.formattedString
b.annotationVisible = manager.getAnnotation(activity, lesson, b.annotation)
@ -110,13 +85,13 @@ class LessonDetailsDialog(
otherLessonDate = lesson.date
when {
lesson.date != lesson.oldDate -> b.shiftedText.setText(
R.string.timetable_lesson_shifted_other_day,
lesson.date?.stringY_m_d ?: "?",
lesson.startTime?.stringHM ?: "?"
R.string.timetable_lesson_shifted_other_day,
lesson.date?.stringY_m_d ?: "?",
lesson.startTime?.stringHM ?: "?"
)
lesson.startTime != lesson.oldStartTime -> b.shiftedText.setText(
R.string.timetable_lesson_shifted_same_day,
lesson.startTime?.stringHM ?: "?"
R.string.timetable_lesson_shifted_same_day,
lesson.startTime?.stringHM ?: "?"
)
else -> b.shiftedText.setText(R.string.timetable_lesson_shifted)
}
@ -125,13 +100,13 @@ class LessonDetailsDialog(
otherLessonDate = lesson.oldDate
when {
lesson.date != lesson.oldDate -> b.shiftedText.setText(
R.string.timetable_lesson_shifted_from_other_day,
lesson.oldDate?.stringY_m_d ?: "?",
lesson.oldStartTime?.stringHM ?: "?"
R.string.timetable_lesson_shifted_from_other_day,
lesson.oldDate?.stringY_m_d ?: "?",
lesson.oldStartTime?.stringHM ?: "?"
)
lesson.startTime != lesson.oldStartTime -> b.shiftedText.setText(
R.string.timetable_lesson_shifted_from_same_day,
lesson.oldStartTime?.stringHM ?: "?"
R.string.timetable_lesson_shifted_from_same_day,
lesson.oldStartTime?.stringHM ?: "?"
)
else -> b.shiftedText.setText(R.string.timetable_lesson_shifted_from)
}
@ -145,8 +120,7 @@ class LessonDetailsDialog(
}
activity.sendBroadcast(intent)
}
}
else {
} else {
b.shiftedLayout.visibility = View.GONE
}
@ -195,38 +169,47 @@ class LessonDetailsDialog(
true
}
b.attendanceDetails.onClick {
AttendanceDetailsDialog(activity, attendance, onShowListener, onDismissListener)
AttendanceDetailsDialog(
activity = activity,
attendance = attendance,
onShowListener = onShowListener,
onDismissListener = onDismissListener,
).show()
}
}
adapter = EventListAdapter(
activity,
showWeekDay = false,
showDate = false,
showType = true,
showTime = true,
showSubject = true,
markAsSeen = true,
onItemClick = {
EventDetailsDialog(
activity,
it,
onShowListener = onShowListener,
onDismissListener = onDismissListener
)
},
onEventEditClick = {
EventManualDialog(
activity,
it.profileId,
editingEvent = it,
onShowListener = onShowListener,
onDismissListener = onDismissListener
)
}
activity,
showWeekDay = false,
showDate = false,
showType = true,
showTime = true,
showSubject = true,
markAsSeen = true,
onItemClick = {
EventDetailsDialog(
activity,
it,
onShowListener = onShowListener,
onDismissListener = onDismissListener
).show()
},
onEventEditClick = {
EventManualDialog(
activity,
it.profileId,
editingEvent = it,
onShowListener = onShowListener,
onDismissListener = onDismissListener
).show()
}
)
app.db.eventDao().getAllByDateTime(lesson.profileId, lessonDate, lessonTime).observe(activity, Observer { events ->
app.db.eventDao().getAllByDateTime(
lesson.profileId,
lessonDate,
lessonTime
).observe(activity) { events ->
adapter.setAllItems(events)
if (b.eventsView.adapter == null) {
b.eventsView.adapter = adapter
@ -246,7 +229,7 @@ class LessonDetailsDialog(
b.eventsView.visibility = View.GONE
b.eventsNoData.visibility = View.VISIBLE
}
})
}
lesson.displayTeacherName?.let { name ->
lesson.displayTeacherId ?: return@let

View File

@ -225,7 +225,7 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
activity = activity,
lesson = lessonObj as LessonFull,
attendance = attendanceObj as AttendanceFull?
)
).show()
}
}

View File

@ -189,7 +189,7 @@ class TimetableFragment : Fragment(), CoroutineScope {
.withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline)
.withOnClickListener(View.OnClickListener {
activity.bottomSheet.close()
EventManualDialog(activity, App.profileId, defaultDate = pageSelection)
EventManualDialog(activity, App.profileId, defaultDate = pageSelection).show()
}),
BottomSheetPrimaryItem(true)
.withTitle(R.string.menu_generate_block_timetable)

View File

@ -65,7 +65,7 @@ class WebPushFragment : Fragment(), CoroutineScope {
QrScannerDialog(activity, {
b.tokenEditText.setText(it.crc32().toString(36).uppercase())
pairBrowser(browserId = it)
})
}).show()
}
}

View File

@ -71,7 +71,7 @@ class LessonDialogActivity : AppCompatActivity(), CoroutineScope {
if (shownDialogs.isEmpty())
finish()
}
)
).show()
}
}
}

View File

@ -87,11 +87,15 @@ class UserActionManager(val app: App) {
return
// show captcha dialog
// use passed onSuccess listener, else sync profile
LibrusCaptchaDialog(activity, onSuccess = onSuccess ?: { code ->
EdziennikTask.syncProfile(profileId, arguments = JsonObject(
LibrusCaptchaDialog(
activity = activity,
onSuccess = onSuccess ?: { code ->
EdziennikTask.syncProfile(profileId, arguments = JsonObject(
"recaptchaCode" to code,
"recaptchaTime" to System.currentTimeMillis()
)).enqueue(activity)
}, onFailure = onFailure)
)).enqueue(activity)
},
onFailure = onFailure
).show()
}
}