Add menu order settings (#1924)

This commit is contained in:
Rafał Borcz 2023-01-14 17:06:47 +01:00 committed by GitHub
parent 6df3f22c7d
commit 32d6b4a7a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 918 additions and 216 deletions

View File

@ -10,6 +10,7 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.enums.*
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.serialization.decodeFromString
@ -28,9 +29,6 @@ class PreferencesRepository @Inject constructor(
private val json: Json,
) {
val startMenuIndex: Int
get() = getString(R.string.pref_key_start_menu, R.string.pref_default_startup).toInt()
val isShowPresent: Boolean
get() = getBoolean(
R.string.pref_key_attendance_present,
@ -315,6 +313,20 @@ class PreferencesRepository @Inject constructor(
putBoolean(context.getString(R.string.pref_key_ads_enabled), value)
}
var appMenuItemOrder: List<AppMenuItem>
get() {
val value = sharedPref.getString(PREF_KEY_APP_MENU_ITEM_ORDER, null)
?: return AppMenuItem.defaultAppMenuItemList
return json.decodeFromString(value)
}
set(value) = sharedPref.edit {
putString(
PREF_KEY_APP_MENU_ITEM_ORDER,
json.encodeToString(value)
)
}
var installationId: String
get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty()
private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) }
@ -341,6 +353,7 @@ class PreferencesRepository @Inject constructor(
sharedPref.getBoolean(id, context.resources.getBoolean(default))
private companion object {
private const val PREF_KEY_APP_MENU_ITEM_ORDER = "app_menu_item_order"
private const val PREF_KEY_INSTALLATION_ID = "installation_id"
private const val PREF_KEY_DASHBOARD_ITEMS_POSITION = "dashboard_items_position"
private const val PREF_KEY_IN_APP_REVIEW_COUNT = "in_app_review_count"

View File

@ -10,10 +10,12 @@ 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.message.MessageFragment
import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment
import io.github.wulkanowy.ui.modules.more.MoreFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.schoolandteachers.school.SchoolFragment
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import kotlinx.serialization.Serializable
import java.time.LocalDate
@ -39,10 +41,12 @@ sealed class Destination {
NOTE(Note),
CONFERENCE(Conference),
SCHOOL_ANNOUNCEMENT(SchoolAnnouncement),
SCHOOL(School),
LUCKY_NUMBER(More),
SCHOOL_AND_TEACHERS(SchoolAndTeachers),
LUCKY_NUMBER(LuckyNumber),
MORE(More),
MESSAGE(Message);
MESSAGE(Message),
MOBILE_DEVICE(MobileDevice),
SETTINGS(Settings);
}
@Serializable
@ -103,9 +107,9 @@ sealed class Destination {
}
@Serializable
object School : Destination() {
override val destinationType get() = Type.SCHOOL
override val destinationFragment get() = SchoolFragment.newInstance()
object SchoolAndTeachers : Destination() {
override val destinationType get() = Type.SCHOOL_AND_TEACHERS
override val destinationFragment get() = SchoolAndTeachersFragment.newInstance()
}
@Serializable
@ -125,4 +129,16 @@ sealed class Destination {
override val destinationType get() = Type.MESSAGE
override val destinationFragment get() = MessageFragment.newInstance()
}
@Serializable
object MobileDevice : Destination() {
override val destinationType get() = Type.MOBILE_DEVICE
override val destinationFragment get() = MobileDeviceFragment.newInstance()
}
@Serializable
object Settings : Destination() {
override val destinationType get() = Type.SETTINGS
override val destinationFragment get() = SettingsFragment.newInstance()
}
}

View File

@ -16,7 +16,7 @@ import javax.inject.Inject
@AndroidEntryPoint
class ConferenceFragment : BaseFragment<FragmentConferenceBinding>(R.layout.fragment_conference),
ConferenceView, MainView.TitledView {
ConferenceView, MainView.TitledView, MainView.MainChildView {
@Inject
lateinit var presenter: ConferencePresenter
@ -109,6 +109,14 @@ class ConferenceFragment : BaseFragment<FragmentConferenceBinding>(R.layout.frag
(activity as? MainActivity)?.showDialogFragment(ConferenceDialog.newInstance(conference))
}
override fun onFragmentReselected() {
if (::presenter.isInitialized) presenter.onFragmentReselected()
}
override fun resetView() {
binding.conferenceRecycler.smoothScrollToPosition(0)
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()

View File

@ -96,4 +96,11 @@ class ConferencePresenter @Inject constructor(
.onResourceError(errorHandler::dispatch)
.launch()
}
fun onFragmentReselected() {
Timber.i("Conference is reselected")
if (view?.isViewEmpty == false) {
view?.resetView()
}
}
}

View File

@ -28,4 +28,6 @@ interface ConferenceView : BaseView {
fun showContent(show: Boolean)
fun openConferenceDialog(conference: Conference)
fun resetView()
}

View File

@ -2,9 +2,7 @@ package io.github.wulkanowy.ui.modules.exam
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
@ -20,7 +18,7 @@ import javax.inject.Inject
@AndroidEntryPoint
class ExamFragment : BaseFragment<FragmentExamBinding>(R.layout.fragment_exam), ExamView,
MainView.TitledView {
MainView.TitledView, MainView.MainChildView {
@Inject
lateinit var presenter: ExamPresenter
@ -126,6 +124,14 @@ class ExamFragment : BaseFragment<FragmentExamBinding>(R.layout.fragment_exam),
(activity as? MainActivity)?.showDialogFragment(ExamDialog.newInstance(exam))
}
override fun onFragmentReselected() {
if (::presenter.isInitialized) presenter.onViewReselected()
}
override fun resetView() {
binding.examRecycler.smoothScrollToPosition(0)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())

View File

@ -175,4 +175,17 @@ class ExamPresenter @Inject constructor(
)
}
}
fun onViewReselected() {
Timber.i("Exam view is reselected")
baseDate = now().nextOrSameSchoolDay
if (currentDate != baseDate) {
reloadView(baseDate)
loadData()
} else if (view?.isViewEmpty == false) {
view?.resetView()
}
}
}

View File

@ -34,4 +34,6 @@ interface ExamView : BaseView {
fun showPreButton(show: Boolean)
fun showExamDialog(exam: Exam)
fun resetView()
}

View File

@ -21,7 +21,7 @@ import javax.inject.Inject
@AndroidEntryPoint
class HomeworkFragment : BaseFragment<FragmentHomeworkBinding>(R.layout.fragment_homework),
HomeworkView, MainView.TitledView {
HomeworkView, MainView.TitledView, MainView.MainChildView {
@Inject
lateinit var presenter: HomeworkPresenter
@ -133,6 +133,14 @@ class HomeworkFragment : BaseFragment<FragmentHomeworkBinding>(R.layout.fragment
(activity as? MainActivity)?.showDialogFragment(HomeworkAddDialog())
}
override fun onFragmentReselected() {
if (::presenter.isInitialized) presenter.onViewReselected()
}
override fun resetView() {
binding.homeworkRecycler.smoothScrollToPosition(0)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())

View File

@ -177,8 +177,21 @@ class HomeworkPresenter @Inject constructor(
showNextButton(!currentDate.plusDays(7).isHolidays)
updateNavigationWeek(
"${currentDate.monday.toFormattedString("dd.MM")} - " +
currentDate.sunday.toFormattedString("dd.MM")
currentDate.sunday.toFormattedString("dd.MM")
)
}
}
fun onViewReselected() {
Timber.i("Homework view is reselected")
baseDate = LocalDate.now().nextOrSameSchoolDay
if (currentDate != baseDate) {
reloadView(baseDate)
loadData()
} else if (view?.isViewEmpty == false) {
view?.resetView()
}
}
}

View File

@ -36,4 +36,6 @@ interface HomeworkView : BaseView {
fun showHomeworkDialog(homework: Homework)
fun showAddHomeworkDialog()
fun resetView()
}

View File

@ -18,7 +18,7 @@ import javax.inject.Inject
@AndroidEntryPoint
class LuckyNumberFragment :
BaseFragment<FragmentLuckyNumberBinding>(R.layout.fragment_lucky_number), LuckyNumberView,
MainView.TitledView {
MainView.TitledView, MainView.MainChildView {
@Inject
lateinit var presenter: LuckyNumberPresenter
@ -86,6 +86,14 @@ class LuckyNumberFragment :
(activity as? MainActivity)?.pushView(LuckyNumberHistoryFragment.newInstance())
}
override fun onFragmentReselected() {
if (::presenter.isInitialized) presenter.onViewReselected()
}
override fun popView() {
(activity as? MainActivity)?.popView()
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()

View File

@ -99,4 +99,9 @@ class LuckyNumberPresenter @Inject constructor(
fun onDetailsClick() {
view?.showErrorDetailsDialog(lastError)
}
fun onViewReselected() {
Timber.i("Luckynumber view is reselected")
view?.popView()
}
}

View File

@ -26,4 +26,6 @@ interface LuckyNumberView : BaseView {
fun showContent(show: Boolean)
fun openLuckyNumberHistory()
fun popView()
}

View File

@ -27,6 +27,7 @@ import io.github.wulkanowy.databinding.DialogAdsConsentBinding
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem
import io.github.wulkanowy.utils.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@ -124,13 +125,20 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
return true
}
override fun initView(startMenuIndex: Int, rootDestinations: List<Destination>) {
override fun initView(
startMenuIndex: Int,
rootAppMenuItems: List<AppMenuItem>,
rootUpdatedDestinations: List<Destination>
) {
initializeToolbar()
initializeBottomNavigation(startMenuIndex)
initializeNavController(startMenuIndex, rootDestinations)
initializeBottomNavigation(startMenuIndex, rootAppMenuItems)
initializeNavController(startMenuIndex, rootUpdatedDestinations)
}
private fun initializeNavController(startMenuIndex: Int, rootDestinations: List<Destination>) {
private fun initializeNavController(
startMenuIndex: Int,
rootUpdatedDestinations: List<Destination>
) {
with(navController) {
setOnViewChangeListener { destinationView ->
presenter.onViewChange(destinationView)
@ -140,7 +148,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
)
}
fragmentHideStrategy = HIDE
rootFragments = rootDestinations.map { it.destinationFragment }
rootFragments = rootUpdatedDestinations.map { it.destinationFragment }
initialize(startMenuIndex, savedInstanceState)
}
@ -156,17 +164,16 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
}
}
private fun initializeBottomNavigation(startMenuIndex: Int) {
private fun initializeBottomNavigation(
startMenuIndex: Int,
rootAppMenuItems: List<AppMenuItem>
) {
with(binding.mainBottomNav) {
with(menu) {
add(Menu.NONE, 0, Menu.NONE, R.string.dashboard_title)
.setIcon(R.drawable.ic_main_dashboard)
add(Menu.NONE, 1, Menu.NONE, R.string.grade_title)
.setIcon(R.drawable.ic_main_grade)
add(Menu.NONE, 2, Menu.NONE, R.string.attendance_title)
.setIcon(R.drawable.ic_main_attendance)
add(Menu.NONE, 3, Menu.NONE, R.string.timetable_title)
.setIcon(R.drawable.ic_main_timetable)
rootAppMenuItems.forEachIndexed { index, item ->
add(Menu.NONE, index, Menu.NONE, item.title)
.setIcon(item.icon)
}
add(Menu.NONE, 4, Menu.NONE, R.string.more_title)
.setIcon(R.drawable.ic_main_more)
}

View File

@ -42,17 +42,16 @@ class MainPresenter @Inject constructor(
private var studentsWitSemesters: List<StudentWithSemesters>? = null
private val rootDestinationTypeList = listOf(
Destination.Type.DASHBOARD,
Destination.Type.GRADE,
Destination.Type.ATTENDANCE,
Destination.Type.TIMETABLE,
Destination.Type.MORE
)
private val rootAppMenuItems = preferencesRepository.appMenuItemOrder
.sortedBy { it.order }
.take(4)
private val rootDestinationTypeList = rootAppMenuItems.map { it.destinationType }
.plus(Destination.Type.MORE)
private val Destination?.startMenuIndex
get() = when {
this == null -> preferencesRepository.startMenuIndex
this == null -> 0
destinationType in rootDestinationTypeList -> {
rootDestinationTypeList.indexOf(destinationType)
}
@ -69,7 +68,7 @@ class MainPresenter @Inject constructor(
if (it == initDestination?.destinationType) initDestination else it.defaultDestination
}
view.initView(startMenuIndex, destinations)
view.initView(startMenuIndex, rootAppMenuItems, destinations)
if (initDestination != null && startMenuIndex == 4) {
view.openMoreDestination(initDestination)
}

View File

@ -4,6 +4,7 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem
interface MainView : BaseView {
@ -15,7 +16,11 @@ interface MainView : BaseView {
val currentStackSize: Int?
fun initView(startMenuIndex: Int, rootDestinations: List<Destination>)
fun initView(
startMenuIndex: Int,
rootAppMenuItems: List<AppMenuItem>,
rootUpdatedDestinations: List<Destination>
)
fun switchMenuView(position: Int)

View File

@ -15,6 +15,7 @@ import io.github.wulkanowy.data.enums.MessageFolder.*
import io.github.wulkanowy.databinding.FragmentMessageBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
import io.github.wulkanowy.ui.modules.message.tab.MessageTabFragment
@ -24,7 +25,7 @@ import javax.inject.Inject
@AndroidEntryPoint
class MessageFragment : BaseFragment<FragmentMessageBinding>(R.layout.fragment_message),
MessageView, MainView.TitledView {
MessageView, MainView.TitledView, MainView.MainChildView {
@Inject
lateinit var presenter: MessagePresenter
@ -123,8 +124,12 @@ class MessageFragment : BaseFragment<FragmentMessageBinding>(R.layout.fragment_m
presenter.onChildViewShowNewMessage(show)
}
fun onFragmentChanged() {
presenter.onFragmentChanged()
override fun onFragmentReselected() {
if (::presenter.isInitialized) presenter.onFragmentReselected()
}
override fun onFragmentChanged() {
if (::presenter.isInitialized) presenter.onFragmentChanged()
}
override fun notifyChildLoadData(index: Int, forceRefresh: Boolean) {
@ -139,10 +144,19 @@ class MessageFragment : BaseFragment<FragmentMessageBinding>(R.layout.fragment_m
}
}
override fun notifyChildParentReselected(index: Int) {
(pagerAdapter.getFragmentInstance(index) as? MessageTabFragment)
?.onParentReselected()
}
override fun openSendMessage() {
context?.let { it.startActivity(SendMessageActivity.getStartIntent(it)) }
}
override fun popView() {
(activity as? MainActivity)?.popView()
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()

View File

@ -39,6 +39,14 @@ class MessagePresenter @Inject constructor(
view?.notifyChildrenFinishActionMode()
}
fun onFragmentReselected() {
Timber.i("Message view is reselected")
view?.run {
popView()
notifyChildParentReselected(currentPageIndex)
}
}
fun onChildViewLoaded() {
view?.apply {
showContent(true)

View File

@ -20,5 +20,9 @@ interface MessageView : BaseView {
fun notifyChildrenFinishActionMode()
fun notifyChildParentReselected(index: Int)
fun openSendMessage()
fun popView()
}

View File

@ -235,6 +235,10 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
presenter.onParentFinishActionMode()
}
fun onParentReselected() {
presenter.onParentReselected()
}
private fun onChipChecked(chip: CompoundButton, isChecked: Boolean) {
when (chip.id) {
R.id.chip_unread -> presenter.onUnreadFilterSelected(isChecked)

View File

@ -83,6 +83,14 @@ class MessageTabPresenter @Inject constructor(
view?.showActionMode(false)
}
fun onParentReselected() {
view?.run {
if (!isViewEmpty) {
resetListPosition()
}
}
}
fun onDestroyActionMode() {
isActionMode = false
messagesToDelete.clear()

View File

@ -22,7 +22,7 @@ import javax.inject.Inject
@AndroidEntryPoint
class MobileDeviceFragment :
BaseFragment<FragmentMobileDeviceBinding>(R.layout.fragment_mobile_device), MobileDeviceView,
MainView.TitledView {
MainView.TitledView, MainView.MainChildView {
@Inject
lateinit var presenter: MobileDevicePresenter
@ -135,6 +135,14 @@ class MobileDeviceFragment :
(activity as? MainActivity)?.showDialogFragment(MobileDeviceTokenDialog.newInstance())
}
override fun onFragmentReselected() {
if (::presenter.isInitialized) presenter.onFragmentReselected()
}
override fun resetView() {
binding.mobileDevicesRecycler.smoothScrollToPosition(0)
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()

View File

@ -129,4 +129,10 @@ class MobileDevicePresenter @Inject constructor(
.onResourceError(errorHandler::dispatch)
.launch("unregister")
}
fun onFragmentReselected() {
if (view?.isViewEmpty == false) {
view?.resetView()
}
}
}

View File

@ -32,4 +32,6 @@ interface MobileDeviceView : BaseView {
fun setErrorDetails(message: String)
fun showTokenDialog()
fun resetView()
}

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.more
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
@ -9,9 +8,9 @@ import javax.inject.Inject
class MoreAdapter @Inject constructor() : RecyclerView.Adapter<MoreAdapter.ItemViewHolder>() {
var items = emptyList<Pair<String, Drawable?>>()
var items = emptyList<MoreItem>()
var onClickListener: (name: String) -> Unit = {}
var onClickListener: (moreItem: MoreItem) -> Unit = {}
override fun getItemCount() = items.size
@ -20,13 +19,14 @@ class MoreAdapter @Inject constructor() : RecyclerView.Adapter<MoreAdapter.ItemV
)
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val (title, drawable) = items[position]
val item = items[position]
val context = holder.binding.root.context
with(holder.binding) {
moreItemTitle.text = title
moreItemImage.setImageDrawable(drawable)
moreItemTitle.text = context.getString(item.title)
moreItemImage.setImageResource(item.icon)
root.setOnClickListener { onClickListener(title) }
root.setOnClickListener { onClickListener(item) }
}
}

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.more
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
@ -8,19 +7,10 @@ import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.databinding.FragmentMoreBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
import io.github.wulkanowy.ui.modules.exam.ExamFragment
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
import io.github.wulkanowy.utils.getCompatDrawable
import javax.inject.Inject
@AndroidEntryPoint
@ -40,36 +30,6 @@ class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more),
override val titleStringId: Int
get() = R.string.more_title
override val messagesRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.message_title) to getCompatDrawable(R.drawable.ic_more_messages) }
override val homeworkRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.homework_title) to getCompatDrawable(R.drawable.ic_more_homework) }
override val noteRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.note_title) to getCompatDrawable(R.drawable.ic_more_note) }
override val conferencesRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.conferences_title) to getCompatDrawable(R.drawable.ic_more_conferences) }
override val schoolAnnouncementRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.school_announcement_title) to getCompatDrawable(R.drawable.ic_all_about) }
override val schoolAndTeachersRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.schoolandteachers_title) to getCompatDrawable((R.drawable.ic_more_schoolandteachers)) }
override val mobileDevicesRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.mobile_devices_title) to getCompatDrawable(R.drawable.ic_more_mobile_devices) }
override val settingsRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.settings_title) to getCompatDrawable(R.drawable.ic_more_settings) }
override val examRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.exam_title) to getCompatDrawable(R.drawable.ic_main_exam) }
override val luckyNumberRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.lucky_number_title) to getCompatDrawable(R.drawable.ic_more_lucky_number) }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentMoreBinding.bind(view)
@ -94,57 +54,21 @@ class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more),
?.onFragmentChanged()
}
override fun updateData(data: List<Pair<String, Drawable?>>) {
override fun updateData(data: List<MoreItem>) {
with(moreAdapter) {
items = data
notifyDataSetChanged()
}
}
override fun openMessagesView() {
(activity as? MainActivity)?.pushView(MessageFragment.newInstance())
}
override fun openHomeworkView() {
(activity as? MainActivity)?.pushView(HomeworkFragment.newInstance())
}
override fun openNoteView() {
(activity as? MainActivity)?.pushView(NoteFragment.newInstance())
}
override fun openSchoolAnnouncementView() {
(activity as? MainActivity)?.pushView(SchoolAnnouncementFragment.newInstance())
}
override fun openConferencesView() {
(activity as? MainActivity)?.pushView(ConferenceFragment.newInstance())
}
override fun openSchoolAndTeachersView() {
(activity as? MainActivity)?.pushView(SchoolAndTeachersFragment.newInstance())
}
override fun openMobileDevicesView() {
(activity as? MainActivity)?.pushView(MobileDeviceFragment.newInstance())
}
override fun openSettingsView() {
(activity as? MainActivity)?.pushView(SettingsFragment.newInstance())
}
override fun openExamView() {
(activity as? MainActivity)?.pushView(ExamFragment.newInstance())
}
override fun openLuckyNumberView() {
(activity as? MainActivity)?.pushView(LuckyNumberFragment.newInstance())
}
override fun popView(depth: Int) {
(activity as? MainActivity)?.popView(depth)
}
override fun openView(destination: Destination) {
(activity as? MainActivity)?.pushView(destination.destinationFragment)
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()

View File

@ -0,0 +1,12 @@
package io.github.wulkanowy.ui.modules.more
import io.github.wulkanowy.ui.modules.Destination
data class MoreItem(
val icon: Int,
val title: Int,
val destination: Destination
)

View File

@ -1,16 +1,24 @@
package io.github.wulkanowy.ui.modules.more
import io.github.wulkanowy.R
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.Destination
import timber.log.Timber
import javax.inject.Inject
class MorePresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository
studentRepository: StudentRepository,
preferencesRepository: PreferencesRepository
) : BasePresenter<MoreView>(errorHandler, studentRepository) {
private val moreAppMenuItem = preferencesRepository.appMenuItemOrder
.sortedBy { it.order }
.drop(4)
override fun onAttachView(view: MoreView) {
super.onAttachView(view)
view.initView()
@ -18,22 +26,10 @@ class MorePresenter @Inject constructor(
loadData()
}
fun onItemSelected(title: String) {
Timber.i("Select more item \"${title}\"")
view?.run {
when (title) {
messagesRes?.first -> openMessagesView()
examRes?.first -> openExamView()
homeworkRes?.first -> openHomeworkView()
noteRes?.first -> openNoteView()
conferencesRes?.first -> openConferencesView()
schoolAnnouncementRes?.first -> openSchoolAnnouncementView()
schoolAndTeachersRes?.first -> openSchoolAndTeachersView()
mobileDevicesRes?.first -> openMobileDevicesView()
settingsRes?.first -> openSettingsView()
luckyNumberRes?.first -> openLuckyNumberView()
}
}
fun onItemSelected(moreItem: MoreItem) {
Timber.i("Select more item \"${moreItem.destination.destinationType}\"")
view?.openView(moreItem.destination)
}
fun onViewReselected() {
@ -43,19 +39,21 @@ class MorePresenter @Inject constructor(
private fun loadData() {
Timber.i("Load items for more view")
view?.run {
updateData(listOfNotNull(
messagesRes,
examRes,
homeworkRes,
noteRes,
luckyNumberRes,
conferencesRes,
schoolAnnouncementRes,
schoolAndTeachersRes,
mobileDevicesRes,
settingsRes
))
val moreItems = moreAppMenuItem.map {
MoreItem(
icon = it.icon,
title = it.title,
destination = it.destinationType.defaultDestination
)
}
.plus(
MoreItem(
icon = R.drawable.ic_more_settings,
title = R.string.settings_title,
destination = Destination.Settings
)
)
view?.updateData(moreItems)
}
}

View File

@ -1,53 +1,15 @@
package io.github.wulkanowy.ui.modules.more
import android.graphics.drawable.Drawable
import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.modules.Destination
interface MoreView : BaseView {
val messagesRes: Pair<String, Drawable?>?
val homeworkRes: Pair<String, Drawable?>?
val noteRes: Pair<String, Drawable?>?
val conferencesRes: Pair<String, Drawable?>?
val schoolAnnouncementRes: Pair<String, Drawable?>?
val schoolAndTeachersRes: Pair<String, Drawable?>?
val mobileDevicesRes: Pair<String, Drawable?>?
val settingsRes: Pair<String, Drawable?>?
val examRes: Pair<String, Drawable?>?
val luckyNumberRes: Pair<String, Drawable?>?
fun initView()
fun updateData(data: List<Pair<String, Drawable?>>)
fun openSettingsView()
fun updateData(data: List<MoreItem>)
fun popView(depth: Int)
fun openMessagesView()
fun openHomeworkView()
fun openNoteView()
fun openSchoolAnnouncementView()
fun openConferencesView()
fun openSchoolAndTeachersView()
fun openMobileDevicesView()
fun openExamView()
fun openLuckyNumberView()
fun openView(destination: Destination)
}

View File

@ -18,7 +18,7 @@ import javax.inject.Inject
@AndroidEntryPoint
class NoteFragment : BaseFragment<FragmentNoteBinding>(R.layout.fragment_note), NoteView,
MainView.TitledView {
MainView.TitledView, MainView.MainChildView {
@Inject
lateinit var presenter: NotePresenter
@ -112,6 +112,14 @@ class NoteFragment : BaseFragment<FragmentNoteBinding>(R.layout.fragment_note),
binding.noteSwipe.isRefreshing = show
}
override fun onFragmentReselected() {
if (::presenter.isInitialized) presenter.onFragmentReselected()
}
override fun resetView() {
binding.noteRecycler.smoothScrollToPosition(0)
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()

View File

@ -121,4 +121,10 @@ class NotePresenter @Inject constructor(
}
.launch("update_note")
}
fun onFragmentReselected() {
if (view?.isViewEmpty == false) {
view?.resetView()
}
}
}

View File

@ -30,4 +30,6 @@ interface NoteView : BaseView {
fun showRefresh(show: Boolean)
fun showNoteDialog(note: Note)
fun resetView()
}

View File

@ -0,0 +1,206 @@
package io.github.wulkanowy.ui.modules.settings.appearance.menuorder
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.modules.Destination
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
@Serializable
sealed class AppMenuItem {
companion object {
val defaultAppMenuItemList = setOf(
DashboardAppMenuItem(),
GradeAppMenuItem(),
TimetableAppMenuItem(),
AttendanceAppMenuItem(),
ExamsAppMenuItem(),
HomeworkAppMenuItem(),
NoteAppMenuItem(),
LuckyNumberAppMenuItem(),
SchoolAnnouncementsAppMenuItem(),
SchoolAndTeachersAppMenuItem(),
MobileDevicesAppMenuItem(),
ConferenceAppMenuItem(),
MessageAppMenuItem()
).sortedBy { it.order }
}
// https://youtrack.jetbrains.com/issue/KT-38958
abstract var order: Int
abstract val icon: Int
abstract val title: Int
abstract val destinationType: Destination.Type
@Serializable
data class DashboardAppMenuItem(override var order: Int = 0) : AppMenuItem() {
@Transient
override val icon = R.drawable.ic_main_dashboard
@Transient
override val title = R.string.dashboard_title
@Transient
override val destinationType = Destination.Type.DASHBOARD
}
@Serializable
data class GradeAppMenuItem(override var order: Int = 1) : AppMenuItem() {
@Transient
override val icon = R.drawable.ic_main_grade
@Transient
override val title = R.string.grade_title
@Transient
override val destinationType = Destination.Type.GRADE
}
@Serializable
data class AttendanceAppMenuItem(override var order: Int = 2) : AppMenuItem() {
@Transient
override val icon = R.drawable.ic_main_attendance
@Transient
override val title = R.string.attendance_title
@Transient
override val destinationType = Destination.Type.ATTENDANCE
}
@Serializable
data class TimetableAppMenuItem(override var order: Int = 3) : AppMenuItem() {
@Transient
override val icon = R.drawable.ic_main_timetable
@Transient
override val title = R.string.timetable_title
@Transient
override val destinationType = Destination.Type.TIMETABLE
}
@Serializable
data class MessageAppMenuItem(override var order: Int = 4) : AppMenuItem() {
@Transient
override val icon = R.drawable.ic_more_messages
@Transient
override val title = R.string.message_title
@Transient
override val destinationType = Destination.Type.MESSAGE
}
@Serializable
data class ExamsAppMenuItem(override var order: Int = 5) : AppMenuItem() {
@Transient
override val icon = R.drawable.ic_main_exam
@Transient
override val title = R.string.exam_title
@Transient
override val destinationType = Destination.Type.EXAM
}
@Serializable
data class HomeworkAppMenuItem(override var order: Int = 6) : AppMenuItem() {
@Transient
override val icon = R.drawable.ic_more_homework
@Transient
override val title = R.string.homework_title
@Transient
override val destinationType = Destination.Type.HOMEWORK
}
@Serializable
data class NoteAppMenuItem(override var order: Int = 7) : AppMenuItem() {
@Transient
override val icon = R.drawable.ic_more_note
@Transient
override val title = R.string.note_title
@Transient
override val destinationType = Destination.Type.NOTE
}
@Serializable
data class LuckyNumberAppMenuItem(override var order: Int = 8) : AppMenuItem() {
@Transient
override val icon = R.drawable.ic_more_lucky_number
@Transient
override val title = R.string.lucky_number_title
@Transient
override val destinationType = Destination.Type.LUCKY_NUMBER
}
@Serializable
data class ConferenceAppMenuItem(override var order: Int = 9) : AppMenuItem() {
@Transient
override val icon = R.drawable.ic_more_conferences
@Transient
override val title = R.string.conferences_title
@Transient
override val destinationType = Destination.Type.CONFERENCE
}
@Serializable
data class SchoolAnnouncementsAppMenuItem(override var order: Int = 10) : AppMenuItem() {
@Transient
override val icon = R.drawable.ic_all_about
@Transient
override val title = R.string.school_announcement_title
@Transient
override val destinationType = Destination.Type.SCHOOL_ANNOUNCEMENT
}
@Serializable
data class SchoolAndTeachersAppMenuItem(override var order: Int = 11) : AppMenuItem() {
@Transient
override val icon = R.drawable.ic_more_schoolandteachers
@Transient
override val title = R.string.schoolandteachers_title
@Transient
override val destinationType = Destination.Type.SCHOOL_AND_TEACHERS
}
@Serializable
data class MobileDevicesAppMenuItem(override var order: Int = 12) : AppMenuItem() {
@Transient
override val icon = R.drawable.ic_more_mobile_devices
@Transient
override val title = R.string.mobile_devices_title
@Transient
override val destinationType = Destination.Type.MOBILE_DEVICE
}
}

View File

@ -0,0 +1,44 @@
package io.github.wulkanowy.ui.modules.settings.appearance.menuorder
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import java.util.*
class MenuItemMoveCallback(
private val menuOrderAdapter: MenuOrderAdapter,
private var onUserInteractionEndListener: (List<MenuOrderItem>) -> Unit = {}
) : ItemTouchHelper.Callback() {
override fun isLongPressDragEnabled() = true
override fun isItemViewSwipeEnabled() = false
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
//Not implemented
}
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
) = makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0)
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val list = menuOrderAdapter.items.toMutableList()
Collections.swap(list, viewHolder.bindingAdapterPosition, target.bindingAdapterPosition)
menuOrderAdapter.submitList(list)
return true
}
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
onUserInteractionEndListener(menuOrderAdapter.items.toList())
}
}

View File

@ -0,0 +1,58 @@
package io.github.wulkanowy.ui.modules.settings.appearance.menuorder
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.databinding.ItemMenuOrderBinding
import javax.inject.Inject
class MenuOrderAdapter @Inject constructor() :
RecyclerView.Adapter<MenuOrderAdapter.ViewHolder>() {
val items = mutableListOf<MenuOrderItem>()
fun submitList(newItems: List<MenuOrderItem>) {
val diffResult = DiffUtil.calculateDiff(DiffCallback(newItems, items.toMutableList()))
with(items) {
clear()
addAll(newItems)
}
diffResult.dispatchUpdatesTo(this)
}
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
ItemMenuOrderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position].appMenuItem
with(holder.binding) {
menuOrderItemTitle.setText(item.title)
menuOrderItemIcon.setImageResource(item.icon)
}
}
class ViewHolder(val binding: ItemMenuOrderBinding) : RecyclerView.ViewHolder(binding.root)
private class DiffCallback(
private val oldList: List<MenuOrderItem>,
private val newList: List<MenuOrderItem>
) : DiffUtil.Callback() {
override fun getNewListSize() = newList.size
override fun getOldListSize() = oldList.size
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition] == newList[newItemPosition]
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition].appMenuItem.destinationType == newList[newItemPosition].appMenuItem.destinationType
}
}

View File

@ -0,0 +1,50 @@
package io.github.wulkanowy.ui.modules.settings.appearance.menuorder
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.ShapeDrawable
import android.view.View
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.forEach
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.utils.getThemeAttrColor
class MenuOrderDividerItemDecoration(private val context: Context) :
DividerItemDecoration(context, VERTICAL) {
private val dividerDrawable = ShapeDrawable()
.apply {
DrawableCompat.setTint(this, context.getThemeAttrColor(R.attr.colorDivider))
}
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
canvas.save()
val dividerLeft = parent.paddingLeft
val dividerRight = parent.width - parent.paddingRight
parent.forEach {
if (parent.getChildAdapterPosition(it) == 3) {
val params = it.layoutParams as RecyclerView.LayoutParams
val dividerTop = it.bottom + params.bottomMargin
val dividerBottom = dividerTop + dividerDrawable.intrinsicHeight
dividerDrawable.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom + 30)
dividerDrawable.draw(canvas)
}
}
canvas.restore()
}
override fun getItemOffsets(
outRect: Rect, view: View, parent: RecyclerView,
state: RecyclerView.State
) {
if (parent.getChildAdapterPosition(view) == 3) {
outRect.bottom = dividerDrawable.intrinsicHeight + 30
}
}
}

View File

@ -0,0 +1,101 @@
package io.github.wulkanowy.ui.modules.settings.appearance.menuorder
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.activity.addCallback
import androidx.core.view.MenuProvider
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.databinding.FragmentMenuOrderBinding
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 javax.inject.Inject
@AndroidEntryPoint
class MenuOrderFragment : BaseFragment<FragmentMenuOrderBinding>(R.layout.fragment_menu_order),
MenuOrderView, MainView.TitledView {
@Inject
lateinit var presenter: MenuOrderPresenter
@Inject
lateinit var menuOrderAdapter: MenuOrderAdapter
override val titleStringId = R.string.menu_order_title
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentMenuOrderBinding.bind(view)
presenter.onAttachView(this)
}
override fun initView() {
val itemTouchHelper = ItemTouchHelper(
MenuItemMoveCallback(menuOrderAdapter, presenter::onDragAndDropEnd)
)
itemTouchHelper.attachToRecyclerView(binding.menuOrderRecycler)
with(binding.menuOrderRecycler) {
layoutManager = LinearLayoutManager(context)
adapter = menuOrderAdapter
addItemDecoration(MenuOrderDividerItemDecoration(context))
addItemDecoration(DividerItemDecoration(context))
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
presenter.onBackSelected()
}
initializeToolbar()
}
private fun initializeToolbar() {
requireActivity().addMenuProvider(object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
if (menuItem.itemId == android.R.id.home) {
presenter.onBackSelected()
return true
}
return false
}
}, viewLifecycleOwner)
}
override fun updateData(data: List<MenuOrderItem>) {
menuOrderAdapter.submitList(data)
}
override fun restartApp() {
startActivity(MainActivity.getStartIntent(requireContext()))
requireActivity().finishAffinity()
}
override fun popView() {
(activity as? MainActivity?)?.popView()
}
override fun showRestartConfirmationDialog() {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.menu_order_confirm_title)
.setMessage(R.string.menu_order_confirm_content)
.setPositiveButton(R.string.menu_order_confirm_restart) { _, _ -> presenter.onConfirmRestart() }
.setNegativeButton(R.string.all_cancel) { _, _ -> presenter.onCancelRestart() }
.show()
}
}

View File

@ -0,0 +1,6 @@
package io.github.wulkanowy.ui.modules.settings.appearance.menuorder
data class MenuOrderItem(
val appMenuItem: AppMenuItem,
val order: Int
)

View File

@ -0,0 +1,64 @@
package io.github.wulkanowy.ui.modules.settings.appearance.menuorder
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import timber.log.Timber
import javax.inject.Inject
class MenuOrderPresenter @Inject constructor(
studentRepository: StudentRepository,
errorHandler: ErrorHandler,
private val preferencesRepository: PreferencesRepository
) : BasePresenter<MenuOrderView>(errorHandler, studentRepository) {
private var updatedMenuOrderItems = emptyList<MenuOrderItem>()
override fun onAttachView(view: MenuOrderView) {
super.onAttachView(view)
view.initView()
Timber.i("Menu order view was initialized")
loadData()
}
private fun loadData() {
val savedMenuItemList = (preferencesRepository.appMenuItemOrder)
.sortedBy { it.order }
.map { MenuOrderItem(it, it.order) }
view?.updateData(savedMenuItemList)
}
fun onDragAndDropEnd(list: List<MenuOrderItem>) {
val updatedList = list.mapIndexed { index, menuOrderItem ->
menuOrderItem.copy(order = index)
}
updatedMenuOrderItems = updatedList
view?.updateData(updatedList)
}
fun onBackSelected() {
if (updatedMenuOrderItems.isNotEmpty()) {
view?.showRestartConfirmationDialog()
} else {
view?.popView()
}
}
fun onConfirmRestart() {
updatedMenuOrderItems.forEach {
it.appMenuItem.apply {
order = it.order
}
}
preferencesRepository.appMenuItemOrder = updatedMenuOrderItems.map { it.appMenuItem }
view?.restartApp()
}
fun onCancelRestart() {
view?.popView()
}
}

View File

@ -0,0 +1,16 @@
package io.github.wulkanowy.ui.modules.settings.appearance.menuorder
import io.github.wulkanowy.ui.base.BaseView
interface MenuOrderView : BaseView {
fun initView()
fun updateData(data: List<MenuOrderItem>)
fun restartApp()
fun showRestartConfirmationDialog()
fun popView()
}

View File

@ -0,0 +1,10 @@
<vector android:height="24dp"
android:tint="#000000"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="@android:color/white"
android:pathData="M20,9H4v2h16V9zM4,15h16v-2H4v2z" />
</vector>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/menu_order_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/item_menu_order" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorSurface"
android:minHeight="56dp">
<ImageView
android:id="@+id/menu_order_item_icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?colorOnSurface"
tools:ignore="ContentDescription"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/menu_order_item_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/menu_order_item_drag_icon"
app:layout_constraintStart_toEndOf="@id/menu_order_item_icon"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem" />
<ImageView
android:id="@+id/menu_order_item_drag_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="16dp"
android:src="@drawable/ic_menu_order_drag"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?colorOnSurface"
tools:ignore="ContentDescription" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -40,4 +40,5 @@
<string name="pref_key_ads_privacy_policy">ads_privacy_policy</string>
<string name="pref_key_ads_consent_data_processing">ads_consent_data_processing</string>
<string name="pref_key_ads_over_eighteen">ads_over_eighteen</string>
<string name="pref_key_menu_order">appearance_menu_order</string>
</resources>

View File

@ -26,6 +26,7 @@
<string name="student_info_title">Student info</string>
<string name="dashboard_title">Dashboard</string>
<string name="notifications_center_title">Notifications center</string>
<string name="menu_order_title">Menu configuartion</string>
<!--Subtitles-->
<string name="grade_subtitle">Semester %1$d, %2$d/%3$d</string>
<!--Login-->
@ -595,6 +596,7 @@
<string name="all_undo">Undo</string>
<string name="all_change">Change</string>
<string name="all_add_to_calendar">Add to calendar</string>
<string name="all_cancel">Cancel</string>
<!--Timetable Widget-->
<string name="widget_timetable_no_items">No lessons</string>
<string name="widget_timetable_theme_title">Choose theme</string>
@ -616,6 +618,8 @@
<string name="pref_view_grade_color_scheme">Grades color scheme</string>
<string name="pref_view_grade_sorting_mode">Subjects sorting</string>
<string name="pref_view_app_language">Language</string>
<string name="pref_view_menu_order_title">Menu configuration</string>
<string name="pref_view_menu_order_summary">Set the order of functions in the menu</string>
<string name="pref_notify_header">Notifications</string>
<string name="pref_notify_header_other">Other</string>
<string name="pref_notify_switch">Show notifications</string>
@ -717,6 +721,10 @@
<string name="update_download_success">An update has just been downloaded.</string>
<string name="update_download_success_button">Restart</string>
<string name="update_failed">Update failed! Wulkanowy may not function properly. Consider updating</string>
<!--Menu order-->
<string name="menu_order_confirm_title">Application restart</string>
<string name="menu_order_confirm_content">The application must restart for the changes to be saved</string>
<string name="menu_order_confirm_restart">Restart</string>
<!--Errors-->
<string name="error_no_internet">No internet connection</string>
<string name="error_invalid_device_datetime">An error occurred. Check your device clock</string>

View File

@ -20,23 +20,21 @@
app:key="@string/pref_key_app_theme"
app:title="@string/pref_view_app_theme"
app:useSimpleSummaryProvider="true" />
<ListPreference
app:defaultValue="@string/pref_default_startup"
app:entries="@array/startup_tab_entries"
app:entryValues="@array/startup_tab_value"
<Preference
app:fragment="io.github.wulkanowy.ui.modules.settings.appearance.menuorder.MenuOrderFragment"
app:iconSpaceReserved="false"
app:key="@string/pref_key_start_menu"
app:title="@string/pref_view_list"
app:useSimpleSummaryProvider="true" />
app:key="@string/pref_key_menu_order"
app:summary="@string/pref_view_menu_order_summary"
app:title="@string/pref_view_menu_order_title" />
</PreferenceCategory>
<PreferenceCategory
app:iconSpaceReserved="false"
app:title="@string/pref_dashboard_appearance_header">
<MultiSelectListPreference
app:defaultValue="@array/pref_default_dashboard_tiles"
app:entries="@array/dashboard_tile_entries"
app:entryValues="@array/dashboard_tile_values"
app:iconSpaceReserved="false"
app:defaultValue="@array/pref_default_dashboard_tiles"
app:key="@string/pref_key_dashboard_tiles"
app:title="@string/pref_dashboard_appearance_tiles_title" />
</PreferenceCategory>

View File

@ -46,7 +46,7 @@ class MainPresenterTest {
MockKAnnotations.init(this)
clearMocks(mainView)
every { mainView.initView(any(), any()) } just Runs
every { mainView.initView(any(), any(), any()) } just Runs
presenter = MainPresenter(
errorHandler = errorHandler,
studentRepository = studentRepository,