From bb7e927065446a9e4b7d6ded4736ec61485fdba3 Mon Sep 17 00:00:00 2001 From: Damian Czupryn <60961958+Daxxxis@users.noreply.github.com> Date: Wed, 29 Mar 2023 22:14:29 +0200 Subject: [PATCH] Migrate to material3 (#1660) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Borcz Co-authored-by: doteq Co-authored-by: Bartosz Bieniek --- .../res/mipmap-anydpi-v26/ic_launcher.xml | 2 +- app/src/main/AndroidManifest.xml | 2 +- .../repositories/PreferencesRepository.kt | 7 - .../data/repositories/TimetableRepository.kt | 6 + .../widgets/TimetableWidgetService.kt | 8 +- .../github/wulkanowy/ui/base/BaseActivity.kt | 15 +- .../wulkanowy/ui/base/BaseDialogFragment.kt | 19 + .../github/wulkanowy/ui/base/ErrorDialog.kt | 45 +-- .../github/wulkanowy/ui/base/ThemeManager.kt | 17 +- .../wulkanowy/ui/modules/Destination.kt | 8 + .../accountdetails/AccountDetailsFragment.kt | 3 +- .../account/accountedit/AccountEditDialog.kt | 18 +- .../accountquick/AccountQuickDialog.kt | 22 +- .../ui/modules/attendance/AttendanceDialog.kt | 24 +- .../modules/attendance/AttendanceFragment.kt | 6 +- .../ui/modules/conference/ConferenceDialog.kt | 24 +- .../ui/modules/dashboard/DashboardFragment.kt | 3 +- .../adapters/DashboardGradesAdapter.kt | 6 +- .../wulkanowy/ui/modules/exam/ExamDialog.kt | 24 +- .../wulkanowy/ui/modules/exam/ExamFragment.kt | 2 +- .../ui/modules/grade/GradeFragment.kt | 3 +- .../grade/details/GradeDetailsAdapter.kt | 8 +- .../grade/details/GradeDetailsDialog.kt | 40 ++- .../grade/summary/GradeSummaryFragment.kt | 5 +- .../ui/modules/homework/HomeworkFragment.kt | 2 +- .../modules/homework/add/HomeworkAddDialog.kt | 19 +- .../details/HomeworkDetailsAdapter.kt | 18 - .../homework/details/HomeworkDetailsDialog.kt | 32 +- .../details/HomeworkDetailsPresenter.kt | 8 - .../history/LuckyNumberHistoryFragment.kt | 2 +- .../LuckyNumberWidgetConfigureActivity.kt | 23 +- .../LuckyNumberWidgetConfigurePresenter.kt | 17 +- .../LuckyNumberWidgetConfigureView.kt | 2 - .../LuckyNumberWidgetProvider.kt | 196 +++++------ .../wulkanowy/ui/modules/main/MainActivity.kt | 38 +- .../ui/modules/main/MainPresenter.kt | 11 - .../wulkanowy/ui/modules/main/MainView.kt | 2 - .../mailboxchooser/MailboxChooserDialog.kt | 20 +- .../message/send/SendMessageActivity.kt | 27 +- .../modules/message/tab/MessageTabAdapter.kt | 15 +- .../token/MobileDeviceTokenDialog.kt | 19 +- .../wulkanowy/ui/modules/note/NoteDialog.kt | 24 +- .../notifications/NotificationsFragment.kt | 4 +- .../SchoolAnnouncementDialog.kt | 27 +- .../notifications/NotificationsFragment.kt | 10 +- .../ui/modules/settings/sync/SyncFragment.kt | 7 +- .../ui/modules/timetable/TimetableAdapter.kt | 8 +- .../ui/modules/timetable/TimetableDialog.kt | 27 +- .../ui/modules/timetable/TimetableFragment.kt | 2 +- .../additional/AdditionalLessonsFragment.kt | 12 +- .../add/AdditionalLessonAddDialog.kt | 20 +- .../completed/CompletedLessonDialog.kt | 26 +- .../completed/CompletedLessonsFragment.kt | 13 +- .../TimetableWidgetConfigureActivity.kt | 27 -- .../TimetableWidgetConfigurePresenter.kt | 19 +- .../TimetableWidgetConfigureView.kt | 2 - .../timetablewidget/TimetableWidgetFactory.kt | 328 ++++++++---------- .../TimetableWidgetProvider.kt | 161 ++++----- .../wulkanowy/utils/LifecycleAwareVariable.kt | 13 +- .../io/github/wulkanowy/utils/RefreshUtils.kt | 5 + .../drawable-night/background_header_note.xml | 5 - .../background_grade_details_rounded.xml | 5 + ...ackground_grade_details_weight_rounded.xml | 5 + ..._dark.xml => background_grade_rounded.xml} | 4 +- ...xml => background_grade_small_rounded.xml} | 6 +- .../res/drawable/background_header_note.xml | 2 +- ... background_luckynumber_widget_button.xml} | 4 +- .../background_luckynumber_widget_dark.xml | 6 - .../background_material_alert_dialog.xml | 26 ++ .../background_timetable_widget_avatar.xml | 6 + .../background_widget_header_timetable.xml | 7 - ...ackground_widget_header_timetable_dark.xml | 7 - .../background_widget_item_timetable.xml | 6 +- .../drawable/background_widget_timetable.xml | 6 +- app/src/main/res/drawable/ic_chevron_left.xml | 13 +- .../main/res/drawable/ic_chevron_right.xml | 13 +- app/src/main/res/drawable/ic_history.xml | 9 + .../main/res/drawable/ic_scale_balance.xml | 7 + .../res/drawable/ic_timetable_widget_swap.xml | 9 + .../main/res/drawable/ic_widget_chevron.png | Bin 130 -> 0 bytes .../main/res/drawable/ic_widget_chevron.xml | 9 + .../img_luckynumber_widget_preview.png | Bin 19550 -> 3702 bytes .../drawable/img_timetable_widget_preview.png | Bin 25538 -> 21111 bytes app/src/main/res/drawable/shape_badge.xml | 9 + .../layout-v31/widget_timetable_preview.xml | 96 +++++ app/src/main/res/layout/activity_main.xml | 19 +- .../main/res/layout/activity_send_message.xml | 24 +- .../main/res/layout/dialog_account_edit.xml | 31 +- .../main/res/layout/dialog_account_quick.xml | 2 +- .../main/res/layout/dialog_additional_add.xml | 115 +++--- .../main/res/layout/dialog_ads_consent.xml | 2 +- app/src/main/res/layout/dialog_attendance.xml | 58 ++-- app/src/main/res/layout/dialog_conference.xml | 68 ++-- app/src/main/res/layout/dialog_exam.xml | 68 ++-- app/src/main/res/layout/dialog_excuse.xml | 2 +- app/src/main/res/layout/dialog_grade.xml | 105 +++--- app/src/main/res/layout/dialog_homework.xml | 97 +++--- .../main/res/layout/dialog_homework_add.xml | 115 +++--- .../res/layout/dialog_lesson_completed.xml | 78 ++--- .../main/res/layout/dialog_mobile_device.xml | 19 +- app/src/main/res/layout/dialog_note.xml | 71 ++-- .../res/layout/dialog_school_announcement.xml | 53 ++- app/src/main/res/layout/dialog_timetable.xml | 95 ++--- .../res/layout/fragment_account_details.xml | 4 +- .../main/res/layout/fragment_attendance.xml | 3 +- .../layout/fragment_attendance_summary.xml | 2 +- .../main/res/layout/fragment_conference.xml | 2 +- .../main/res/layout/fragment_contributor.xml | 2 +- .../main/res/layout/fragment_dashboard.xml | 2 +- app/src/main/res/layout/fragment_exam.xml | 2 +- app/src/main/res/layout/fragment_grade.xml | 3 +- .../res/layout/fragment_grade_details.xml | 2 +- .../res/layout/fragment_grade_statistics.xml | 2 +- .../res/layout/fragment_grade_summary.xml | 2 +- app/src/main/res/layout/fragment_homework.xml | 4 +- .../res/layout/fragment_login_advanced.xml | 14 +- .../main/res/layout/fragment_login_form.xml | 16 +- .../res/layout/fragment_login_recover.xml | 6 +- .../main/res/layout/fragment_login_symbol.xml | 8 +- .../main/res/layout/fragment_logviewer.xml | 2 +- .../main/res/layout/fragment_lucky_number.xml | 4 +- .../layout/fragment_lucky_number_history.xml | 2 +- app/src/main/res/layout/fragment_message.xml | 2 +- .../res/layout/fragment_message_preview.xml | 2 +- .../main/res/layout/fragment_message_tab.xml | 2 +- .../res/layout/fragment_mobile_device.xml | 3 +- app/src/main/res/layout/fragment_note.xml | 2 +- .../layout/fragment_notifications_center.xml | 2 +- app/src/main/res/layout/fragment_school.xml | 2 +- .../layout/fragment_school_announcement.xml | 2 +- .../main/res/layout/fragment_student_info.xml | 2 +- app/src/main/res/layout/fragment_teacher.xml | 2 +- .../main/res/layout/fragment_timetable.xml | 2 +- .../layout/fragment_timetable_additional.xml | 12 +- .../layout/fragment_timetable_completed.xml | 2 +- .../res/layout/item_dashboard_account.xml | 3 +- .../layout/item_dashboard_admin_message.xml | 7 +- .../layout/item_dashboard_announcements.xml | 5 +- .../res/layout/item_dashboard_conferences.xml | 5 +- .../main/res/layout/item_dashboard_exams.xml | 5 +- .../main/res/layout/item_dashboard_grades.xml | 9 +- .../res/layout/item_dashboard_homework.xml | 5 +- .../item_dashboard_horizontal_group.xml | 4 - .../res/layout/item_dashboard_lessons.xml | 8 +- .../main/res/layout/item_grade_details.xml | 5 +- .../layout/item_homework_dialog_details.xml | 38 +- .../layout/item_login_student_select_help.xml | 7 +- .../main/res/layout/item_message_chips.xml | 16 +- .../res/layout/item_notifications_center.xml | 5 +- .../main/res/layout/item_widget_timetable.xml | 173 +++++---- .../res/layout/item_widget_timetable_dark.xml | 114 ------ .../layout/item_widget_timetable_footer.xml | 12 + .../layout/item_widget_timetable_small.xml | 98 ------ .../item_widget_timetable_small_dark.xml | 97 ------ .../res/layout/layout_preference_switch.xml | 5 + .../res/layout/subitem_dashboard_grades.xml | 4 +- .../layout/subitem_dashboard_small_grade.xml | 3 +- .../main/res/layout/widget_luckynumber.xml | 83 ++--- .../res/layout/widget_luckynumber_dark.xml | 67 ---- app/src/main/res/layout/widget_timetable.xml | 168 +++++---- .../main/res/layout/widget_timetable_dark.xml | 99 ------ .../main/res/menu/action_menu_dashboard.xml | 6 +- .../res/mipmap-anydpi-v26/ic_launcher.xml | 2 +- app/src/main/res/values-night-v31/styles.xml | 65 ++++ app/src/main/res/values-night/styles.xml | 57 ++- app/src/main/res/values-v23/styles.xml | 9 +- app/src/main/res/values-v26/styles.xml | 8 - app/src/main/res/values-v27/styles.xml | 16 + app/src/main/res/values-v28/styles.xml | 9 - app/src/main/res/values-v29/styles.xml | 9 - app/src/main/res/values-v31/styles.xml | 60 ++++ app/src/main/res/values/attrs.xml | 1 + app/src/main/res/values/colors.xml | 53 ++- .../main/res/values/preferences_defaults.xml | 1 - app/src/main/res/values/preferences_keys.xml | 1 - app/src/main/res/values/strings.xml | 83 +++++ app/src/main/res/values/styles.xml | 49 ++- .../res/xml/provider_widget_lucky_number.xml | 8 +- .../res/xml/provider_widget_timetable.xml | 5 +- 179 files changed, 2019 insertions(+), 2372 deletions(-) delete mode 100644 app/src/main/res/drawable-night/background_header_note.xml create mode 100644 app/src/main/res/drawable/background_grade_details_rounded.xml create mode 100644 app/src/main/res/drawable/background_grade_details_weight_rounded.xml rename app/src/main/res/drawable/{background_widget_timetable_dark.xml => background_grade_rounded.xml} (74%) rename app/src/main/res/drawable/{background_widget_item_timetable_dark.xml => background_grade_small_rounded.xml} (51%) rename app/src/main/res/drawable/{background_luckynumber_widget.xml => background_luckynumber_widget_button.xml} (65%) delete mode 100644 app/src/main/res/drawable/background_luckynumber_widget_dark.xml create mode 100644 app/src/main/res/drawable/background_material_alert_dialog.xml create mode 100644 app/src/main/res/drawable/background_timetable_widget_avatar.xml delete mode 100644 app/src/main/res/drawable/background_widget_header_timetable.xml delete mode 100644 app/src/main/res/drawable/background_widget_header_timetable_dark.xml create mode 100644 app/src/main/res/drawable/ic_history.xml create mode 100644 app/src/main/res/drawable/ic_scale_balance.xml create mode 100644 app/src/main/res/drawable/ic_timetable_widget_swap.xml delete mode 100644 app/src/main/res/drawable/ic_widget_chevron.png create mode 100644 app/src/main/res/drawable/ic_widget_chevron.xml create mode 100644 app/src/main/res/drawable/shape_badge.xml create mode 100644 app/src/main/res/layout-v31/widget_timetable_preview.xml delete mode 100644 app/src/main/res/layout/item_widget_timetable_dark.xml create mode 100644 app/src/main/res/layout/item_widget_timetable_footer.xml delete mode 100644 app/src/main/res/layout/item_widget_timetable_small.xml delete mode 100644 app/src/main/res/layout/item_widget_timetable_small_dark.xml create mode 100644 app/src/main/res/layout/layout_preference_switch.xml delete mode 100644 app/src/main/res/layout/widget_luckynumber_dark.xml delete mode 100644 app/src/main/res/layout/widget_timetable_dark.xml create mode 100644 app/src/main/res/values-night-v31/styles.xml delete mode 100644 app/src/main/res/values-v26/styles.xml create mode 100644 app/src/main/res/values-v27/styles.xml delete mode 100644 app/src/main/res/values-v28/styles.xml delete mode 100644 app/src/main/res/values-v29/styles.xml create mode 100644 app/src/main/res/values-v31/styles.xml diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml index b7b756b9..9c21d49d 100644 --- a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,6 @@ - + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3773093b..174c9a1f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -72,7 +72,7 @@ android:name=".ui.modules.message.send.SendMessageActivity" android:configChanges="orientation|screenSize" android:label="@string/send_message_title" - android:theme="@style/WulkanowyTheme.MessageSend" + android:theme="@style/WulkanowyTheme.NoActionBar" android:windowSoftInputMode="adjustResize" /> ) = timetableAdditionalDb.insertAll(additionalList) diff --git a/app/src/main/java/io/github/wulkanowy/services/widgets/TimetableWidgetService.kt b/app/src/main/java/io/github/wulkanowy/services/widgets/TimetableWidgetService.kt index 45cd2b04..d48556fa 100644 --- a/app/src/main/java/io/github/wulkanowy/services/widgets/TimetableWidgetService.kt +++ b/app/src/main/java/io/github/wulkanowy/services/widgets/TimetableWidgetService.kt @@ -4,7 +4,6 @@ import android.content.Intent import android.widget.RemoteViewsService import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.SharedPrefProvider -import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.TimetableRepository @@ -24,14 +23,13 @@ class TimetableWidgetService : RemoteViewsService() { @Inject lateinit var semesterRepo: SemesterRepository - @Inject - lateinit var prefRepository: PreferencesRepository - @Inject lateinit var sharedPref: SharedPrefProvider override fun onGetViewFactory(intent: Intent?): RemoteViewsFactory { Timber.d("TimetableWidgetFactory created") - return TimetableWidgetFactory(timetableRepo, studentRepo, semesterRepo, prefRepository, sharedPref, applicationContext, intent) + return TimetableWidgetFactory( + timetableRepo, studentRepo, semesterRepo, sharedPref, applicationContext, intent + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt index 075557a5..7914df81 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt @@ -4,9 +4,9 @@ import android.app.ActivityManager import android.os.Bundle import android.view.View import android.widget.Toast -import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.viewbinding.ViewBinding +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar.LENGTH_LONG import io.github.wulkanowy.R @@ -30,6 +30,8 @@ abstract class BaseActivity, VB : ViewBinding> : protected var messageContainer: View? = null + protected var messageAnchor: View? = null + abstract var presenter: T override fun onCreate(savedInstanceState: Bundle?) { @@ -48,6 +50,7 @@ abstract class BaseActivity, VB : ViewBinding> : if (messageContainer != null) { Snackbar.make(messageContainer!!, text, LENGTH_LONG) .setAction(R.string.all_details) { showErrorDetailsDialog(error) } + .apply { messageAnchor?.let { anchorView = it } } .show() } else showMessage(text) } @@ -57,12 +60,15 @@ abstract class BaseActivity, VB : ViewBinding> : } override fun showMessage(text: String) { - if (messageContainer != null) Snackbar.make(messageContainer!!, text, LENGTH_LONG).show() - else Toast.makeText(this, text, Toast.LENGTH_LONG).show() + if (messageContainer != null) { + Snackbar.make(messageContainer!!, text, LENGTH_LONG) + .apply { messageAnchor?.let { anchorView = it } } + .show() + } else Toast.makeText(this, text, Toast.LENGTH_LONG).show() } override fun showExpiredDialog() { - AlertDialog.Builder(this) + MaterialAlertDialogBuilder(this) .setTitle(R.string.main_session_expired) .setMessage(R.string.main_session_relogin) .setPositiveButton(R.string.main_log_in) { _, _ -> presenter.onExpiredLoginSelected() } @@ -74,6 +80,7 @@ abstract class BaseActivity, VB : ViewBinding> : messageContainer?.let { Snackbar.make(it, R.string.error_password_change_required, LENGTH_LONG) .setAction(R.string.all_change) { openInternetBrowser(redirectUrl) } + .apply { messageAnchor?.let { anchorView = it } } .show() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt index 25a53395..561d181a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt @@ -1,8 +1,14 @@ package io.github.wulkanowy.ui.base +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import android.widget.Toast +import androidx.annotation.CallSuper import androidx.fragment.app.DialogFragment import androidx.viewbinding.ViewBinding +import com.google.android.material.elevation.SurfaceColors import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.lifecycleAwareVariable import javax.inject.Inject @@ -38,6 +44,19 @@ abstract class BaseDialogFragment : DialogFragment(), BaseView ErrorDialog.newInstance(error).show(childFragmentManager, error.toString()) } + @CallSuper + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + view.setBackgroundColor(SurfaceColors.SURFACE_3.getColor(requireContext())) + } + + @CallSuper + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ) = binding.root + override fun onResume() { super.onResume() analyticsHelper.setCurrentScreen(requireActivity(), this::class.simpleName) diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt index fe0e6469..679d904a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorDialog.kt @@ -4,13 +4,13 @@ import android.app.Dialog import android.content.ClipData import android.content.ClipboardManager import android.os.Bundle +import android.view.View import android.widget.Toast import android.widget.Toast.LENGTH_LONG import androidx.appcompat.app.AlertDialog import androidx.core.content.getSystemService import androidx.core.os.bundleOf import androidx.core.view.isGone -import androidx.fragment.app.DialogFragment import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -20,7 +20,7 @@ import io.github.wulkanowy.utils.* import javax.inject.Inject @AndroidEntryPoint -class ErrorDialog : DialogFragment() { +class ErrorDialog : BaseDialogFragment() { @Inject lateinit var appInfo: AppInfo @@ -28,6 +28,8 @@ class ErrorDialog : DialogFragment() { @Inject lateinit var preferencesRepository: PreferencesRepository + private lateinit var error: Throwable + companion object { private const val ARGUMENT_KEY = "error" @@ -36,32 +38,31 @@ class ErrorDialog : DialogFragment() { } } - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val error = requireArguments().serializable(ARGUMENT_KEY) - - val binding = DialogErrorBinding.inflate(layoutInflater) - binding.bindErrorDetails(error) - - return getAlertDialog(binding, error).apply { - enableReportButtonIfErrorIsReportable(error) - } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + error = requireArguments().serializable(ARGUMENT_KEY) } - private fun getAlertDialog(binding: DialogErrorBinding, error: Throwable): AlertDialog { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return MaterialAlertDialogBuilder(requireContext()).apply { val errorStacktrace = error.stackTraceToString() setTitle(R.string.all_details) - setView(binding.root) + setView(DialogErrorBinding.inflate(layoutInflater).apply { binding = this }.root) setNeutralButton(R.string.about_feedback) { _, _ -> openConfirmDialog { openEmailClient(errorStacktrace) } } setNegativeButton(android.R.string.cancel) { _, _ -> } setPositiveButton(android.R.string.copy) { _, _ -> copyErrorToClipboard(errorStacktrace) } - }.create() + }.create().apply { + setOnShowListener { + getButton(AlertDialog.BUTTON_NEUTRAL).isEnabled = error.isShouldBeReported() + } + } } - private fun DialogErrorBinding.bindErrorDetails(error: Throwable) { - return with(this) { + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + with(binding) { errorDialogHumanizedMessage.text = resources.getErrorString(error) errorDialogErrorMessage.text = error.localizedMessage errorDialogErrorMessage.isGone = error.localizedMessage.isNullOrBlank() @@ -70,12 +71,6 @@ class ErrorDialog : DialogFragment() { } } - private fun AlertDialog.enableReportButtonIfErrorIsReportable(error: Throwable) { - setOnShowListener { - getButton(AlertDialog.BUTTON_NEUTRAL).isEnabled = error.isShouldBeReported() - } - } - private fun copyErrorToClipboard(errorStacktrace: String) { val clip = ClipData.newPlainText("Error details", errorStacktrace) requireActivity().getSystemService()?.setPrimaryClip(clip) @@ -83,7 +78,7 @@ class ErrorDialog : DialogFragment() { } private fun openConfirmDialog(callback: () -> Unit) { - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.dialog_error_check_update) .setMessage(R.string.dialog_error_check_update_message) .setNeutralButton(R.string.about_feedback) { _, _ -> callback() } @@ -113,8 +108,4 @@ class ErrorDialog : DialogFragment() { } ) } - - private fun showMessage(text: String) { - Toast.makeText(requireContext(), text, LENGTH_LONG).show() - } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt index e1c23457..f42f315c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ThemeManager.kt @@ -6,15 +6,14 @@ import android.content.pm.PackageManager.GET_ACTIVITIES import android.os.Build import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate -import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM -import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO -import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES +import com.google.android.material.color.DynamicColors import io.github.wulkanowy.R import io.github.wulkanowy.data.enums.AppTheme import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.ui.modules.login.LoginActivity +import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity import io.github.wulkanowy.ui.modules.main.MainActivity -import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity +import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureActivity import javax.inject.Inject import javax.inject.Singleton @@ -28,18 +27,19 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer when (activity) { is MainActivity -> activity.setTheme(R.style.WulkanowyTheme_Black) is LoginActivity -> activity.setTheme(R.style.WulkanowyTheme_Login_Black) - is SendMessageActivity -> activity.setTheme(R.style.WulkanowyTheme_MessageSend_Black) } } + } else if (activity is TimetableWidgetConfigureActivity || activity is LuckyNumberWidgetConfigureActivity) { + DynamicColors.applyToActivityIfAvailable(activity) } } fun applyDefaultTheme() { AppCompatDelegate.setDefaultNightMode( when (preferencesRepository.appTheme) { - AppTheme.LIGHT -> MODE_NIGHT_NO - AppTheme.DARK, AppTheme.BLACK -> MODE_NIGHT_YES - AppTheme.SYSTEM -> MODE_NIGHT_FOLLOW_SYSTEM + AppTheme.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO + AppTheme.DARK, AppTheme.BLACK -> AppCompatDelegate.MODE_NIGHT_YES + AppTheme.SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM } ) } @@ -52,7 +52,6 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer .let { it == R.style.WulkanowyTheme_Black || it == R.style.WulkanowyTheme_NoActionBar || it == R.style.WulkanowyTheme_Login || it == R.style.WulkanowyTheme_Login_Black - || it == R.style.WulkanowyTheme_MessageSend || it == R.style.WulkanowyTheme_MessageSend_Black } @Suppress("DEPRECATION") diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt index 958be5a7..f0969fac 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt @@ -9,6 +9,7 @@ import io.github.wulkanowy.ui.modules.exam.ExamFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.homework.HomeworkFragment import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment +import io.github.wulkanowy.ui.modules.luckynumber.history.LuckyNumberHistoryFragment import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment import io.github.wulkanowy.ui.modules.more.MoreFragment @@ -43,6 +44,7 @@ sealed class Destination { SCHOOL_ANNOUNCEMENT(SchoolAnnouncement), SCHOOL_AND_TEACHERS(SchoolAndTeachers), LUCKY_NUMBER(LuckyNumber), + LUCKY_NUMBER_HISTORY(LuckyNumberHistory), MORE(More), MESSAGE(Message), MOBILE_DEVICE(MobileDevice), @@ -118,6 +120,12 @@ sealed class Destination { override val destinationFragment get() = LuckyNumberFragment.newInstance() } + @Serializable + object LuckyNumberHistory : Destination() { + override val destinationType get() = Type.LUCKY_NUMBER_HISTORY + override val destinationFragment get() = LuckyNumberHistoryFragment.newInstance() + } + @Serializable object More : Destination() { override val destinationType get() = Type.MORE diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt index 41b97b07..d6bc6154 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountdetails/AccountDetailsFragment.kt @@ -9,6 +9,7 @@ import androidx.appcompat.app.AlertDialog import androidx.core.os.bundleOf import androidx.core.view.get import androidx.core.view.isVisible +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Student @@ -114,7 +115,7 @@ class AccountDetailsFragment : override fun showLogoutConfirmDialog() { context?.let { - AlertDialog.Builder(it) + MaterialAlertDialogBuilder(it) .setTitle(R.string.account_logout_student) .setMessage(R.string.account_confirm) .setPositiveButton(R.string.account_logout) { _, _ -> presenter.onLogoutConfirm() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt index 6e2bc8c4..4229579c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountedit/AccountEditDialog.kt @@ -1,11 +1,11 @@ package io.github.wulkanowy.ui.modules.account.accountedit +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.os.bundleOf import androidx.recyclerview.widget.GridLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.databinding.DialogAccountEditBinding @@ -31,16 +31,12 @@ class AccountEditDialog : BaseDialogFragment(), Accoun } } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) - } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View = DialogAccountEditBinding.inflate(inflater).apply { binding = this }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogAccountEditBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt index d23978f5..2d2dccec 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/account/accountquick/AccountQuickDialog.kt @@ -1,11 +1,11 @@ package io.github.wulkanowy.ui.modules.account.accountquick +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.os.bundleOf import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.databinding.DialogAccountQuickBinding @@ -36,19 +36,17 @@ class AccountQuickDialog : BaseDialogFragment(), Acco } } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView( + DialogAccountQuickBinding.inflate(layoutInflater) + .apply { binding = this }.root + ) + .create() } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogAccountQuickBinding.inflate(inflater).apply { binding = this }.root - - @Suppress("UNCHECKED_CAST") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) val studentsWithSemesters = requireArguments() .serializable>(STUDENTS_ARGUMENT_KEY).toList() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt index eab24f91..c0026bee 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt @@ -1,21 +1,20 @@ package io.github.wulkanowy.ui.modules.attendance +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.os.bundleOf -import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.databinding.DialogAttendanceBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.descriptionRes -import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.toFormattedString -class AttendanceDialog : DialogFragment() { - - private var binding: DialogAttendanceBinding by lifecycleAwareVariable() +@AndroidEntryPoint +class AttendanceDialog : BaseDialogFragment() { private lateinit var attendance: Attendance @@ -30,15 +29,14 @@ class AttendanceDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) attendance = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogAttendanceBinding.inflate(inflater).apply { binding = this }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogAttendanceBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt index 21f30b04..a73c2606 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt @@ -4,10 +4,10 @@ import android.content.DialogInterface.BUTTON_POSITIVE import android.os.Bundle import android.view.* import android.view.View.* -import androidx.appcompat.app.AlertDialog import androidx.appcompat.view.ActionMode import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Attendance @@ -124,7 +124,7 @@ class AttendanceFragment : BaseFragment(R.layout.frag attendanceExcuseButton.setOnClickListener { presenter.onExcuseButtonClick() } - attendanceNavContainer.elevation = requireContext().dpToPx(8f) + attendanceNavContainer.elevation = requireContext().dpToPx(3f) } } @@ -228,7 +228,7 @@ class AttendanceFragment : BaseFragment(R.layout.frag override fun showExcuseDialog() { val dialogBinding = DialogExcuseBinding.inflate(LayoutInflater.from(context)) - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.attendance_excuse_title) .setView(dialogBinding.root) .setNegativeButton(android.R.string.cancel) { _, _ -> } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt index 7834b6e8..c532377e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/conference/ConferenceDialog.kt @@ -1,21 +1,20 @@ package io.github.wulkanowy.ui.modules.conference +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.os.bundleOf import androidx.core.view.isVisible -import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.databinding.DialogConferenceBinding -import io.github.wulkanowy.utils.lifecycleAwareVariable +import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.toFormattedString -class ConferenceDialog : DialogFragment() { - - private var binding: DialogConferenceBinding by lifecycleAwareVariable() +@AndroidEntryPoint +class ConferenceDialog : BaseDialogFragment() { private lateinit var conference: Conference @@ -30,15 +29,14 @@ class ConferenceDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) conference = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogConferenceBinding.inflate(inflater).also { binding = it }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogConferenceBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt index cd66d6c2..ce17c763 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt @@ -11,6 +11,7 @@ import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.databinding.FragmentDashboardBinding @@ -148,7 +149,7 @@ class DashboardFragment : BaseFragment(R.layout.fragme val values = requireContext().resources.getStringArray(R.array.dashboard_tile_values) val selectedItemsState = values.map { value -> selectedItems.any { it.name == value } } - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.pref_dashboard_appearance_tiles_title) .setMultiChoiceItems(entries, selectedItemsState.toBooleanArray()) { _, _, _ -> } .setPositiveButton(android.R.string.ok) { dialog, _ -> diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardGradesAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardGradesAdapter.kt index d00df9d4..d821de53 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardGradesAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/adapters/DashboardGradesAdapter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.dashboard.adapters +import android.content.res.ColorStateList import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView @@ -8,6 +9,7 @@ import io.github.wulkanowy.data.enums.GradeColorTheme import io.github.wulkanowy.databinding.SubitemDashboardGradesBinding import io.github.wulkanowy.databinding.SubitemDashboardSmallGradeBinding import io.github.wulkanowy.utils.getBackgroundColor +import io.github.wulkanowy.utils.getCompatColor class DashboardGradesAdapter : RecyclerView.Adapter() { @@ -37,7 +39,9 @@ class DashboardGradesAdapter : RecyclerView.Adapter() { private lateinit var exam: Exam @@ -32,15 +31,14 @@ class ExamDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) exam = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogExamBinding.inflate(inflater).apply { binding = this }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogExamBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt index 3d42bd00..0123e234 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt @@ -62,7 +62,7 @@ class ExamFragment : BaseFragment(R.layout.fragment_exam), examPreviousButton.setOnClickListener { presenter.onPreviousWeek() } examNextButton.setOnClickListener { presenter.onNextWeek() } - examNavContainer.elevation = requireContext().dpToPx(8f) + examNavContainer.elevation = requireContext().dpToPx(3f) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt index 15df47a1..7ce07eb6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/GradeFragment.kt @@ -8,6 +8,7 @@ import android.view.View import android.view.View.INVISIBLE import android.view.View.VISIBLE import androidx.appcompat.app.AlertDialog +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -141,7 +142,7 @@ class GradeFragment : BaseFragment(R.layout.fragment_grade val choices = semesters.map { getString(R.string.grade_semester, it.semesterName) } .toTypedArray() - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setSingleChoiceItems(choices, selectedIndex) { dialog, which -> presenter.onSemesterSelected(which) dialog.dismiss() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt index e5c3bb63..15b5db03 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsAdapter.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.ui.modules.grade.details import android.annotation.SuppressLint +import android.content.res.ColorStateList import android.content.res.Resources import android.view.LayoutInflater import android.view.View @@ -17,9 +18,10 @@ import io.github.wulkanowy.databinding.HeaderGradeDetailsBinding import io.github.wulkanowy.databinding.ItemGradeDetailsBinding import io.github.wulkanowy.ui.base.BaseExpandableAdapter import io.github.wulkanowy.utils.getBackgroundColor +import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.toFormattedString import timber.log.Timber -import java.util.BitSet +import java.util.* import javax.inject.Inject class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter() { @@ -203,7 +205,9 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter grade.description diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt index a1ef2ec5..39f72f8b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/details/GradeDetailsDialog.kt @@ -1,22 +1,23 @@ package io.github.wulkanowy.ui.modules.grade.details +import android.app.Dialog +import android.content.res.ColorStateList import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE -import android.view.ViewGroup +import androidx.core.content.ContextCompat import androidx.core.os.bundleOf -import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.enums.GradeColorTheme import io.github.wulkanowy.databinding.DialogGradeBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.* - -class GradeDetailsDialog : DialogFragment() { - - private var binding: DialogGradeBinding by lifecycleAwareVariable() +@AndroidEntryPoint +class GradeDetailsDialog : BaseDialogFragment() { private lateinit var grade: Grade @@ -38,16 +39,15 @@ class GradeDetailsDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) grade = requireArguments().serializable(ARGUMENT_KEY) gradeColorTheme = requireArguments().serializable(COLOR_THEME_KEY) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogGradeBinding.inflate(inflater).apply { binding = this }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogGradeBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -55,10 +55,9 @@ class GradeDetailsDialog : DialogFragment() { with(binding) { gradeDialogSubject.text = grade.subject - gradeDialogColorAndWeightValue.run { - text = context.getString(R.string.grade_weight_value, grade.weight) - setBackgroundResource(grade.getGradeColor()) - } + gradeDialogWeightValue.text = grade.weight + gradeDialogWeightLayout.backgroundTintList = + ColorStateList.valueOf(requireContext().getCompatColor(grade.getGradeColor())) gradeDialogDateValue.text = grade.date.toFormattedString() gradeDialogColorValue.text = getString(grade.colorStringId) @@ -72,7 +71,12 @@ class GradeDetailsDialog : DialogFragment() { gradeDialogValue.run { text = grade.entry - setBackgroundResource(grade.getBackgroundColor(gradeColorTheme)) + backgroundTintList = ColorStateList.valueOf( + ContextCompat.getColor( + requireContext(), + grade.getBackgroundColor(gradeColorTheme) + ) + ) } gradeDialogTeacherValue.text = grade.teacher.ifBlank { getString(R.string.all_no_data) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt index 3810902f..abd0b13c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryFragment.kt @@ -7,6 +7,7 @@ import android.view.View.INVISIBLE import android.view.View.VISIBLE import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.GradeSummary @@ -118,7 +119,7 @@ class GradeSummaryFragment : } override fun showCalculatedAverageHelpDialog() { - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.grade_summary_calculated_average_help_dialog_title) .setMessage(R.string.grade_summary_calculated_average_help_dialog_message) .setPositiveButton(R.string.all_close) { _, _ -> } @@ -126,7 +127,7 @@ class GradeSummaryFragment : } override fun showFinalAverageHelpDialog() { - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.grade_summary_final_average_help_dialog_title) .setMessage(R.string.grade_summary_final_average_help_dialog_message) .setPositiveButton(R.string.all_close) { _, _ -> } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt index 9d5130e4..0381acf3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/HomeworkFragment.kt @@ -67,7 +67,7 @@ class HomeworkFragment : BaseFragment(R.layout.fragment openAddHomeworkButton.setOnClickListener { presenter.onHomeworkAddButtonClicked() } - homeworkNavContainer.elevation = requireContext().dpToPx(8f) + homeworkNavContainer.elevation = requireContext().dpToPx(3f) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt index c2aff2b1..c51370ea 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/add/HomeworkAddDialog.kt @@ -1,10 +1,10 @@ package io.github.wulkanowy.ui.modules.homework.add +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.widget.doOnTextChanged +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.databinding.DialogHomeworkAddBinding @@ -21,20 +21,15 @@ class HomeworkAddDialog : BaseDialogFragment(), Homewo @Inject lateinit var presenter: HomeworkAddPresenter - // todo: move it to presenter + //todo: move it to presenter private var date: LocalDate? = null - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogHomeworkAddBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogHomeworkAddBinding.inflate(inflater).apply { binding = this }.root - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) presenter.onAttachView(this) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt index e03707a5..1ad2a0e3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsAdapter.kt @@ -31,14 +31,8 @@ class HomeworkDetailsAdapter @Inject constructor() : attachments = value?.attachments.orEmpty() } - var isHomeworkFullscreen = false - var onAttachmentClickListener: (url: String) -> Unit = {} - var onFullScreenClickListener = {} - - var onFullScreenExitClickListener = {} - var onDeleteClickListener: (homework: Homework) -> Unit = {} override fun getItemCount() = 1 + if (attachments.isNotEmpty()) attachments.size + 1 else 0 @@ -82,18 +76,6 @@ class HomeworkDetailsAdapter @Inject constructor() : homeworkDialogTeacher.text = homework?.teacher.ifNullOrBlank { noDataString } homeworkDialogContent.text = homework?.content.ifNullOrBlank { noDataString } homeworkDialogDelete.visibility = if (homework?.isAddedByUser == true) VISIBLE else GONE - homeworkDialogFullScreen.visibility = if (isHomeworkFullscreen) GONE else VISIBLE - homeworkDialogFullScreenExit.visibility = if (isHomeworkFullscreen) VISIBLE else GONE - homeworkDialogFullScreen.setOnClickListener { - homeworkDialogFullScreen.visibility = GONE - homeworkDialogFullScreenExit.visibility = VISIBLE - onFullScreenClickListener() - } - homeworkDialogFullScreenExit.setOnClickListener { - homeworkDialogFullScreen.visibility = VISIBLE - homeworkDialogFullScreenExit.visibility = GONE - onFullScreenExitClickListener() - } homeworkDialogDelete.setOnClickListener { onDeleteClickListener(homework!!) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt index 5e2cc65d..1f9bc881 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsDialog.kt @@ -1,14 +1,12 @@ package io.github.wulkanowy.ui.modules.homework.details import android.annotation.SuppressLint +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import androidx.core.os.bundleOf import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Homework @@ -43,15 +41,14 @@ class HomeworkDetailsDialog : BaseDialogFragment(), Homew override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) homework = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogHomeworkBinding.inflate(inflater).apply { binding = this }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogHomeworkBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -67,26 +64,11 @@ class HomeworkDetailsDialog : BaseDialogFragment(), Homew homeworkDialogClose.setOnClickListener { dismiss() } } - if (presenter.isHomeworkFullscreen) { - dialog?.window?.setLayout(MATCH_PARENT, MATCH_PARENT) - } else { - dialog?.window?.setLayout(WRAP_CONTENT, WRAP_CONTENT) - } - with(binding.homeworkDialogRecycler) { layoutManager = LinearLayoutManager(context) adapter = detailsAdapter.apply { onAttachmentClickListener = { context.openInternetBrowser(it, ::showMessage) } - onFullScreenClickListener = { - dialog?.window?.setLayout(MATCH_PARENT, MATCH_PARENT) - presenter.isHomeworkFullscreen = true - } - onFullScreenExitClickListener = { - dialog?.window?.setLayout(WRAP_CONTENT, WRAP_CONTENT) - presenter.isHomeworkFullscreen = false - } onDeleteClickListener = { homework -> presenter.deleteHomework(homework) } - isHomeworkFullscreen = presenter.isHomeworkFullscreen homework = this@HomeworkDetailsDialog.homework } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt index e76df6bd..84933f06 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/homework/details/HomeworkDetailsPresenter.kt @@ -5,7 +5,6 @@ import io.github.wulkanowy.data.logResourceStatus import io.github.wulkanowy.data.onResourceError import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.repositories.HomeworkRepository -import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter @@ -19,15 +18,8 @@ class HomeworkDetailsPresenter @Inject constructor( studentRepository: StudentRepository, private val homeworkRepository: HomeworkRepository, private val analytics: AnalyticsHelper, - private val preferencesRepository: PreferencesRepository ) : BasePresenter(errorHandler, studentRepository) { - var isHomeworkFullscreen - get() = preferencesRepository.isHomeworkFullscreen - set(value) { - preferencesRepository.isHomeworkFullscreen = value - } - override fun onAttachView(view: HomeworkDetailsView) { super.onAttachView(view) view.initView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt index 53f06cac..a78ce5dd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumber/history/LuckyNumberHistoryFragment.kt @@ -61,7 +61,7 @@ class LuckyNumberHistoryFragment : luckyNumberHistoryPreviousButton.setOnClickListener { presenter.onPreviousWeek() } luckyNumberHistoryNextButton.setOnClickListener { presenter.onNextWeek() } - luckyNumberHistoryNavContainer.elevation = requireContext().dpToPx(8f) + luckyNumberHistoryNavContainer.elevation = requireContext().dpToPx(3f) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt index 024beff8..a2d23e54 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureActivity.kt @@ -1,16 +1,12 @@ package io.github.wulkanowy.ui.modules.luckynumberwidget -import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE -import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID -import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS +import android.appwidget.AppWidgetManager.* import android.content.Intent -import android.os.Build import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint -import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.databinding.ActivityWidgetConfigureBinding import io.github.wulkanowy.ui.base.BaseActivity @@ -41,7 +37,6 @@ class LuckyNumberWidgetConfigureActivity : setContentView( ActivityWidgetConfigureBinding.inflate(layoutInflater).apply { binding = this }.root ) - intent.extras.let { presenter.onAttachView(this, it?.getInt(EXTRA_APPWIDGET_ID)) } @@ -56,22 +51,6 @@ class LuckyNumberWidgetConfigureActivity : configureAdapter.onClickListener = presenter::onItemSelect } - override fun showThemeDialog() { - var items = arrayOf( - getString(R.string.widget_timetable_theme_light), - getString(R.string.widget_timetable_theme_dark) - ) - if (appInfo.systemVersion >= Build.VERSION_CODES.Q) items += (getString(R.string.widget_timetable_theme_system)) - - dialog = AlertDialog.Builder(this, R.style.WulkanowyTheme_WidgetAccountSwitcher) - .setTitle(R.string.widget_timetable_theme_title) - .setOnDismissListener { presenter.onDismissThemeView() } - .setSingleChoiceItems(items, -1) { _, which -> - presenter.onThemeSelect(which) - } - .show() - } - override fun updateData(data: List, selectedStudentId: Long) { with(configureAdapter) { selectedId = selectedStudentId diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt index cac648da..7e53dad0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigurePresenter.kt @@ -8,7 +8,6 @@ import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider.Companion.getStudentWidgetKey -import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider.Companion.getThemeWidgetKey import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -32,20 +31,9 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor( fun onItemSelect(student: Student) { selectedStudent = student - view?.showThemeDialog() - } - - fun onThemeSelect(index: Int) { - appWidgetId?.let { - sharedPref.putLong(getThemeWidgetKey(it), index.toLong()) - } registerStudent(selectedStudent) } - fun onDismissThemeView() { - view?.finishView() - } - private fun loadData() { resourceFlow { studentRepository.getSavedStudents(false) }.onEach { when (it) { @@ -56,10 +44,7 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor( } ?: -1 when { it.data.isEmpty() -> view?.openLoginView() - it.data.size == 1 -> { - selectedStudent = it.data.single().student - view?.showThemeDialog() - } + it.data.size == 1 -> onItemSelect(it.data.single().student) else -> view?.updateData(it.data, selectedStudentId) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt index b4556f7e..df13b993 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetConfigureView.kt @@ -7,8 +7,6 @@ interface LuckyNumberWidgetConfigureView : BaseView { fun initView() - fun showThemeDialog() - fun updateData(data: List, selectedStudentId: Long) fun updateLuckyNumberWidget(widgetId: Int) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt index e03e3e90..bafb2d7e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt @@ -2,14 +2,12 @@ package io.github.wulkanowy.ui.modules.luckynumberwidget import android.app.PendingIntent import android.appwidget.AppWidgetManager -import android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT -import android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH import android.appwidget.AppWidgetProvider import android.content.Context import android.content.res.Configuration import android.os.Bundle -import android.view.View.GONE -import android.view.View.VISIBLE +import android.util.TypedValue.COMPLEX_UNIT_SP +import android.view.View import android.widget.RemoteViews import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -17,7 +15,6 @@ import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.entities.LuckyNumber -import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.data.repositories.LuckyNumberRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.toFirstResult @@ -41,16 +38,12 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { lateinit var sharedPref: SharedPrefProvider companion object { + private const val LUCKY_NUMBER_WIDGET_MAX_SIZE = 196 - const val LUCKY_NUMBER_PENDING_INTENT_ID = 200 + private const val LUCKY_NUMBER_PENDING_INTENT_ID = 300 + private const val LUCKY_NUMBER_HISTORY_PENDING_INTENT_ID = 301 fun getStudentWidgetKey(appWidgetId: Int) = "lucky_number_widget_student_$appWidgetId" - - fun getThemeWidgetKey(appWidgetId: Int) = "lucky_number_widget_theme_$appWidgetId" - - fun getHeightWidgetKey(appWidgetId: Int) = "lucky_number_widget_height_$appWidgetId" - - fun getWidthWidgetKey(appWidgetId: Int) = "lucky_number_widget_width_$appWidgetId" } override fun onUpdate( @@ -59,107 +52,86 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { appWidgetIds: IntArray? ) { super.onUpdate(context, appWidgetManager, appWidgetIds) - appWidgetIds?.forEach { appWidgetId -> - val luckyNumber = - getLuckyNumber(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId) - val appIntent = PendingIntent.getActivity( - context, - LUCKY_NUMBER_PENDING_INTENT_ID, - SplashActivity.getStartIntent(context, Destination.LuckyNumber), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE - ) - if (luckyNumber is Resource.Error) { - Timber.e("Error loading lucky number for widget", luckyNumber.error) - } + val appIntent = PendingIntent.getActivity( + context, + LUCKY_NUMBER_PENDING_INTENT_ID, + SplashActivity.getStartIntent(context, Destination.LuckyNumber), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) - val remoteView = - RemoteViews(context.packageName, getCorrectLayoutId(appWidgetId, context)) - .apply { - setTextViewText( - R.id.luckyNumberWidgetNumber, - luckyNumber.dataOrNull?.luckyNumber?.toString() ?: "#" - ) - setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent) - } + val historyIntent = PendingIntent.getActivity( + context, + LUCKY_NUMBER_HISTORY_PENDING_INTENT_ID, + SplashActivity.getStartIntent(context, Destination.LuckyNumberHistory), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) - setStyles(remoteView, appWidgetId) - appWidgetManager.updateAppWidget(appWidgetId, remoteView) + appWidgetIds?.forEach { widgetId -> + val studentId = sharedPref.getLong(getStudentWidgetKey(widgetId), 0) + val luckyNumberResource = getLuckyNumber(studentId, widgetId) + val luckyNumber = luckyNumberResource.dataOrNull?.luckyNumber?.toString() + val remoteView = RemoteViews(context.packageName, R.layout.widget_luckynumber) + .apply { + setTextViewText(R.id.luckyNumberWidgetValue, luckyNumber ?: "-") + setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent) + setOnClickPendingIntent(R.id.luckyNumberWidgetHistoryButton, historyIntent) + } + + resizeWidget(context, appWidgetManager.getAppWidgetOptions(widgetId), remoteView) + appWidgetManager.updateAppWidget(widgetId, remoteView) + } + } + + override fun onAppWidgetOptionsChanged( + context: Context?, + appWidgetManager: AppWidgetManager?, + appWidgetId: Int, + newOptions: Bundle? + ) { + super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions) + + if (context == null || newOptions == null || appWidgetManager == null) { + return + } + + val remoteView = RemoteViews(context.packageName, R.layout.widget_luckynumber) + resizeWidget(context, newOptions, remoteView) + appWidgetManager.partiallyUpdateAppWidget(appWidgetId, remoteView) + } + + private fun resizeWidget(context: Context, options: Bundle, remoteViews: RemoteViews) { + val (width, height) = options.getWidgetSize(context) + val size = minOf(width, height, LUCKY_NUMBER_WIDGET_MAX_SIZE).toFloat() + resizeWidgetContents(size, remoteViews) + Timber.v("LuckyNumberWidget resized: ${width}x${height} ($size)") + } + + private fun resizeWidgetContents(size: Float, remoteViews: RemoteViews) { + var historyButtonVisibility = View.VISIBLE + var luckyNumberTextSize = 72f + + if (size < 150) { + luckyNumberTextSize = 44f + historyButtonVisibility = View.GONE + } + if (size < 75) { + luckyNumberTextSize = 26f + } + + remoteViews.apply { + setTextViewTextSize(R.id.luckyNumberWidgetValue, COMPLEX_UNIT_SP, luckyNumberTextSize) + setViewVisibility(R.id.luckyNumberWidgetHistoryButton, historyButtonVisibility) } } override fun onDeleted(context: Context?, appWidgetIds: IntArray?) { super.onDeleted(context, appWidgetIds) appWidgetIds?.forEach { appWidgetId -> - with(sharedPref) { - delete(getHeightWidgetKey(appWidgetId)) - delete(getStudentWidgetKey(appWidgetId)) - delete(getThemeWidgetKey(appWidgetId)) - delete(getWidthWidgetKey(appWidgetId)) - } + sharedPref.delete(getStudentWidgetKey(appWidgetId)) } } - override fun onAppWidgetOptionsChanged( - context: Context, - appWidgetManager: AppWidgetManager, - appWidgetId: Int, - newOptions: Bundle? - ) { - super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions) - - val remoteView = RemoteViews(context.packageName, getCorrectLayoutId(appWidgetId, context)) - - setStyles(remoteView, appWidgetId, newOptions) - appWidgetManager.updateAppWidget(appWidgetId, remoteView) - } - - private fun setStyles(views: RemoteViews, appWidgetId: Int, options: Bundle? = null) { - val width = options?.getInt(OPTION_APPWIDGET_MIN_WIDTH) ?: sharedPref.getLong( - getWidthWidgetKey(appWidgetId), 74 - ).toInt() - val height = options?.getInt(OPTION_APPWIDGET_MAX_HEIGHT) ?: sharedPref.getLong( - getHeightWidgetKey(appWidgetId), 74 - ).toInt() - - with(sharedPref) { - putLong(getWidthWidgetKey(appWidgetId), width.toLong()) - putLong(getHeightWidgetKey(appWidgetId), height.toLong()) - } - - val rows = getCellsForSize(height) - val cols = getCellsForSize(width) - - Timber.d("New lucky number widget measurement: %dx%d", width, height) - Timber.d("Widget size: $cols x $rows") - - when { - 1 == cols && 1 == rows -> views.setVisibility(imageTop = false, imageLeft = false) - 1 == cols && 1 < rows -> views.setVisibility(imageTop = true, imageLeft = false) - 1 < cols && 1 == rows -> views.setVisibility(imageTop = false, imageLeft = true) - 1 == cols && 1 == rows -> views.setVisibility(imageTop = true, imageLeft = false) - 2 == cols && 1 == rows -> views.setVisibility(imageTop = false, imageLeft = true) - else -> views.setVisibility(imageTop = false, imageLeft = false, title = true) - } - } - - private fun RemoteViews.setVisibility( - imageTop: Boolean, - imageLeft: Boolean, - title: Boolean = false - ) { - setViewVisibility(R.id.luckyNumberWidgetImageTop, if (imageTop) VISIBLE else GONE) - setViewVisibility(R.id.luckyNumberWidgetImageLeft, if (imageLeft) VISIBLE else GONE) - setViewVisibility(R.id.luckyNumberWidgetTitle, if (title) VISIBLE else GONE) - setViewVisibility(R.id.luckyNumberWidgetNumber, VISIBLE) - } - - private fun getCellsForSize(size: Int): Int { - var n = 2 - while (74 * n - 30 < size) ++n - return n - 1 - } - private fun getLuckyNumber(studentId: Long, appWidgetId: Int) = runBlocking { try { val students = studentRepository.getSavedStudents() @@ -181,22 +153,24 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { Resource.Success(null) } } catch (e: Exception) { - if (e.cause !is NoCurrentStudentException) { - Timber.e(e, "An error has occurred in lucky number provider") - } + Timber.e(e, "An error has occurred in lucky number provider") Resource.Error(e) } } - private fun getCorrectLayoutId(appWidgetId: Int, context: Context): Int { - val savedTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0) - val isSystemDarkMode = - context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES + private fun Bundle.getWidgetSize(context: Context): Pair { + val minWidth = getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH) + val maxWidth = getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH) + val minHeight = getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT) + val maxHeight = getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT) - return if (savedTheme == 1L || (savedTheme == 2L && isSystemDarkMode)) { - R.layout.widget_luckynumber_dark + val orientation = context.resources.configuration.orientation + val isPortrait = orientation == Configuration.ORIENTATION_PORTRAIT + + return if (isPortrait) { + minWidth to maxHeight } else { - R.layout.widget_luckynumber + maxWidth to minHeight } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index 51092376..091080a5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -2,14 +2,14 @@ package io.github.wulkanowy.ui.modules.main import android.content.Context import android.content.Intent -import android.os.Build.VERSION_CODES.P +import android.os.Build import android.os.Bundle import android.view.Menu import android.view.MenuItem +import android.view.ViewGroup.MarginLayoutParams import androidx.activity.OnBackPressedCallback import androidx.activity.addCallback -import androidx.core.view.ViewCompat -import androidx.core.view.isVisible +import androidx.core.view.* import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.preference.Preference @@ -90,8 +90,16 @@ class MainActivity : BaseActivity(), MainVie super.onCreate(savedInstanceState) setContentView(ActivityMainBinding.inflate(layoutInflater).apply { binding = this }.root) setSupportActionBar(binding.mainToolbar) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowCompat.setDecorFitsSystemWindows(window, false) + binding.mainAppBar.isLifted = true + } + initializeFragmentContainer() + this.savedInstanceState = savedInstanceState messageContainer = binding.mainMessageContainer + messageAnchor = binding.mainMessageContainer updateHelper.messageContainer = binding.mainFragmentContainer onBackCallback = onBackPressedDispatcher.addCallback(this, enabled = false) { presenter.onBackPressed() @@ -187,6 +195,17 @@ class MainActivity : BaseActivity(), MainVie } } + private fun initializeFragmentContainer() { + ViewCompat.setOnApplyWindowInsetsListener(binding.mainFragmentContainer) { view, insets -> + val bottomInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars()) + + view.updateLayoutParams { + bottomMargin = if (binding.mainBottomNav.isVisible) 0 else bottomInsets.bottom + } + WindowInsetsCompat.CONSUMED + } + } + override fun onPreferenceStartFragment( caller: PreferenceFragmentCompat, pref: Preference @@ -231,20 +250,9 @@ class MainActivity : BaseActivity(), MainVie showDialogFragment(AccountQuickDialog.newInstance(studentWithSemesters)) } - override fun showActionBarElevation(show: Boolean) { - ViewCompat.setElevation(binding.mainToolbar, if (show) dpToPx(4f) else 0f) - } - override fun showBottomNavigation(show: Boolean) { binding.mainBottomNav.isVisible = show - - if (appInfo.systemVersion >= P) { - window.navigationBarColor = if (show) { - getThemeAttrColor(android.R.attr.navigationBarColor) - } else { - getThemeAttrColor(R.attr.colorSurface) - } - } + binding.mainFragmentContainer.requestApplyInsets() } override fun openMoreDestination(destination: Destination) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index d51cdac6..ae05ecf2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -14,9 +14,6 @@ import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.account.AccountView import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsView -import io.github.wulkanowy.ui.modules.grade.GradeView -import io.github.wulkanowy.ui.modules.message.MessageView -import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersView import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView import io.github.wulkanowy.utils.AdsHelper import io.github.wulkanowy.utils.AnalyticsHelper @@ -100,7 +97,6 @@ class MainPresenter @Inject constructor( fun onViewChange(destinationView: BaseView) { view?.apply { showBottomNavigation(shouldShowBottomNavigation(destinationView)) - showActionBarElevation(shouldShowActionBarElevation(destinationView)) currentViewTitle?.let { setViewTitle(it) } currentViewSubtitle?.let { setViewSubTitle(it.ifBlank { null }) } currentStackSize?.let { @@ -110,13 +106,6 @@ class MainPresenter @Inject constructor( } } - private fun shouldShowActionBarElevation(destination: BaseView) = when (destination) { - is GradeView, - is MessageView, - is SchoolAndTeachersView -> false - else -> true - } - private fun shouldShowBottomNavigation(destination: BaseView) = when (destination) { is AccountView, is StudentInfoView, diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt index 03f9641d..62436f3b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt @@ -28,8 +28,6 @@ interface MainView : BaseView { fun showAccountPicker(studentWithSemesters: List) - fun showActionBarElevation(show: Boolean) - fun showBottomNavigation(show: Boolean) fun notifyMenuViewReselected() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt index 37f9a19b..8bd84f2b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/mailboxchooser/MailboxChooserDialog.kt @@ -1,11 +1,11 @@ package io.github.wulkanowy.ui.modules.message.mailboxchooser +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.os.bundleOf import androidx.fragment.app.setFragmentResult +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.databinding.DialogMailboxChooserBinding @@ -37,19 +37,19 @@ class MailboxChooserDialog : BaseDialogFragment(), } } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView( + DialogMailboxChooserBinding.inflate(layoutInflater).apply { binding = this }.root + ) + .create() } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogMailboxChooserBinding.inflate(inflater).apply { binding = this }.root @Suppress("UNCHECKED_CAST") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) presenter.onAttachView( view = this, requireMailbox = requireArguments().getBoolean(REQUIRED_KEY, false), diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt index 14f3d718..28147fae 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/send/SendMessageActivity.kt @@ -1,10 +1,10 @@ package io.github.wulkanowy.ui.modules.message.send import android.annotation.SuppressLint -import android.app.AlertDialog import android.content.Context import android.content.Intent import android.graphics.Rect +import android.os.Build import android.os.Bundle import android.text.Spanned import android.view.Menu @@ -12,11 +12,14 @@ import android.view.MenuItem import android.view.TouchDelegate import android.view.View.GONE import android.view.View.VISIBLE +import android.view.ViewGroup import android.widget.Toast import android.widget.Toast.LENGTH_LONG import androidx.core.text.parseAsHtml import androidx.core.text.toHtml +import androidx.core.view.* import androidx.core.widget.doOnTextChanged +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Mailbox @@ -24,8 +27,8 @@ import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.databinding.ActivitySendMessageBinding import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog -import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.MAILBOX_KEY import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.LISTENER_KEY +import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.MAILBOX_KEY import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.nullableSerializable @@ -99,6 +102,13 @@ class SendMessageActivity : BaseActivity= Build.VERSION_CODES.R) { + WindowCompat.setDecorFitsSystemWindows(window, false) + binding.sendAppBar.isLifted = true + } + initializeMessageContainer() + messageContainer = binding.sendMessageContainer formRecipientsData = binding.sendMessageTo.addedChipItems as List @@ -130,6 +140,17 @@ class SendMessageActivity : BaseActivity + val bottomInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars()) + + view.updateLayoutParams { + bottomMargin = bottomInsets.bottom + } + WindowInsetsCompat.CONSUMED + } + } + private fun onMessageSubjectChange(text: CharSequence?) { formSubjectValue = text.toString() presenter.onMessageContentChange() @@ -252,7 +273,7 @@ class SendMessageActivity : BaseActivity presenter.restoreMessageParts() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt index 6df6153c..9792c708 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/message/tab/MessageTabAdapter.kt @@ -1,5 +1,6 @@ package io.github.wulkanowy.ui.modules.message.tab +import android.annotation.SuppressLint import android.content.res.ColorStateList import android.graphics.Typeface import android.view.LayoutInflater @@ -68,21 +69,23 @@ class MessageTabAdapter @Inject constructor() : } } + @SuppressLint("PrivateResource") private fun bindHeaderViewHolder(holder: HeaderViewHolder, position: Int) { val item = items[position] as MessageTabDataItem.FilterHeader + val context = holder.binding.root.context with(holder.binding) { - chipMailbox.text = item.selectedMailbox - ?: root.context.getString(R.string.message_chip_all_mailboxes) + chipMailbox.text = + item.selectedMailbox ?: context.getString(R.string.message_chip_all_mailboxes) chipMailbox.chipBackgroundColor = ColorStateList.valueOf( if (item.selectedMailbox == null) { - root.context.getCompatColor(R.color.mtrl_choice_chip_background_color) - } else root.context.getThemeAttrColor(android.R.attr.colorPrimary, 64) + context.getCompatColor(R.color.m3_elevated_chip_background_color) + } else context.getThemeAttrColor(R.attr.colorPrimary, 64) ) chipMailbox.setTextColor( if (item.selectedMailbox == null) { - root.context.getThemeAttrColor(android.R.attr.textColorPrimary) - } else root.context.getThemeAttrColor(android.R.attr.colorPrimary) + context.getThemeAttrColor(R.attr.colorOnSurfaceVariant) + } else context.getThemeAttrColor(R.attr.colorPrimary) ) chipMailbox.setOnClickListener { onMailboxClickListener() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenDialog.kt index eb420a6a..2cc2a2aa 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/mobiledevice/token/MobileDeviceTokenDialog.kt @@ -1,17 +1,17 @@ package io.github.wulkanowy.ui.modules.mobiledevice.token +import android.app.Dialog import android.content.ClipData import android.content.ClipboardManager import android.graphics.BitmapFactory import android.os.Bundle import android.util.Base64 -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import android.widget.Toast import androidx.core.content.getSystemService +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.pojos.MobileDeviceToken @@ -31,17 +31,14 @@ class MobileDeviceTokenDialog : BaseDialogFragment(), fun newInstance() = MobileDeviceTokenDialog() } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView( + DialogMobileDeviceBinding.inflate(layoutInflater).apply { binding = this }.root + ) + .create() } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogMobileDeviceBinding.inflate(inflater).apply { binding = this }.root - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) presenter.onAttachView(this) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt index e46ab42c..0592e924 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/note/NoteDialog.kt @@ -1,25 +1,24 @@ package io.github.wulkanowy.ui.modules.note import android.annotation.SuppressLint +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.content.ContextCompat import androidx.core.os.bundleOf -import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.databinding.DialogNoteBinding import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory +import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.toFormattedString -class NoteDialog : DialogFragment() { - - private var binding: DialogNoteBinding by lifecycleAwareVariable() +@AndroidEntryPoint +class NoteDialog : BaseDialogFragment() { private lateinit var note: Note @@ -34,15 +33,14 @@ class NoteDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) note = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogNoteBinding.inflate(inflater).apply { binding = this }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogNoteBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt index 163ba8cd..0763d4fa 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/notifications/NotificationsFragment.kt @@ -3,7 +3,7 @@ package io.github.wulkanowy.ui.modules.notifications import android.os.Bundle import android.view.View import androidx.activity.result.contract.ActivityResultContracts.RequestPermission -import androidx.appcompat.app.AlertDialog +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.databinding.FragmentNotificationsBinding @@ -41,7 +41,7 @@ class NotificationsFragment : } private fun showSettingsDialog() { - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.notifications_header_title) .setMessage(R.string.notifications_header_description) .setNegativeButton(R.string.notifications_skip) { dialog, _ -> diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt index e33a48f0..c1c58441 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/schoolannouncement/SchoolAnnouncementDialog.kt @@ -1,21 +1,20 @@ package io.github.wulkanowy.ui.modules.schoolannouncement +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.os.bundleOf -import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.databinding.DialogSchoolAnnouncementBinding -import io.github.wulkanowy.utils.lifecycleAwareVariable +import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.parseUonetHtml import io.github.wulkanowy.utils.serializable import io.github.wulkanowy.utils.toFormattedString -class SchoolAnnouncementDialog : DialogFragment() { - - private var binding: DialogSchoolAnnouncementBinding by lifecycleAwareVariable() +@AndroidEntryPoint +class SchoolAnnouncementDialog : BaseDialogFragment() { private lateinit var announcement: SchoolAnnouncement @@ -30,15 +29,17 @@ class SchoolAnnouncementDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) announcement = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogSchoolAnnouncementBinding.inflate(inflater).also { binding = it }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView( + DialogSchoolAnnouncementBinding.inflate(layoutInflater) + .apply { binding = this }.root + ) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt index 77a3c6cf..98ac1573 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt @@ -8,13 +8,13 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AlertDialog import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreferenceCompat import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.thelittlefireman.appkillermanager.AppKillerManager import com.thelittlefireman.appkillermanager.exceptions.NoActionFoundException import dagger.hilt.android.AndroidEntryPoint @@ -149,7 +149,7 @@ class NotificationsFragment : PreferenceFragmentCompat(), } override fun showFixSyncDialog() { - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.pref_notify_fix_sync_issues) .setMessage(R.string.pref_notify_fix_sync_issues_message) .setNegativeButton(android.R.string.cancel) { _, _ -> } @@ -177,7 +177,7 @@ class NotificationsFragment : PreferenceFragmentCompat(), } override fun openNotificationsPermissionDialog() { - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.notifications_header_title) .setMessage(R.string.notifications_header_description) .setPositiveButton(R.string.pref_notification_go_to_settings) { _, _ -> @@ -191,7 +191,7 @@ class NotificationsFragment : PreferenceFragmentCompat(), } override fun openNotificationPiggyBackPermissionDialog() { - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(getString(R.string.pref_notification_piggyback_popup_title)) .setMessage(getString(R.string.pref_notification_piggyback_popup_description)) .setPositiveButton(getString(R.string.pref_notification_go_to_settings)) { _, _ -> @@ -205,7 +205,7 @@ class NotificationsFragment : PreferenceFragmentCompat(), } override fun openNotificationExactAlarmSettings() { - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(getString(R.string.pref_notification_exact_alarm_popup_title)) .setMessage(getString(R.string.pref_notification_exact_alarm_popup_descriptions)) .setPositiveButton(getString(R.string.pref_notification_go_to_settings)) { _, _ -> diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt index 8477e322..2a804d9f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt @@ -5,6 +5,7 @@ import android.os.Bundle import android.view.View import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat +import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseActivity @@ -75,7 +76,11 @@ class SyncFragment : PreferenceFragmentCompat(), } override fun showMessage(text: String) { - (activity as? BaseActivity<*, *>)?.showMessage(text) + Snackbar.make(requireView(), text, Snackbar.LENGTH_LONG) + .apply { + anchorView = requireActivity().findViewById(R.id.main_bottom_nav) + show() + } } override fun showExpiredDialog() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt index 2f0d697f..d917e7d5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableAdapter.kt @@ -160,7 +160,7 @@ class TimetableAdapter @Inject constructor() : timetableSmallItemDescription.setTextColor( root.context.getThemeAttrColor( - if (lesson.canceled) R.attr.colorPrimary + if (lesson.canceled) R.attr.colorTimetableCanceled else R.attr.colorTimetableChange ) ) @@ -185,7 +185,7 @@ class TimetableAdapter @Inject constructor() : timetableItemDescription.setTextColor( root.context.getThemeAttrColor( - if (lesson.canceled) R.attr.colorPrimary + if (lesson.canceled) R.attr.colorTimetableCanceled else R.attr.colorTimetableChange ) ) @@ -228,8 +228,8 @@ class TimetableAdapter @Inject constructor() : } private fun updateNumberAndSubjectCanceledColor(numberView: TextView, subjectView: TextView) { - numberView.setTextColor(numberView.context.getThemeAttrColor(R.attr.colorPrimary)) - subjectView.setTextColor(subjectView.context.getThemeAttrColor(R.attr.colorPrimary)) + numberView.setTextColor(numberView.context.getThemeAttrColor(R.attr.colorTimetableCanceled)) + subjectView.setTextColor(subjectView.context.getThemeAttrColor(R.attr.colorTimetableCanceled)) } private fun updateNumberColor(numberView: TextView, lesson: Timetable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt index 4f5547d2..e8a85347 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableDialog.kt @@ -1,24 +1,24 @@ package io.github.wulkanowy.ui.modules.timetable import android.annotation.SuppressLint +import android.app.Dialog import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.view.ViewGroup import androidx.core.os.bundleOf -import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.databinding.DialogTimetableBinding +import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.* import java.time.Instant -class TimetableDialog : DialogFragment() { - - private var binding: DialogTimetableBinding by lifecycleAwareVariable() +@AndroidEntryPoint +class TimetableDialog : BaseDialogFragment() { private lateinit var lesson: Timetable @@ -33,15 +33,14 @@ class TimetableDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) lesson = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogTimetableBinding.inflate(inflater).apply { binding = this }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView(DialogTimetableBinding.inflate(layoutInflater).apply { binding = this }.root) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -82,12 +81,12 @@ class TimetableDialog : DialogFragment() { if (canceled) { timetableDialogChangesTitle.setTextColor( requireContext().getThemeAttrColor( - R.attr.colorPrimary + R.attr.colorTimetableCanceled ) ) timetableDialogChangesValue.setTextColor( requireContext().getThemeAttrColor( - R.attr.colorPrimary + R.attr.colorTimetableCanceled ) ) } else { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt index e95d6f82..ebc16239 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt @@ -87,7 +87,7 @@ class TimetableFragment : BaseFragment(R.layout.fragme timetableNavDate.setOnClickListener { presenter.onPickDate() } timetableNextButton.setOnClickListener { presenter.onNextDay() } - timetableNavContainer.elevation = requireContext().dpToPx(8f) + timetableNavContainer.elevation = requireContext().dpToPx(3f) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt index 043fa1f7..faa833c2 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/AdditionalLessonsFragment.kt @@ -2,8 +2,8 @@ package io.github.wulkanowy.ui.modules.timetable.additional import android.os.Bundle import android.view.View -import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.TimetableAdditional @@ -13,11 +13,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.timetable.additional.add.AdditionalLessonAddDialog import io.github.wulkanowy.ui.widgets.DividerItemDecoration -import io.github.wulkanowy.utils.dpToPx -import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear -import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear -import io.github.wulkanowy.utils.openMaterialDatePicker +import io.github.wulkanowy.utils.* import java.time.LocalDate import javax.inject.Inject @@ -73,7 +69,7 @@ class AdditionalLessonsFragment : openAddAdditionalLessonButton.setOnClickListener { presenter.onAdditionalLessonAddButtonClicked() } - additionalLessonsNavContainer.elevation = requireContext().dpToPx(8f) + additionalLessonsNavContainer.elevation = requireContext().dpToPx(3f) } } @@ -154,7 +150,7 @@ class AdditionalLessonsFragment : } override fun showDeleteLessonDialog(timetableAdditional: TimetableAdditional) { - AlertDialog.Builder(requireContext()) + MaterialAlertDialogBuilder(requireContext()) .setTitle(getString(R.string.additional_lessons_delete_title)) .setItems( arrayOf( diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt index f82d6483..13471997 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/additional/add/AdditionalLessonAddDialog.kt @@ -1,10 +1,10 @@ package io.github.wulkanowy.ui.modules.timetable.additional.add +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.widget.doOnTextChanged +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.timepicker.MaterialTimePicker import com.google.android.material.timepicker.TimeFormat import dagger.hilt.android.AndroidEntryPoint @@ -29,16 +29,14 @@ class AdditionalLessonAddDialog : BaseDialogFragment fun newInstance() = AdditionalLessonAddDialog() } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) - } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogAdditionalAddBinding.inflate(inflater).apply { binding = this }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView( + DialogAdditionalAddBinding.inflate(layoutInflater).apply { binding = this }.root + ) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt index ddd7488e..d937d4dd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonDialog.kt @@ -1,19 +1,18 @@ package io.github.wulkanowy.ui.modules.timetable.completed +import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.os.bundleOf -import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.databinding.DialogLessonCompletedBinding -import io.github.wulkanowy.utils.lifecycleAwareVariable +import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.utils.serializable -class CompletedLessonDialog : DialogFragment() { - - private var binding: DialogLessonCompletedBinding by lifecycleAwareVariable() +@AndroidEntryPoint +class CompletedLessonDialog : BaseDialogFragment() { private lateinit var completedLesson: CompletedLesson @@ -28,15 +27,16 @@ class CompletedLessonDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, 0) completedLesson = requireArguments().serializable(ARGUMENT_KEY) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = DialogLessonCompletedBinding.inflate(inflater).apply { binding = this }.root + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext(), theme) + .setView( + DialogLessonCompletedBinding.inflate(layoutInflater).apply { binding = this }.root + ) + .create() + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt index 34a69e6a..77a7bbd5 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/completed/CompletedLessonsFragment.kt @@ -2,9 +2,7 @@ package io.github.wulkanowy.ui.modules.timetable.completed import android.os.Bundle import android.view.View -import android.view.View.GONE -import android.view.View.INVISIBLE -import android.view.View.VISIBLE +import android.view.View.* import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -14,12 +12,7 @@ import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.widgets.DividerItemDecoration -import io.github.wulkanowy.utils.dpToPx -import io.github.wulkanowy.utils.firstSchoolDayInSchoolYear -import io.github.wulkanowy.utils.getCompatDrawable -import io.github.wulkanowy.utils.getThemeAttrColor -import io.github.wulkanowy.utils.lastSchoolDayInSchoolYear -import io.github.wulkanowy.utils.openMaterialDatePicker +import io.github.wulkanowy.utils.* import java.time.LocalDate import javax.inject.Inject @@ -73,7 +66,7 @@ class CompletedLessonsFragment : completedLessonsNavDate.setOnClickListener { presenter.onPickDate() } completedLessonsNextButton.setOnClickListener { presenter.onNextDay() } - completedLessonsNavContainer.elevation = requireContext().dpToPx(8f) + completedLessonsNavContainer.elevation = requireContext().dpToPx(3f) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt index 6ef6cfc9..672dbe72 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureActivity.kt @@ -2,14 +2,11 @@ package io.github.wulkanowy.ui.modules.timetablewidget import android.appwidget.AppWidgetManager.* import android.content.Intent -import android.os.Build import android.os.Bundle import android.widget.Toast import android.widget.Toast.LENGTH_LONG -import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint -import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.databinding.ActivityWidgetConfigureBinding import io.github.wulkanowy.ui.base.BaseActivity @@ -34,8 +31,6 @@ class TimetableWidgetConfigureActivity : @Inject lateinit var appInfo: AppInfo - private var dialog: AlertDialog? = null - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setResult(RESULT_CANCELED) @@ -61,23 +56,6 @@ class TimetableWidgetConfigureActivity : configureAdapter.onClickListener = presenter::onItemSelect } - override fun showThemeDialog() { - var items = arrayOf( - getString(R.string.widget_timetable_theme_light), - getString(R.string.widget_timetable_theme_dark) - ) - - if (appInfo.systemVersion >= Build.VERSION_CODES.Q) items += getString(R.string.widget_timetable_theme_system) - - dialog = AlertDialog.Builder(this, R.style.WulkanowyTheme_WidgetAccountSwitcher) - .setTitle(R.string.widget_timetable_theme_title) - .setOnDismissListener { presenter.onDismissThemeView() } - .setSingleChoiceItems(items, -1) { _, which -> - presenter.onThemeSelect(which) - } - .show() - } - override fun updateData(data: List, selectedStudentId: Long) { with(configureAdapter) { selectedId = selectedStudentId @@ -110,9 +88,4 @@ class TimetableWidgetConfigureActivity : override fun openLoginView() { startActivity(LoginActivity.getStartIntent(this)) } - - override fun onDestroy() { - super.onDestroy() - dialog?.dismiss() - } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt index dc2a7c6c..87e89336 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigurePresenter.kt @@ -8,7 +8,6 @@ import io.github.wulkanowy.data.resourceFlow import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey -import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getThemeWidgetKey import kotlinx.coroutines.flow.onEach import timber.log.Timber import javax.inject.Inject @@ -39,22 +38,9 @@ class TimetableWidgetConfigurePresenter @Inject constructor( fun onItemSelect(student: Student) { selectedStudent = student - - if (isFromProvider) registerStudent(selectedStudent) - else view?.showThemeDialog() - } - - fun onThemeSelect(index: Int) { - appWidgetId?.let { - sharedPref.putLong(getThemeWidgetKey(it), index.toLong()) - } registerStudent(selectedStudent) } - fun onDismissThemeView() { - view?.finishView() - } - private fun loadData() { resourceFlow { studentRepository.getSavedStudents(false) }.onEach { when (it) { @@ -65,10 +51,7 @@ class TimetableWidgetConfigurePresenter @Inject constructor( } ?: -1 when { it.data.isEmpty() -> view?.openLoginView() - it.data.size == 1 && !isFromProvider -> { - selectedStudent = it.data.single().student - view?.showThemeDialog() - } + it.data.size == 1 && !isFromProvider -> onItemSelect(it.data.single().student) else -> view?.updateData(it.data, selectedStudentId) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt index accdc28d..7740b9bb 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetConfigureView.kt @@ -11,8 +11,6 @@ interface TimetableWidgetConfigureView : BaseView { fun updateTimetableWidget(widgetId: Int) - fun showThemeDialog() - fun setSuccessResult(widgetId: Int) fun finishView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt index 664086bc..9c5abe1c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetFactory.kt @@ -1,27 +1,25 @@ package io.github.wulkanowy.ui.modules.timetablewidget -import android.annotation.SuppressLint import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID import android.content.Context import android.content.Intent +import android.content.res.Configuration import android.graphics.Paint.ANTI_ALIAS_FLAG import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG import android.view.View.GONE import android.view.View.VISIBLE -import android.widget.AdapterView.INVALID_POSITION import android.widget.RemoteViews import android.widget.RemoteViewsService import io.github.wulkanowy.R import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.db.SharedPrefProvider +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Timetable -import io.github.wulkanowy.data.enums.TimetableMode -import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.TimetableRepository import io.github.wulkanowy.data.toFirstResult -import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getCurrentThemeWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getDateWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getTodayLastLessonEndDateTimeWidgetKey @@ -29,13 +27,13 @@ import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.toFormattedString import kotlinx.coroutines.runBlocking import timber.log.Timber +import java.time.Instant import java.time.LocalDate class TimetableWidgetFactory( private val timetableRepository: TimetableRepository, private val studentRepository: StudentRepository, private val semesterRepository: SemesterRepository, - private val prefRepository: PreferencesRepository, private val sharedPref: SharedPrefProvider, private val context: Context, private val intent: Intent? @@ -43,19 +41,22 @@ class TimetableWidgetFactory( private var lessons = emptyList() - private var savedCurrentTheme: Long? = null - - private var primaryColor: Int? = null + private var timetableCanceledColor: Int? = null private var textColor: Int? = null private var timetableChangeColor: Int? = null + private var lastSyncInstant: Instant? = null + override fun getLoadingView() = null override fun hasStableIds() = true - override fun getCount() = lessons.size + override fun getCount() = when { + lessons.isEmpty() -> 0 + else -> lessons.size + 1 + } override fun getViewTypeCount() = 2 @@ -70,195 +71,170 @@ class TimetableWidgetFactory( val date = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(appWidgetId), 0)) val studentId = sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0) - updateTheme(appWidgetId) - lessons = getLessons(date, studentId) - - val todayLastLessonEndTimestamp = lessons.maxOfOrNull { it.end } - if (date == LocalDate.now() && todayLastLessonEndTimestamp != null) { - sharedPref.putLong( - key = getTodayLastLessonEndDateTimeWidgetKey(appWidgetId), - value = todayLastLessonEndTimestamp.epochSecond, - sync = true - ) - } - } - } - - private fun updateTheme(appWidgetId: Int) { - savedCurrentTheme = sharedPref.getLong(getCurrentThemeWidgetKey(appWidgetId), 0) - - if (savedCurrentTheme == 0L) { - primaryColor = R.color.colorPrimary - textColor = android.R.color.black - timetableChangeColor = R.color.timetable_change_dark - } else { - primaryColor = R.color.colorPrimaryLight - textColor = android.R.color.white - timetableChangeColor = R.color.timetable_change_light - } - } - - private fun getItemLayout(lesson: Timetable): Int { - return when { - prefRepository.showWholeClassPlan == TimetableMode.SMALL_OTHER_GROUP && !lesson.isStudentPlan -> { - if (savedCurrentTheme == 0L) R.layout.item_widget_timetable_small - else R.layout.item_widget_timetable_small_dark - } - savedCurrentTheme == 1L -> R.layout.item_widget_timetable_dark - else -> R.layout.item_widget_timetable - } - } - - private fun getLessons(date: LocalDate, studentId: Long) = try { - runBlocking { - if (!studentRepository.isStudentSaved()) return@runBlocking emptyList() - - val students = studentRepository.getSavedStudents() - val student = students.singleOrNull { it.student.id == studentId }?.student - ?: return@runBlocking emptyList() - - val semester = semesterRepository.getCurrentSemester(student) - timetableRepository.getTimetable(student, semester, date, date, false) - .toFirstResult().dataOrNull?.lessons.orEmpty() - .sortedWith(compareBy({ it.number }, { !it.isStudentPlan })) - .filter { - if (prefRepository.showWholeClassPlan == TimetableMode.ONLY_CURRENT_GROUP) { - it.isStudentPlan - } else true + runCatching { + runBlocking { + val student = getStudent(studentId) ?: return@runBlocking + val semester = semesterRepository.getCurrentSemester(student) + lessons = getLessons(student, semester, date) + lastSyncInstant = + timetableRepository.getLastRefreshTimestamp(semester, date, date) + if (date == LocalDate.now()) { + updateTodayLastLessonEnd(appWidgetId) + } } + }.onFailure { + Timber.e(it, "An error has occurred in timetable widget factory") + } } - } catch (e: Exception) { - Timber.e(e, "An error has occurred in timetable widget factory") - emptyList() } - @SuppressLint("DefaultLocale") + private suspend fun getStudent(studentId: Long): Student? { + val students = studentRepository.getSavedStudents() + return students.singleOrNull { it.student.id == studentId }?.student + } + + private suspend fun getLessons( + student: Student, semester: Semester, date: LocalDate + ): List { + val timetable = timetableRepository.getTimetable(student, semester, date, date, false) + val lessons = timetable.toFirstResult().dataOrNull?.lessons.orEmpty() + return lessons.sortedBy { it.number } + } + + private fun updateTodayLastLessonEnd(appWidgetId: Int) { + val todayLastLessonEnd = lessons.maxOfOrNull { it.end } ?: return + val key = getTodayLastLessonEndDateTimeWidgetKey(appWidgetId) + sharedPref.putLong(key, todayLastLessonEnd.epochSecond, true) + } + + companion object { + const val TIME_FORMAT_STYLE = "HH:mm" + } + override fun getViewAt(position: Int): RemoteViews? { - if (position == INVALID_POSITION || lessons.getOrNull(position) == null) return null - - val lesson = lessons[position] - return RemoteViews(context.packageName, getItemLayout(lesson)).apply { - setTextViewText(R.id.timetableWidgetItemSubject, lesson.subject) - setTextViewText(R.id.timetableWidgetItemNumber, lesson.number.toString()) - setTextViewText( - R.id.timetableWidgetItemTimeStart, - lesson.start.toFormattedString("HH:mm") - ) - setTextViewText( - R.id.timetableWidgetItemTimeFinish, - lesson.end.toFormattedString("HH:mm") - ) - - updateDescription(this, lesson) - - if (lesson.canceled) { - updateStylesCanceled(this) - } else { - updateStylesNotCanceled(this, lesson) + if (position == lessons.size) { + val synchronizationInstant = lastSyncInstant ?: Instant.MIN + val synchronizationText = getSynchronizationInfoText(synchronizationInstant) + return RemoteViews(context.packageName, R.layout.item_widget_timetable_footer).apply { + setTextViewText(R.id.timetableWidgetSynchronizationTime, synchronizationText) } + } + val lesson = lessons.getOrNull(position) ?: return null + + val lessonStartTime = lesson.start.toFormattedString(TIME_FORMAT_STYLE) + val lessonEndTime = lesson.end.toFormattedString(TIME_FORMAT_STYLE) + val roomText = "${context.getString(R.string.timetable_room)} ${lesson.room}" + + val remoteViews = RemoteViews(context.packageName, R.layout.item_widget_timetable).apply { + setTextViewText(R.id.timetableWidgetItemNumber, lesson.number.toString()) + setTextViewText(R.id.timetableWidgetItemTimeStart, lessonStartTime) + setTextViewText(R.id.timetableWidgetItemTimeFinish, lessonEndTime) + setTextViewText(R.id.timetableWidgetItemSubject, lesson.subject) + setTextViewText(R.id.timetableWidgetItemRoom, roomText) + setTextViewText(R.id.timetableWidgetItemTeacher, lesson.teacher) + setTextViewText(R.id.timetableWidgetItemDescription, lesson.info) setOnClickFillInIntent(R.id.timetableWidgetItemContainer, Intent()) } + + updateTheme() + clearLessonStyles(remoteViews) + + when { + lesson.canceled -> applyCancelledLessonStyles(remoteViews) + lesson.changes or lesson.info.isNotBlank() -> applyChangedLessonStyles( + remoteViews, lesson + ) + } + + return remoteViews } - private fun updateDescription(remoteViews: RemoteViews, lesson: Timetable) { - with(remoteViews) { - if (lesson.info.isNotBlank() && !lesson.changes) { - setTextViewText(R.id.timetableWidgetItemDescription, lesson.info) - setViewVisibility(R.id.timetableWidgetItemDescription, VISIBLE) - setViewVisibility(R.id.timetableWidgetItemRoom, GONE) - setViewVisibility(R.id.timetableWidgetItemTeacher, GONE) - } else { - setViewVisibility(R.id.timetableWidgetItemDescription, GONE) - setViewVisibility(R.id.timetableWidgetItemRoom, VISIBLE) - setViewVisibility(R.id.timetableWidgetItemTeacher, VISIBLE) + private fun updateTheme() { + when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { + Configuration.UI_MODE_NIGHT_YES -> { + textColor = android.R.color.white + timetableChangeColor = R.color.timetable_change_dark + timetableCanceledColor = R.color.timetable_canceled_dark + } + + else -> { + textColor = android.R.color.black + timetableChangeColor = R.color.timetable_change_light + timetableCanceledColor = R.color.timetable_canceled_light } } } - private fun updateStylesCanceled(remoteViews: RemoteViews) { - with(remoteViews) { - setInt( - R.id.timetableWidgetItemSubject, "setPaintFlags", - STRIKE_THRU_TEXT_FLAG or ANTI_ALIAS_FLAG - ) - setTextColor(R.id.timetableWidgetItemNumber, context.getCompatColor(primaryColor!!)) - setTextColor(R.id.timetableWidgetItemSubject, context.getCompatColor(primaryColor!!)) - setTextColor( - R.id.timetableWidgetItemDescription, - context.getCompatColor(primaryColor!!) - ) - } - } + private fun clearLessonStyles(remoteViews: RemoteViews) { + val defaultTextColor = context.getCompatColor(textColor ?: 0) - private fun updateStylesNotCanceled(remoteViews: RemoteViews, lesson: Timetable) { - with(remoteViews) { + remoteViews.apply { setInt(R.id.timetableWidgetItemSubject, "setPaintFlags", ANTI_ALIAS_FLAG) - setTextColor(R.id.timetableWidgetItemSubject, context.getCompatColor(textColor!!)) - setTextColor( - R.id.timetableWidgetItemDescription, - context.getCompatColor(timetableChangeColor!!) - ) - - updateNotCanceledLessonNumberColor(this, lesson) - updateNotCanceledSubjectColor(this, lesson) - - val teacherChange = lesson.teacherOld.isNotBlank() - updateNotCanceledRoom(this, lesson, teacherChange) - updateNotCanceledTeacher(this, lesson, teacherChange) + setViewVisibility(R.id.timetableWidgetItemRoom, VISIBLE) + setViewVisibility(R.id.timetableWidgetItemTeacher, VISIBLE) + setViewVisibility(R.id.timetableWidgetItemIcon, GONE) + setViewVisibility(R.id.timetableWidgetItemDescription, GONE) + setTextColor(R.id.timetableWidgetItemNumber, defaultTextColor) + setTextColor(R.id.timetableWidgetItemSubject, defaultTextColor) + setTextColor(R.id.timetableWidgetItemRoom, defaultTextColor) + setTextColor(R.id.timetableWidgetItemTeacher, defaultTextColor) + setTextColor(R.id.timetableWidgetItemDescription, defaultTextColor) } } - private fun updateNotCanceledLessonNumberColor(remoteViews: RemoteViews, lesson: Timetable) { - remoteViews.setTextColor( - R.id.timetableWidgetItemNumber, context.getCompatColor( - if (lesson.changes || (lesson.info.isNotBlank() && !lesson.canceled)) timetableChangeColor!! - else textColor!! - ) - ) - } + private fun applyCancelledLessonStyles(remoteViews: RemoteViews) { + val cancelledThemeColor = context.getCompatColor(timetableCanceledColor ?: 0) + val strikeThroughPaintFlags = STRIKE_THRU_TEXT_FLAG or ANTI_ALIAS_FLAG - private fun updateNotCanceledSubjectColor(remoteViews: RemoteViews, lesson: Timetable) { - remoteViews.setTextColor( - R.id.timetableWidgetItemSubject, context.getCompatColor( - if (lesson.subjectOld.isNotBlank() && lesson.subject != lesson.subjectOld) timetableChangeColor!! - else textColor!! - ) - ) - } - - private fun updateNotCanceledRoom( - remoteViews: RemoteViews, - lesson: Timetable, - teacherChange: Boolean - ) { - with(remoteViews) { - if (lesson.room.isNotBlank()) { - setTextViewText( - R.id.timetableWidgetItemRoom, - if (teacherChange) lesson.room - else "${context.getString(R.string.timetable_room)} ${lesson.room}" - ) - - setTextColor( - R.id.timetableWidgetItemRoom, context.getCompatColor( - if (lesson.roomOld.isNotBlank() && lesson.room != lesson.roomOld) timetableChangeColor!! - else textColor!! - ) - ) - } else setTextViewText(R.id.timetableWidgetItemRoom, "") + remoteViews.apply { + setInt(R.id.timetableWidgetItemSubject, "setPaintFlags", strikeThroughPaintFlags) + setTextColor(R.id.timetableWidgetItemNumber, cancelledThemeColor) + setTextColor(R.id.timetableWidgetItemSubject, cancelledThemeColor) + setTextColor(R.id.timetableWidgetItemDescription, cancelledThemeColor) + setViewVisibility(R.id.timetableWidgetItemDescription, VISIBLE) + setViewVisibility(R.id.timetableWidgetItemRoom, GONE) + setViewVisibility(R.id.timetableWidgetItemTeacher, GONE) } } - private fun updateNotCanceledTeacher( - remoteViews: RemoteViews, - lesson: Timetable, - teacherChange: Boolean - ) { - remoteViews.setTextViewText( - R.id.timetableWidgetItemTeacher, - if (teacherChange) lesson.teacher - else "" - ) + private fun applyChangedLessonStyles(remoteViews: RemoteViews, lesson: Timetable) { + val changesTextColor = context.getCompatColor(timetableChangeColor ?: 0) + + remoteViews.apply { + setTextColor(R.id.timetableWidgetItemNumber, changesTextColor) + setTextColor(R.id.timetableWidgetItemDescription, changesTextColor) + setViewVisibility(R.id.timetableWidgetItemIcon, VISIBLE) + setImageViewResource(R.id.timetableWidgetItemIcon, R.drawable.ic_timetable_widget_swap) + } + + if (lesson.subject != lesson.subjectOld) { + remoteViews.setTextColor(R.id.timetableWidgetItemSubject, changesTextColor) + } + + if (lesson.room != lesson.roomOld) { + remoteViews.setTextColor(R.id.timetableWidgetItemRoom, changesTextColor) + } + + if (lesson.teacher != lesson.teacherOld) { + remoteViews.setTextColor(R.id.timetableWidgetItemTeacher, changesTextColor) + } + + if (lesson.info.isNotBlank() && !lesson.changes) { + remoteViews.setViewVisibility(R.id.timetableWidgetItemDescription, VISIBLE) + remoteViews.setViewVisibility(R.id.timetableWidgetItemRoom, GONE) + remoteViews.setViewVisibility(R.id.timetableWidgetItemTeacher, GONE) + } } + + private fun getSynchronizationInfoText(synchronizationInstant: Instant) = + synchronizationInstant.run { + val synchronizationTime = toFormattedString(TIME_FORMAT_STYLE) + val synchronizationDate = toFormattedString() + context.getString( + R.string.widget_timetable_last_synchronization, + synchronizationDate, + synchronizationTime, + ) + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt index 3ba2ae94..624ca30f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt @@ -8,10 +8,10 @@ import android.content.Context import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK -import android.content.res.Configuration -import android.graphics.Bitmap -import android.graphics.Canvas import android.widget.RemoteViews +import androidx.appcompat.content.res.AppCompatResources +import androidx.core.graphics.drawable.DrawableCompat +import androidx.core.graphics.drawable.toBitmap import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.data.db.SharedPrefProvider @@ -70,11 +70,6 @@ class TimetableWidgetProvider : BroadcastReceiver() { "timetable_widget_today_last_lesson_end_date_time_$appWidgetId" fun getStudentWidgetKey(appWidgetId: Int) = "timetable_widget_student_$appWidgetId" - - fun getThemeWidgetKey(appWidgetId: Int) = "timetable_widget_theme_$appWidgetId" - - fun getCurrentThemeWidgetKey(appWidgetId: Int) = - "timetable_widget_current_theme_$appWidgetId" } @OptIn(DelicateCoroutinesApi::class) @@ -109,8 +104,7 @@ class TimetableWidgetProvider : BroadcastReceiver() { val buttonType = intent.getStringExtra(EXTRA_BUTTON_TYPE) val toggledWidgetId = intent.getIntExtra(EXTRA_TOGGLED_WIDGET_ID, 0) val student = getStudent( - sharedPref.getLong(getStudentWidgetKey(toggledWidgetId), 0), - toggledWidgetId + sharedPref.getLong(getStudentWidgetKey(toggledWidgetId), 0), toggledWidgetId ) val savedDate = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(toggledWidgetId), 0)) @@ -122,8 +116,7 @@ class TimetableWidgetProvider : BroadcastReceiver() { } if (!buttonType.isNullOrBlank()) { analytics.logEvent( - "changed_timetable_widget_day", - "button" to buttonType + "changed_timetable_widget_day", "button" to buttonType ) } updateWidget(context, toggledWidgetId, date, student) @@ -137,49 +130,21 @@ class TimetableWidgetProvider : BroadcastReceiver() { with(sharedPref) { delete(getStudentWidgetKey(appWidgetId)) delete(getDateWidgetKey(appWidgetId)) - delete(getThemeWidgetKey(appWidgetId)) - delete(getCurrentThemeWidgetKey(appWidgetId)) } } } private fun updateWidget( - context: Context, - appWidgetId: Int, - date: LocalDate, - student: Student? + context: Context, appWidgetId: Int, date: LocalDate, student: Student? ) { - val savedConfigureTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0) - val isSystemDarkMode = - context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES - var currentTheme = 0L - var layoutId = R.layout.widget_timetable - - if (savedConfigureTheme == 1L || (savedConfigureTheme == 2L && isSystemDarkMode)) { - currentTheme = 1L - layoutId = R.layout.widget_timetable_dark - } - val nextNavIntent = createNavIntent(context, appWidgetId, appWidgetId, BUTTON_NEXT) val prevNavIntent = createNavIntent(context, -appWidgetId, appWidgetId, BUTTON_PREV) val resetNavIntent = createNavIntent(context, Int.MAX_VALUE - appWidgetId, appWidgetId, BUTTON_RESET) - val adapterIntent = Intent(context, TimetableWidgetService::class.java) - .apply { - putExtra(EXTRA_APPWIDGET_ID, appWidgetId) - //make Intent unique - action = appWidgetId.toString() - } - val accountIntent = PendingIntent.getActivity( - context, - -Int.MAX_VALUE + appWidgetId, - Intent(context, TimetableWidgetConfigureActivity::class.java).apply { - addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK) - putExtra(EXTRA_APPWIDGET_ID, appWidgetId) - putExtra(EXTRA_FROM_PROVIDER, true) - }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE - - ) + val adapterIntent = Intent(context, TimetableWidgetService::class.java).apply { + putExtra(EXTRA_APPWIDGET_ID, appWidgetId) + action = appWidgetId.toString() //make Intent unique + } val appIntent = PendingIntent.getActivity( context, TIMETABLE_PENDING_INTENT_ID, @@ -187,56 +152,41 @@ class TimetableWidgetProvider : BroadcastReceiver() { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) - val remoteView = RemoteViews(context.packageName, layoutId).apply { + val formattedDate = date.toFormattedString("EEE, dd.MM").capitalise() + val remoteView = RemoteViews(context.packageName, R.layout.widget_timetable).apply { setEmptyView(R.id.timetableWidgetList, R.id.timetableWidgetEmpty) - setTextViewText( - R.id.timetableWidgetDate, - date.toFormattedString("EEEE, dd.MM").capitalise() - ) - setTextViewText( - R.id.timetableWidgetName, - student?.nickOrName ?: context.getString(R.string.all_no_data) - ) - - student?.let { - setImageViewBitmap(R.id.timetableWidgetAccount, context.createAvatarBitmap(it)) - } - + setTextViewText(R.id.timetableWidgetDate, formattedDate) setRemoteAdapter(R.id.timetableWidgetList, adapterIntent) setOnClickPendingIntent(R.id.timetableWidgetNext, nextNavIntent) setOnClickPendingIntent(R.id.timetableWidgetPrev, prevNavIntent) setOnClickPendingIntent(R.id.timetableWidgetDate, resetNavIntent) - setOnClickPendingIntent(R.id.timetableWidgetName, resetNavIntent) - setOnClickPendingIntent(R.id.timetableWidgetAccount, accountIntent) setPendingIntentTemplate(R.id.timetableWidgetList, appIntent) } + student?.let { + setupAccountView(context, student, remoteView, appWidgetId) + } + with(sharedPref) { - putLong(getCurrentThemeWidgetKey(appWidgetId), currentTheme) putLong(getDateWidgetKey(appWidgetId), date.toEpochDay(), true) } with(appWidgetManager) { - updateAppWidget(appWidgetId, remoteView) + partiallyUpdateAppWidget(appWidgetId, remoteView) notifyAppWidgetViewDataChanged(appWidgetId, R.id.timetableWidgetList) - Timber.d("TimetableWidgetProvider updated") } + + Timber.d("TimetableWidgetProvider updated") } private fun createNavIntent( - context: Context, - code: Int, - appWidgetId: Int, - buttonType: String + context: Context, code: Int, appWidgetId: Int, buttonType: String ) = PendingIntent.getBroadcast( - context, - code, - Intent(context, TimetableWidgetProvider::class.java).apply { + context, code, Intent(context, TimetableWidgetProvider::class.java).apply { action = ACTION_APPWIDGET_UPDATE putExtra(EXTRA_BUTTON_TYPE, buttonType) putExtra(EXTRA_TOGGLED_WIDGET_ID, appWidgetId) - }, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE ) private suspend fun getStudent(studentId: Long, appWidgetId: Int) = try { @@ -258,31 +208,6 @@ class TimetableWidgetProvider : BroadcastReceiver() { null } - private fun Context.createAvatarBitmap(student: Student): Bitmap { - val avatarColor = if (student.avatarColor == -2937041L) { - getCompatColor(R.color.colorPrimaryLight).toLong() - } else { - student.avatarColor - } - val avatarDrawable = createNameInitialsDrawable(student.nickOrName, avatarColor, 0.5f) - - val avatarBitmap = - if (avatarDrawable.intrinsicWidth <= 0 || avatarDrawable.intrinsicHeight <= 0) { - Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) - } else { - Bitmap.createBitmap( - avatarDrawable.intrinsicWidth, - avatarDrawable.intrinsicHeight, - Bitmap.Config.ARGB_8888 - ) - } - - val canvas = Canvas(avatarBitmap) - avatarDrawable.setBounds(0, 0, canvas.width, canvas.height) - avatarDrawable.draw(canvas) - return avatarBitmap - } - private fun getWidgetDefaultDateToLoad(appWidgetId: Int): LocalDate { val lastLessonEndTimestamp = sharedPref.getLong(getTodayLastLessonEndDateTimeWidgetKey(appWidgetId), 0) @@ -299,4 +224,44 @@ class TimetableWidgetProvider : BroadcastReceiver() { todayDate.nextOrSameSchoolDay } } + + private fun setupAccountView( + context: Context, + student: Student, + remoteViews: RemoteViews, + appWidgetId: Int + ) { + val accountInitials = student.nickOrName + .split(" ") + .mapNotNull { it.firstOrNull() }.take(2) + .joinToString(separator = "").uppercase() + + val accountPickerIntent = PendingIntent.getActivity( + context, + -Int.MAX_VALUE + appWidgetId, + Intent(context, TimetableWidgetConfigureActivity::class.java).apply { + addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK) + putExtra(EXTRA_APPWIDGET_ID, appWidgetId) + putExtra(EXTRA_FROM_PROVIDER, true) + }, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) + + // Create background bitmap + val avatarDrawableResource = R.drawable.background_timetable_widget_avatar + AppCompatResources.getDrawable(context, avatarDrawableResource)?.let { drawable -> + val screenDensity = context.resources.displayMetrics.density + val avatarSize = (48 * screenDensity).toInt() + val backgroundBitmap = DrawableCompat.wrap(drawable).run { + DrawableCompat.setTint(this, student.avatarColor.toInt()) + toBitmap(avatarSize, avatarSize) + } + remoteViews.setImageViewBitmap(R.id.timetableWidgetAccountBackground, backgroundBitmap) + } + + remoteViews.apply { + setTextViewText(R.id.timetableWidgetAccountInitials, accountInitials) + setOnClickPendingIntent(R.id.timetableWidgetAccount, accountPickerIntent) + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt b/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt index 032e2d28..76ce66dc 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/LifecycleAwareVariable.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.utils import android.os.Handler import android.os.Looper import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner @@ -29,18 +30,18 @@ class LifecycleAwareVariable : ReadWriteProperty, DefaultL } } -class LifecycleAwareVariableActivity : ReadWriteProperty, +class LifecycleAwareVariableComponent : ReadWriteProperty, DefaultLifecycleObserver { private var _value: T? = null - override fun setValue(thisRef: AppCompatActivity, property: KProperty<*>, value: T) { + override fun setValue(thisRef: LifecycleOwner, property: KProperty<*>, value: T) { thisRef.lifecycle.removeObserver(this) _value = value thisRef.lifecycle.addObserver(this) } - override fun getValue(thisRef: AppCompatActivity, property: KProperty<*>) = _value + override fun getValue(thisRef: LifecycleOwner, property: KProperty<*>) = _value ?: throw IllegalStateException("Trying to call an lifecycle-aware value outside of the view lifecycle, or the value has not been initialized") override fun onDestroy(owner: LifecycleOwner) { @@ -53,4 +54,8 @@ class LifecycleAwareVariableActivity : ReadWriteProperty Fragment.lifecycleAwareVariable() = LifecycleAwareVariable() -fun lifecycleAwareVariable() = LifecycleAwareVariableActivity() +@Suppress("unused") +fun DialogFragment.lifecycleAwareVariable() = LifecycleAwareVariableComponent() + +@Suppress("unused") +fun AppCompatActivity.lifecycleAwareVariable() = LifecycleAwareVariableComponent() diff --git a/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt index 93e67be0..72129751 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/RefreshUtils.kt @@ -49,4 +49,9 @@ class AutoRefreshHelper @Inject constructor( fun updateLastRefreshTimestamp(key: String) { sharedPref.putLong(key, Instant.now().toEpochMilli()) } + + fun getLastRefreshTimestamp(key: String): Instant { + val refreshTimestampMilli = sharedPref.getLong(key, 0) + return Instant.ofEpochMilli(refreshTimestampMilli) + } } diff --git a/app/src/main/res/drawable-night/background_header_note.xml b/app/src/main/res/drawable-night/background_header_note.xml deleted file mode 100644 index 6b594e7c..00000000 --- a/app/src/main/res/drawable-night/background_header_note.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/background_grade_details_rounded.xml b/app/src/main/res/drawable/background_grade_details_rounded.xml new file mode 100644 index 00000000..e24088a0 --- /dev/null +++ b/app/src/main/res/drawable/background_grade_details_rounded.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/background_grade_details_weight_rounded.xml b/app/src/main/res/drawable/background_grade_details_weight_rounded.xml new file mode 100644 index 00000000..4b210912 --- /dev/null +++ b/app/src/main/res/drawable/background_grade_details_weight_rounded.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/background_widget_timetable_dark.xml b/app/src/main/res/drawable/background_grade_rounded.xml similarity index 74% rename from app/src/main/res/drawable/background_widget_timetable_dark.xml rename to app/src/main/res/drawable/background_grade_rounded.xml index 6fe7d0ab..52c10c2f 100644 --- a/app/src/main/res/drawable/background_widget_timetable_dark.xml +++ b/app/src/main/res/drawable/background_grade_rounded.xml @@ -1,5 +1,5 @@ - + - \ No newline at end of file + diff --git a/app/src/main/res/drawable/background_widget_item_timetable_dark.xml b/app/src/main/res/drawable/background_grade_small_rounded.xml similarity index 51% rename from app/src/main/res/drawable/background_widget_item_timetable_dark.xml rename to app/src/main/res/drawable/background_grade_small_rounded.xml index e432a648..dd50417f 100644 --- a/app/src/main/res/drawable/background_widget_item_timetable_dark.xml +++ b/app/src/main/res/drawable/background_grade_small_rounded.xml @@ -1,5 +1,5 @@ - - - \ No newline at end of file + + + diff --git a/app/src/main/res/drawable/background_header_note.xml b/app/src/main/res/drawable/background_header_note.xml index c21e55c6..8cf84a1c 100644 --- a/app/src/main/res/drawable/background_header_note.xml +++ b/app/src/main/res/drawable/background_header_note.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/drawable/background_luckynumber_widget.xml b/app/src/main/res/drawable/background_luckynumber_widget_button.xml similarity index 65% rename from app/src/main/res/drawable/background_luckynumber_widget.xml rename to app/src/main/res/drawable/background_luckynumber_widget_button.xml index 367c5527..66b1685f 100644 --- a/app/src/main/res/drawable/background_luckynumber_widget.xml +++ b/app/src/main/res/drawable/background_luckynumber_widget_button.xml @@ -1,6 +1,6 @@ - - + + diff --git a/app/src/main/res/drawable/background_luckynumber_widget_dark.xml b/app/src/main/res/drawable/background_luckynumber_widget_dark.xml deleted file mode 100644 index cb094b57..00000000 --- a/app/src/main/res/drawable/background_luckynumber_widget_dark.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/background_material_alert_dialog.xml b/app/src/main/res/drawable/background_material_alert_dialog.xml new file mode 100644 index 00000000..5ab8a350 --- /dev/null +++ b/app/src/main/res/drawable/background_material_alert_dialog.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/background_timetable_widget_avatar.xml b/app/src/main/res/drawable/background_timetable_widget_avatar.xml new file mode 100644 index 00000000..7f64c4eb --- /dev/null +++ b/app/src/main/res/drawable/background_timetable_widget_avatar.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/drawable/background_widget_header_timetable.xml b/app/src/main/res/drawable/background_widget_header_timetable.xml deleted file mode 100644 index 98eec700..00000000 --- a/app/src/main/res/drawable/background_widget_header_timetable.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/background_widget_header_timetable_dark.xml b/app/src/main/res/drawable/background_widget_header_timetable_dark.xml deleted file mode 100644 index 616a9127..00000000 --- a/app/src/main/res/drawable/background_widget_header_timetable_dark.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/background_widget_item_timetable.xml b/app/src/main/res/drawable/background_widget_item_timetable.xml index 08854fba..09635758 100644 --- a/app/src/main/res/drawable/background_widget_item_timetable.xml +++ b/app/src/main/res/drawable/background_widget_item_timetable.xml @@ -1,5 +1,5 @@ - - - \ No newline at end of file + + + diff --git a/app/src/main/res/drawable/background_widget_timetable.xml b/app/src/main/res/drawable/background_widget_timetable.xml index 2267587d..b589ad29 100644 --- a/app/src/main/res/drawable/background_widget_timetable.xml +++ b/app/src/main/res/drawable/background_widget_timetable.xml @@ -1,5 +1,5 @@ - - - \ No newline at end of file + + + diff --git a/app/src/main/res/drawable/ic_chevron_left.xml b/app/src/main/res/drawable/ic_chevron_left.xml index ee3ff4be..4250fae4 100644 --- a/app/src/main/res/drawable/ic_chevron_left.xml +++ b/app/src/main/res/drawable/ic_chevron_left.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_chevron_right.xml b/app/src/main/res/drawable/ic_chevron_right.xml index a6d73497..de5037cf 100644 --- a/app/src/main/res/drawable/ic_chevron_right.xml +++ b/app/src/main/res/drawable/ic_chevron_right.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_history.xml b/app/src/main/res/drawable/ic_history.xml new file mode 100644 index 00000000..f20e2094 --- /dev/null +++ b/app/src/main/res/drawable/ic_history.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_scale_balance.xml b/app/src/main/res/drawable/ic_scale_balance.xml new file mode 100644 index 00000000..c65467a6 --- /dev/null +++ b/app/src/main/res/drawable/ic_scale_balance.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/drawable/ic_timetable_widget_swap.xml b/app/src/main/res/drawable/ic_timetable_widget_swap.xml new file mode 100644 index 00000000..2f91489a --- /dev/null +++ b/app/src/main/res/drawable/ic_timetable_widget_swap.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_widget_chevron.png b/app/src/main/res/drawable/ic_widget_chevron.png deleted file mode 100644 index 34345521a5b5f97b7a8bb543c3f86104ac914c4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 130 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaA@dWsUxVjhk{D%Ry`4>PMSW1HY zf*JnRhX*Fk)C2MrJzX3_G|nd{Nc?H& + + diff --git a/app/src/main/res/drawable/img_luckynumber_widget_preview.png b/app/src/main/res/drawable/img_luckynumber_widget_preview.png index 539b0a598422fe833ab445130c55c51373473568..267018550f11c15ad0a1c0ecefc14285a5e23d8b 100644 GIT binary patch literal 3702 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 C8_;zC literal 19550 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 z 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 O0000KI`ZUu_FPdaza!>l`V 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 diff --git a/app/src/main/res/drawable/shape_badge.xml b/app/src/main/res/drawable/shape_badge.xml new file mode 100644 index 00000000..3153f592 --- /dev/null +++ b/app/src/main/res/drawable/shape_badge.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout-v31/widget_timetable_preview.xml b/app/src/main/res/layout-v31/widget_timetable_preview.xml new file mode 100644 index 00000000..bc556f50 --- /dev/null +++ b/app/src/main/res/layout-v31/widget_timetable_preview.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 11844e24..d14de50a 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,24 +1,31 @@ - + android:fitsSystemWindows="true" + app:layout_constraintBaseline_toTopOf="parent"> + + + + app:layout_constraintTop_toBottomOf="@id/main_app_bar" + tools:layout="@layout/fragment_dashboard" /> - + android:fitsSystemWindows="true" + app:layout_constraintBaseline_toTopOf="parent"> + + + + app:layout_constraintTop_toBottomOf="@id/send_app_bar"> + app:layout_constraintTop_toBottomOf="@id/send_app_bar" /> diff --git a/app/src/main/res/layout/dialog_account_edit.xml b/app/src/main/res/layout/dialog_account_edit.xml index 9f617e44..2ab4ccc6 100644 --- a/app/src/main/res/layout/dialog_account_edit.xml +++ b/app/src/main/res/layout/dialog_account_edit.xml @@ -1,19 +1,14 @@ - - + android:layout_height="match_parent"> - + android:paddingStart="24dp" + android:paddingEnd="24dp"> + android:textAppearance="?attr/textAppearanceHeadlineSmall" + android:textColor="?attr/colorOnSurface" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - + android:text="@string/additional_lessons_repeat" + app:layout_constraintTop_toBottomOf="@id/additionalLessonDialogDate" /> + android:hint="@string/all_subject" + app:layout_constraintTop_toBottomOf="@id/additionalLessonDialogEnd"> - + - - - - - + + diff --git a/app/src/main/res/layout/dialog_ads_consent.xml b/app/src/main/res/layout/dialog_ads_consent.xml index 81607478..118fb9c1 100644 --- a/app/src/main/res/layout/dialog_ads_consent.xml +++ b/app/src/main/res/layout/dialog_ads_consent.xml @@ -63,7 +63,7 @@ - - - + android:layout_height="match_parent"> - - + app:layout_constraintEnd_toStartOf="@+id/examDialogClose" /> diff --git a/app/src/main/res/layout/dialog_grade.xml b/app/src/main/res/layout/dialog_grade.xml index 94facb23..f47f6108 100644 --- a/app/src/main/res/layout/dialog_grade.xml +++ b/app/src/main/res/layout/dialog_grade.xml @@ -32,43 +32,57 @@ android:id="@+id/gradeDialogValue" android:layout_width="match_parent" android:layout_height="86dp" - android:layout_marginStart="0dp" android:layout_marginEnd="16dp" - android:background="@color/grade_material_default" + android:background="@drawable/background_grade_details_rounded" + android:backgroundTint="@color/grade_material_default" android:gravity="center" android:textColor="@android:color/white" android:textSize="30sp" tools:text="6" /> - + android:background="@drawable/background_grade_details_weight_rounded" + android:backgroundTint="@color/grade_black" + android:gravity="center_horizontal"> + + + + + + android:textAppearance="?attr/textAppearanceHeadlineSmall" + android:textColor="?attr/colorOnSurface" + android:textIsSelectable="true" /> + android:text="@string/all_no_description" + android:textAppearance="?attr/textAppearanceBodyLarge" + android:textColor="?attr/colorOnSurface" + android:textIsSelectable="true" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" /> + android:textAppearance="?attr/textAppearanceBodyLarge" + android:textColor="?attr/colorOnSurface" + android:textIsSelectable="true" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" /> + android:textAppearance="?attr/textAppearanceBodyLarge" + android:textColor="?attr/colorOnSurface" + android:textIsSelectable="true" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" /> + android:textAppearance="?attr/textAppearanceBodyLarge" + android:textColor="?attr/colorOnSurface" + android:textIsSelectable="true" /> + android:textAppearance="?attr/textAppearanceBodySmall" + android:textColor="?attr/colorOnSurfaceVariant" /> + android:textAppearance="?attr/textAppearanceBodyLarge" + android:textColor="?attr/colorOnSurface" + android:textIsSelectable="true" /> - - diff --git a/app/src/main/res/layout/dialog_homework.xml b/app/src/main/res/layout/dialog_homework.xml index 341cec54..8c6cf0a7 100644 --- a/app/src/main/res/layout/dialog_homework.xml +++ b/app/src/main/res/layout/dialog_homework.xml @@ -1,71 +1,56 @@ - + android:layout_height="wrap_content"> + android:background="@drawable/ic_all_divider" + app:layout_constraintTop_toBottomOf="@id/homeworkDialogRecycler" /> - + - - - - - + + diff --git a/app/src/main/res/layout/dialog_homework_add.xml b/app/src/main/res/layout/dialog_homework_add.xml index 524f0db0..e0ff5b74 100644 --- a/app/src/main/res/layout/dialog_homework_add.xml +++ b/app/src/main/res/layout/dialog_homework_add.xml @@ -2,37 +2,35 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" - android:fillViewport="true" - android:minWidth="300dp" - android:paddingStart="8dp" - android:paddingEnd="8dp"> + android:layout_height="match_parent"> - + android:paddingStart="24dp" + android:paddingEnd="24dp"> + android:textAppearance="?attr/textAppearanceHeadlineSmall" + android:textColor="?attr/colorOnSurface" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + android:hint="@string/all_subject" + app:layout_constraintTop_toBottomOf="@id/homeworkDialogDate"> + android:hint="@string/all_teacher" + app:layout_constraintTop_toBottomOf="@id/homeworkDialogSubject"> + android:hint="@string/all_content" + app:layout_constraintTop_toBottomOf="@id/homeworkDialogTeacher"> - + - - - - - + + diff --git a/app/src/main/res/layout/dialog_lesson_completed.xml b/app/src/main/res/layout/dialog_lesson_completed.xml index 500cdb6f..3a1d3fd0 100644 --- a/app/src/main/res/layout/dialog_lesson_completed.xml +++ b/app/src/main/res/layout/dialog_lesson_completed.xml @@ -1,4 +1,5 @@ + - - - - + - - + - - - - + app:layout_constraintTop_toBottomOf="@id/timetableDialogLessonValue" + tools:visibility="visible" /> + app:layout_constraintTop_toBottomOf="@id/timetableDialogTeacherValue" + tools:visibility="visible" /> + app:layout_constraintTop_toBottomOf="@id/timetableDialogRoomValue" + tools:visibility="visible" /> @@ -106,7 +105,7 @@ diff --git a/app/src/main/res/layout/fragment_login_advanced.xml b/app/src/main/res/layout/fragment_login_advanced.xml index c7acaa70..43016db4 100644 --- a/app/src/main/res/layout/fragment_login_advanced.xml +++ b/app/src/main/res/layout/fragment_login_advanced.xml @@ -97,7 +97,7 @@ + android:layout_height="0dp" + android:layout_weight="1"> @@ -173,4 +173,4 @@ app:srcCompat="@drawable/ic_chevron_right" app:tint="?colorPrimary" /> - + diff --git a/app/src/main/res/layout/fragment_timetable_completed.xml b/app/src/main/res/layout/fragment_timetable_completed.xml index e089275d..8d647ff6 100644 --- a/app/src/main/res/layout/fragment_timetable_completed.xml +++ b/app/src/main/res/layout/fragment_timetable_completed.xml @@ -93,7 +93,7 @@ + android:layout_marginVertical="6dp"> + android:layout_marginVertical="6dp"> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_dashboard_announcements.xml b/app/src/main/res/layout/item_dashboard_announcements.xml index 19f72088..b9ddb757 100644 --- a/app/src/main/res/layout/item_dashboard_announcements.xml +++ b/app/src/main/res/layout/item_dashboard_announcements.xml @@ -8,8 +8,7 @@ android:layout_marginVertical="6dp" android:clickable="true" android:focusable="true" - android:foreground="?selectableItemBackground" - app:cardElevation="4dp"> + android:foreground="?selectableItemBackground"> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_dashboard_conferences.xml b/app/src/main/res/layout/item_dashboard_conferences.xml index 02d3edfc..b02b8e18 100644 --- a/app/src/main/res/layout/item_dashboard_conferences.xml +++ b/app/src/main/res/layout/item_dashboard_conferences.xml @@ -8,8 +8,7 @@ android:layout_marginVertical="6dp" android:clickable="true" android:focusable="true" - android:foreground="?selectableItemBackground" - app:cardElevation="4dp"> + android:foreground="?selectableItemBackground"> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_dashboard_exams.xml b/app/src/main/res/layout/item_dashboard_exams.xml index 9cc98d79..84302403 100644 --- a/app/src/main/res/layout/item_dashboard_exams.xml +++ b/app/src/main/res/layout/item_dashboard_exams.xml @@ -8,8 +8,7 @@ android:layout_marginVertical="6dp" android:clickable="true" android:focusable="true" - android:foreground="?selectableItemBackground" - app:cardElevation="4dp"> + android:foreground="?selectableItemBackground"> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_dashboard_grades.xml b/app/src/main/res/layout/item_dashboard_grades.xml index 5cc9ce30..345d8a5e 100644 --- a/app/src/main/res/layout/item_dashboard_grades.xml +++ b/app/src/main/res/layout/item_dashboard_grades.xml @@ -6,8 +6,7 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="12dp" android:layout_marginVertical="6dp" - android:foreground="?selectableItemBackground" - app:cardElevation="4dp"> + android:foreground="?selectableItemBackground"> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_dashboard_homework.xml b/app/src/main/res/layout/item_dashboard_homework.xml index 975d66ef..b36afc57 100644 --- a/app/src/main/res/layout/item_dashboard_homework.xml +++ b/app/src/main/res/layout/item_dashboard_homework.xml @@ -8,8 +8,7 @@ android:layout_marginVertical="6dp" android:clickable="true" android:focusable="true" - android:foreground="?selectableItemBackground" - app:cardElevation="4dp"> + android:foreground="?selectableItemBackground"> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_dashboard_horizontal_group.xml b/app/src/main/res/layout/item_dashboard_horizontal_group.xml index 0c59d1eb..1c9246a1 100644 --- a/app/src/main/res/layout/item_dashboard_horizontal_group.xml +++ b/app/src/main/res/layout/item_dashboard_horizontal_group.xml @@ -13,7 +13,6 @@ android:layout_width="0dp" android:layout_height="44dp" android:layout_marginVertical="4dp" - app:cardElevation="4dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/dashboard_horizontal_group_item_message_container" app:layout_constraintHorizontal_chainStyle="spread_inside" @@ -81,7 +80,6 @@ android:layout_height="44dp" android:layout_marginVertical="4dp" android:layout_marginEnd="8dp" - app:cardElevation="4dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/dashboard_horizontal_group_item_attendance_container" app:layout_constraintStart_toEndOf="@id/dashboard_horizontal_group_item_lucky_container" @@ -154,7 +152,6 @@ android:layout_width="0dp" android:layout_height="44dp" android:layout_marginVertical="4dp" - app:cardElevation="4dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/dashboard_horizontal_group_item_message_container" @@ -221,7 +218,6 @@ android:layout_height="44dp" android:layout_marginVertical="4dp" android:visibility="gone" - app:cardElevation="4dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/dashboard_horizontal_group_item_message_container" diff --git a/app/src/main/res/layout/item_dashboard_lessons.xml b/app/src/main/res/layout/item_dashboard_lessons.xml index 9156c1a2..a40f17f2 100644 --- a/app/src/main/res/layout/item_dashboard_lessons.xml +++ b/app/src/main/res/layout/item_dashboard_lessons.xml @@ -5,11 +5,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="12dp" - android:layout_marginVertical="6dp" - android:clickable="true" - android:focusable="true" - android:foreground="?selectableItemBackground" - app:cardElevation="4dp"> + android:layout_marginVertical="6dp"> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_grade_details.xml b/app/src/main/res/layout/item_grade_details.xml index 2f3bd2de..6849e929 100644 --- a/app/src/main/res/layout/item_grade_details.xml +++ b/app/src/main/res/layout/item_grade_details.xml @@ -20,11 +20,12 @@ android:id="@+id/gradeItemValue" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:background="@color/grade_material_default" + android:background="@drawable/background_grade_rounded" + android:backgroundTint="@color/grade_material_default" android:gravity="center" android:maxLength="5" android:minWidth="45dp" - android:minHeight="40dp" + android:minHeight="45dp" android:textColor="@android:color/white" android:textSize="16sp" app:layout_constraintBottom_toBottomOf="@+id/gradeDetailsContainer" diff --git a/app/src/main/res/layout/item_homework_dialog_details.xml b/app/src/main/res/layout/item_homework_dialog_details.xml index 9d560ba5..1b1c1d39 100644 --- a/app/src/main/res/layout/item_homework_dialog_details.xml +++ b/app/src/main/res/layout/item_homework_dialog_details.xml @@ -13,16 +13,17 @@ android:orientation="horizontal"> + android:textAppearance="?attr/textAppearanceHeadlineSmall" + android:textColor="?attr/colorOnSurface" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - - - - - diff --git a/app/src/main/res/layout/item_message_chips.xml b/app/src/main/res/layout/item_message_chips.xml index da2e2031..c1f36c4d 100644 --- a/app/src/main/res/layout/item_message_chips.xml +++ b/app/src/main/res/layout/item_message_chips.xml @@ -21,29 +21,23 @@ + android:text="@string/message_chip_only_unread" /> + android:text="@string/message_chip_only_with_attachments" /> diff --git a/app/src/main/res/layout/item_notifications_center.xml b/app/src/main/res/layout/item_notifications_center.xml index 16a7ae0c..a2a67748 100644 --- a/app/src/main/res/layout/item_notifications_center.xml +++ b/app/src/main/res/layout/item_notifications_center.xml @@ -5,8 +5,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="8dp" - android:layout_marginTop="12dp" - app:cardElevation="4dp"> + android:layout_marginTop="12dp"> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_widget_timetable.xml b/app/src/main/res/layout/item_widget_timetable.xml index 899d7503..27c9db66 100644 --- a/app/src/main/res/layout/item_widget_timetable.xml +++ b/app/src/main/res/layout/item_widget_timetable.xml @@ -1,129 +1,112 @@ - + android:textAppearance="?attr/textAppearanceHeadline6" + android:textSize="24sp" + tools:text="1" + tools:textColor="?attr/colorTimetableChange" /> - - - + + android:textAppearance="?attr/textAppearanceBodySmall" + tools:text="08:00" /> + android:layout_marginTop="4dp" + android:textAppearance="?attr/textAppearanceBodySmall" + tools:text="09:45" /> + + + + - - + android:lines="1" + android:textAppearance="?attr/textAppearanceTitleMedium" + tools:text="Programowanie aplikacji mobilnych i desktopowych" /> + + + + + + + + - + + + + + diff --git a/app/src/main/res/layout/item_widget_timetable_dark.xml b/app/src/main/res/layout/item_widget_timetable_dark.xml deleted file mode 100644 index 06233244..00000000 --- a/app/src/main/res/layout/item_widget_timetable_dark.xml +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/item_widget_timetable_footer.xml b/app/src/main/res/layout/item_widget_timetable_footer.xml new file mode 100644 index 00000000..ef14da5d --- /dev/null +++ b/app/src/main/res/layout/item_widget_timetable_footer.xml @@ -0,0 +1,12 @@ + + diff --git a/app/src/main/res/layout/item_widget_timetable_small.xml b/app/src/main/res/layout/item_widget_timetable_small.xml deleted file mode 100644 index 1bf4072d..00000000 --- a/app/src/main/res/layout/item_widget_timetable_small.xml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/item_widget_timetable_small_dark.xml b/app/src/main/res/layout/item_widget_timetable_small_dark.xml deleted file mode 100644 index 50bbbd03..00000000 --- a/app/src/main/res/layout/item_widget_timetable_small_dark.xml +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_preference_switch.xml b/app/src/main/res/layout/layout_preference_switch.xml new file mode 100644 index 00000000..c4f8a6c2 --- /dev/null +++ b/app/src/main/res/layout/layout_preference_switch.xml @@ -0,0 +1,5 @@ + + diff --git a/app/src/main/res/layout/subitem_dashboard_grades.xml b/app/src/main/res/layout/subitem_dashboard_grades.xml index 9354be3d..c8165b95 100644 --- a/app/src/main/res/layout/subitem_dashboard_grades.xml +++ b/app/src/main/res/layout/subitem_dashboard_grades.xml @@ -23,7 +23,7 @@ android:id="@+id/dashboard_grades_subitem_grade_container" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="8dp" + android:layout_marginStart="2dp" android:layout_marginBottom="6dp" android:orientation="horizontal" app:layout_constraintBottom_toBottomOf="parent" @@ -36,4 +36,4 @@ android:visibility="gone" tools:visibility="visible" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/subitem_dashboard_small_grade.xml b/app/src/main/res/layout/subitem_dashboard_small_grade.xml index 986d9602..6800b72e 100644 --- a/app/src/main/res/layout/subitem_dashboard_small_grade.xml +++ b/app/src/main/res/layout/subitem_dashboard_small_grade.xml @@ -5,7 +5,8 @@ android:layout_width="wrap_content" android:layout_height="20dp" android:layout_marginStart="4dp" - android:background="@color/grade_material_default" + android:background="@drawable/background_grade_small_rounded" + android:backgroundTint="@color/grade_material_default" android:gravity="center" android:maxLength="5" android:minWidth="20dp" diff --git a/app/src/main/res/layout/widget_luckynumber.xml b/app/src/main/res/layout/widget_luckynumber.xml index 360a1970..fc8acc60 100644 --- a/app/src/main/res/layout/widget_luckynumber.xml +++ b/app/src/main/res/layout/widget_luckynumber.xml @@ -1,63 +1,46 @@ - + android:adjustViewBounds="true" + android:importantForAccessibility="no" + android:scaleType="fitCenter" + android:src="@drawable/shape_badge" + android:tint="?attr/colorSurface" + app:tint="?attr/colorSurface" + tools:ignore="UseAppTint" /> - - + android:layout_gravity="center" + android:text="17" + android:textColor="?attr/colorPrimary" + android:textSize="72sp" + android:textStyle="bold" + tools:ignore="HardcodedText" /> - - + + diff --git a/app/src/main/res/layout/widget_luckynumber_dark.xml b/app/src/main/res/layout/widget_luckynumber_dark.xml deleted file mode 100644 index def110de..00000000 --- a/app/src/main/res/layout/widget_luckynumber_dark.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/layout/widget_timetable.xml b/app/src/main/res/layout/widget_timetable.xml index 059bb741..3abc488e 100644 --- a/app/src/main/res/layout/widget_timetable.xml +++ b/app/src/main/res/layout/widget_timetable.xml @@ -1,99 +1,115 @@ - - + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingVertical="16dp"> - - - - - - - - + android:layout_marginStart="8dp" + android:layout_weight="1" + android:lines="1" + android:textAppearance="?attr/textAppearanceHeadline5" + tools:text="Pon, 12.05" /> + android:tint="?attr/colorPrimary" + app:tint="?attr/colorPrimary" + tools:ignore="UseAppTint" /> - + android:tint="?attr/colorPrimary" + app:tint="?attr/colorPrimary" + tools:ignore="UseAppTint" /> - + + + + + + + + + android:layout_height="match_parent"> - - + + + + + diff --git a/app/src/main/res/layout/widget_timetable_dark.xml b/app/src/main/res/layout/widget_timetable_dark.xml deleted file mode 100644 index 9c8b8c56..00000000 --- a/app/src/main/res/layout/widget_timetable_dark.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/menu/action_menu_dashboard.xml b/app/src/main/res/menu/action_menu_dashboard.xml index 13565a19..71203d32 100644 --- a/app/src/main/res/menu/action_menu_dashboard.xml +++ b/app/src/main/res/menu/action_menu_dashboard.xml @@ -6,13 +6,13 @@ android:icon="@drawable/ic_settings_notifications" android:orderInCategory="1" android:title="@string/notifications_center_title" - app:iconTint="@color/material_on_surface_emphasis_medium" + app:iconTint="?colorControlNormal" app:showAsAction="ifRoom" /> - \ No newline at end of file + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index da1bca12..a8699eec 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,6 @@ - + diff --git a/app/src/main/res/values-night-v31/styles.xml b/app/src/main/res/values-night-v31/styles.xml new file mode 100644 index 00000000..067a4353 --- /dev/null +++ b/app/src/main/res/values-night-v31/styles.xml @@ -0,0 +1,65 @@ + + + + + + + + diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml index 881d5bd4..7d2f0cfe 100644 --- a/app/src/main/res/values-night/styles.xml +++ b/app/src/main/res/values-night/styles.xml @@ -1,20 +1,41 @@ - - - - - - + + diff --git a/app/src/main/res/values-v23/styles.xml b/app/src/main/res/values-v23/styles.xml index 840f5357..95450ae1 100644 --- a/app/src/main/res/values-v23/styles.xml +++ b/app/src/main/res/values-v23/styles.xml @@ -3,6 +3,11 @@ - \ No newline at end of file + + + diff --git a/app/src/main/res/values-v26/styles.xml b/app/src/main/res/values-v26/styles.xml deleted file mode 100644 index 3fb0a5dd..00000000 --- a/app/src/main/res/values-v26/styles.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/app/src/main/res/values-v27/styles.xml b/app/src/main/res/values-v27/styles.xml new file mode 100644 index 00000000..1cbe9791 --- /dev/null +++ b/app/src/main/res/values-v27/styles.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/app/src/main/res/values-v28/styles.xml b/app/src/main/res/values-v28/styles.xml deleted file mode 100644 index a936566f..00000000 --- a/app/src/main/res/values-v28/styles.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/values-v29/styles.xml b/app/src/main/res/values-v29/styles.xml deleted file mode 100644 index a936566f..00000000 --- a/app/src/main/res/values-v29/styles.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/values-v31/styles.xml b/app/src/main/res/values-v31/styles.xml new file mode 100644 index 00000000..cffb284e --- /dev/null +++ b/app/src/main/res/values-v31/styles.xml @@ -0,0 +1,60 @@ + + + + + + + - - - - + + diff --git a/app/src/main/res/xml/provider_widget_lucky_number.xml b/app/src/main/res/xml/provider_widget_lucky_number.xml index 064f2057..330bd53f 100644 --- a/app/src/main/res/xml/provider_widget_lucky_number.xml +++ b/app/src/main/res/xml/provider_widget_lucky_number.xml @@ -4,11 +4,13 @@ android:configure="io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity" android:initialLayout="@layout/widget_luckynumber" android:minWidth="110dp" - android:minHeight="40dp" - android:minResizeWidth="40dp" + android:minHeight="110dp" android:minResizeHeight="40dp" android:previewImage="@drawable/img_luckynumber_widget_preview" + android:previewLayout="@layout/widget_luckynumber" android:resizeMode="horizontal|vertical" + android:targetCellWidth="2" + android:targetCellHeight="2" android:updatePeriodMillis="3600000" android:widgetCategory="home_screen" - tools:targetApi="jelly_bean_mr1" /> + tools:targetApi="s" /> diff --git a/app/src/main/res/xml/provider_widget_timetable.xml b/app/src/main/res/xml/provider_widget_timetable.xml index 5392dd50..3cdad0c8 100644 --- a/app/src/main/res/xml/provider_widget_timetable.xml +++ b/app/src/main/res/xml/provider_widget_timetable.xml @@ -8,7 +8,10 @@ android:minResizeWidth="250dp" android:minResizeHeight="110dp" android:previewImage="@drawable/img_timetable_widget_preview" + android:previewLayout="@layout/widget_timetable_preview" android:resizeMode="horizontal|vertical" + android:targetCellWidth="3" + android:targetCellHeight="2" android:updatePeriodMillis="3600000" android:widgetCategory="home_screen" - tools:targetApi="jelly_bean_mr1" /> + tools:targetApi="s" />