Add timetable changes, attendance notifications and refactor notification deeplinks (#1547)

This commit is contained in:
Mateusz Idziejczak
2021-11-06 22:21:34 +01:00
committed by GitHub
parent 4401df6203
commit f88d44f0ec
51 changed files with 3819 additions and 601 deletions

View File

@ -0,0 +1,132 @@
package io.github.wulkanowy.ui.modules
import androidx.fragment.app.Fragment
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
import io.github.wulkanowy.ui.modules.dashboard.DashboardFragment
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.message.MessageFragment
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.schoolannouncement.SchoolAnnouncementFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import java.io.Serializable
import java.time.LocalDate
sealed interface Destination : Serializable {
val type: Type
val fragment: Fragment
enum class Type(val defaultDestination: Destination) {
DASHBOARD(Dashboard),
GRADE(Grade),
ATTENDANCE(Attendance),
EXAM(Exam),
TIMETABLE(Timetable()),
HOMEWORK(Homework),
NOTE(Note),
CONFERENCE(Conference),
SCHOOL_ANNOUNCEMENT(SchoolAnnouncement),
SCHOOL(School),
LUCKY_NUMBER(More),
MORE(More),
MESSAGE(Message);
}
object Dashboard : Destination {
override val type = Type.DASHBOARD
override val fragment get() = DashboardFragment.newInstance()
}
object Grade : Destination {
override val type = Type.GRADE
override val fragment get() = GradeFragment.newInstance()
}
object Attendance : Destination {
override val type = Type.ATTENDANCE
override val fragment get() = AttendanceFragment.newInstance()
}
object Exam : Destination {
override val type = Type.EXAM
override val fragment get() = ExamFragment.newInstance()
}
data class Timetable(val date: LocalDate? = null) : Destination {
override val type = Type.TIMETABLE
override val fragment get() = TimetableFragment.newInstance(date)
}
object Homework : Destination {
override val type = Type.HOMEWORK
override val fragment get() = HomeworkFragment.newInstance()
}
object Note : Destination {
override val type = Type.NOTE
override val fragment get() = NoteFragment.newInstance()
}
object Conference : Destination {
override val type = Type.CONFERENCE
override val fragment get() = ConferenceFragment.newInstance()
}
object SchoolAnnouncement : Destination {
override val type = Type.SCHOOL_ANNOUNCEMENT
override val fragment get() = SchoolAnnouncementFragment.newInstance()
}
object School : Destination {
override val type = Type.SCHOOL
override val fragment get() = SchoolFragment.newInstance()
}
object LuckyNumber : Destination {
override val type = Type.LUCKY_NUMBER
override val fragment get() = LuckyNumberFragment.newInstance()
}
object More : Destination {
override val type = Type.MORE
override val fragment get() = MoreFragment.newInstance()
}
object Message : Destination {
override val type = Type.MESSAGE
override val fragment get() = MessageFragment.newInstance()
}
}

View File

@ -9,7 +9,7 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.enums.SentExcuseStatus
import io.github.wulkanowy.databinding.ItemAttendanceBinding
import io.github.wulkanowy.utils.description
import io.github.wulkanowy.utils.descriptionRes
import io.github.wulkanowy.utils.isExcusableOrNotExcused
import javax.inject.Inject
@ -36,7 +36,7 @@ class AttendanceAdapter @Inject constructor() :
with(holder.binding) {
attendanceItemNumber.text = item.number.toString()
attendanceItemSubject.text = item.subject
attendanceItemDescription.setText(item.description)
attendanceItemDescription.setText(item.descriptionRes)
attendanceItemAlert.visibility = item.run { if (absence && !excused) View.VISIBLE else View.INVISIBLE }
attendanceItemNumber.visibility = View.GONE
attendanceItemExcuseInfo.visibility = View.GONE

View File

@ -7,7 +7,7 @@ import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.databinding.DialogAttendanceBinding
import io.github.wulkanowy.utils.description
import io.github.wulkanowy.utils.descriptionRes
import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.toFormattedString
@ -45,7 +45,7 @@ class AttendanceDialog : DialogFragment() {
with(binding) {
attendanceDialogSubjectValue.text = attendance.subject
attendanceDialogDescriptionValue.setText(attendance.description)
attendanceDialogDescriptionValue.setText(attendance.descriptionRes)
attendanceDialogDateValue.text = attendance.date.toFormattedString()
attendanceDialogNumberValue.text = attendance.number.toString()
attendanceDialogClose.setOnClickListener { dismiss() }

View File

@ -3,6 +3,8 @@ package io.github.wulkanowy.ui.modules.debug.notification
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification
import io.github.wulkanowy.services.sync.notifications.NewAttendanceNotification
import io.github.wulkanowy.services.sync.notifications.NewConferenceNotification
import io.github.wulkanowy.services.sync.notifications.NewExamNotification
import io.github.wulkanowy.services.sync.notifications.NewGradeNotification
@ -13,6 +15,7 @@ import io.github.wulkanowy.services.sync.notifications.NewNoteNotification
import io.github.wulkanowy.services.sync.notifications.NewSchoolAnnouncementNotification
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugAttendanceItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugConferenceItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugExamItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeDetailsItems
@ -22,6 +25,7 @@ import io.github.wulkanowy.ui.modules.debug.notification.mock.debugLuckyNumber
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugMessageItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugNoteItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugSchoolAnnouncementItems
import io.github.wulkanowy.ui.modules.debug.notification.mock.debugTimetableItems
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@ -37,6 +41,8 @@ class NotificationDebugPresenter @Inject constructor(
private val newNoteNotification: NewNoteNotification,
private val newSchoolAnnouncementNotification: NewSchoolAnnouncementNotification,
private val newLuckyNumberNotification: NewLuckyNumberNotification,
private val changeTimetableNotification: ChangeTimetableNotification,
private val newAttendanceNotification: NewAttendanceNotification,
) : BasePresenter<NotificationDebugView>(errorHandler, studentRepository) {
private val items = listOf(
@ -64,6 +70,12 @@ class NotificationDebugPresenter @Inject constructor(
NotificationDebugItem(R.string.note_title) { n ->
withStudent { newNoteNotification.notify(debugNoteItems.take(n), it) }
},
NotificationDebugItem(R.string.attendance_title) { n ->
withStudent { newAttendanceNotification.notify(debugAttendanceItems.take(n), it) }
},
NotificationDebugItem(R.string.timetable_title) { n ->
withStudent { changeTimetableNotification.notify(debugTimetableItems.take(n), it) }
},
NotificationDebugItem(R.string.school_announcement_title) { n ->
withStudent {
newSchoolAnnouncementNotification.notify(debugSchoolAnnouncementItems.take(n), it)

View File

@ -0,0 +1,35 @@
package io.github.wulkanowy.ui.modules.debug.notification.mock
import io.github.wulkanowy.data.db.entities.Attendance
import java.time.LocalDate
val debugAttendanceItems = listOf(
generateAttendance("Matematyka", "PRESENCE"),
generateAttendance("Język angielski", "UNEXCUSED_LATENESS"),
generateAttendance("Geografia", "ABSENCE_UNEXCUSED"),
generateAttendance("Sieci komputerowe", "ABSENCE_EXCUSED"),
generateAttendance("Systemy operacyjne", "EXCUSED_LATENESS"),
generateAttendance("Język niemiecki", "ABSENCE_UNEXCUSED"),
generateAttendance("Biologia", "ABSENCE_UNEXCUSED"),
generateAttendance("Chemia", "ABSENCE_EXCUSED"),
generateAttendance("Fizyka", "ABSENCE_UNEXCUSED"),
generateAttendance("Matematyka", "ABSENCE_EXCUSED"),
)
private fun generateAttendance(subject: String, name: String) = Attendance(
subject = subject,
studentId = 0,
diaryId = 0,
date = LocalDate.now(),
timeId = 0,
number = 1,
name = name,
presence = false,
absence = false,
exemption = false,
lateness = false,
excused = false,
deleted = false,
excusable = false,
excuseStatus = ""
)

View File

@ -0,0 +1,39 @@
package io.github.wulkanowy.ui.modules.debug.notification.mock
import io.github.wulkanowy.data.db.entities.Timetable
import java.time.LocalDate
import java.time.LocalDateTime
import kotlin.random.Random
val debugTimetableItems = listOf(
generateTimetable("Matematyka", "12", "01"),
generateTimetable("Język angielski", "23", "12"),
generateTimetable("Geografia", "34", "23"),
generateTimetable("Sieci komputerowe", "45", "34"),
generateTimetable("Systemy operacyjne", "56", "45"),
generateTimetable("Język niemiecki", "67", "56"),
generateTimetable("Biologia", "78", "67"),
generateTimetable("Chemia", "89", "78"),
generateTimetable("Fizyka", "90", "89"),
generateTimetable("Matematyka", "01", "90"),
)
private fun generateTimetable(subject: String, room: String, roomOld: String) = Timetable(
subject = subject,
studentId = 0,
diaryId = 0,
date = LocalDate.now().minusDays(Random.nextLong(0, 8)),
number = 1,
start = LocalDateTime.now().plusHours(1),
end = LocalDateTime.now(),
subjectOld = "",
group = "",
room = room,
roomOld = roomOld,
teacher = "Wtorkowska Renata",
teacherOld = "",
info = "",
isStudentPlan = true,
changes = true,
canceled = true
)

View File

@ -66,7 +66,14 @@ class LoginStudentSelectFragment :
}
override fun openMainView() {
activity?.let { startActivity(MainActivity.getStartIntent(context = it, clear = true)) }
activity?.let {
startActivity(
MainActivity.getStartIntent(
context = it,
startNewTask = true
)
)
}
}
override fun showProgress(show: Boolean) {

View File

@ -18,8 +18,8 @@ import io.github.wulkanowy.data.db.SharedPrefProvider
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.ui.modules.Destination
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.toFirstResult
import kotlinx.coroutines.runBlocking
import timber.log.Timber
@ -39,6 +39,8 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
companion object {
const val LUCKY_NUMBER_PENDING_INTENT_ID = 200
fun getStudentWidgetKey(appWidgetId: Int) = "lucky_number_widget_student_$appWidgetId"
fun getThemeWidgetKey(appWidgetId: Int) = "lucky_number_widget_theme_$appWidgetId"
@ -48,18 +50,31 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
fun getWidthWidgetKey(appWidgetId: Int) = "lucky_number_widget_width_$appWidgetId"
}
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray?) {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
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,
MainActivity.getStartIntent(context, Destination.LuckyNumber, true),
FLAG_UPDATE_CURRENT
)
val luckyNumber = getLuckyNumber(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId)
val appIntent = PendingIntent.getActivity(context, MainView.Section.LUCKY_NUMBER.id,
MainActivity.getStartIntent(context, MainView.Section.LUCKY_NUMBER, true), FLAG_UPDATE_CURRENT)
val remoteView = RemoteViews(context.packageName, getCorrectLayoutId(appWidgetId, context)).apply {
setTextViewText(R.id.luckyNumberWidgetNumber, luckyNumber?.luckyNumber?.toString() ?: "#")
setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent)
}
val remoteView =
RemoteViews(context.packageName, getCorrectLayoutId(appWidgetId, context))
.apply {
setTextViewText(
R.id.luckyNumberWidgetNumber,
luckyNumber?.luckyNumber?.toString() ?: "#"
)
setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent)
}
setStyles(remoteView, appWidgetId)
appWidgetManager.updateAppWidget(appWidgetId, remoteView)
@ -78,7 +93,12 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
}
}
override fun onAppWidgetOptionsChanged(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, newOptions: Bundle?) {
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))
@ -88,8 +108,12 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
}
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()
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())
@ -112,7 +136,11 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
}
}
private fun RemoteViews.setVisibility(imageTop: Boolean, imageLeft: Boolean, title: Boolean = false) {
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)
@ -152,7 +180,8 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
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
val isSystemDarkMode =
context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
return if (savedTheme == 1L || (savedTheme == 2L && isSystemDarkMode)) {
R.layout.widget_luckynumber_dark

View File

@ -5,16 +5,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.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Build
import android.os.Build.VERSION_CODES.P
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment
@ -29,20 +23,10 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.databinding.ActivityMainBinding
import io.github.wulkanowy.services.shortcuts.ShortcutsHelper
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.attendance.AttendanceFragment
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
import io.github.wulkanowy.ui.modules.dashboard.DashboardFragment
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.message.MessageFragment
import io.github.wulkanowy.ui.modules.more.MoreFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.InAppReviewHelper
@ -75,6 +59,9 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
@Inject
lateinit var appInfo: AppInfo
@Inject
lateinit var shortcutsHelper: ShortcutsHelper
private var accountMenu: MenuItem? = null
private val overlayProvider by lazy { ElevationOverlayProvider(this) }
@ -83,15 +70,19 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
FragNavController(supportFragmentManager, R.id.main_fragment_container)
companion object {
const val EXTRA_START_MENU = "extraStartMenu"
private const val EXTRA_START_DESTINATION = "start_destination"
fun getStartIntent(
context: Context,
startMenu: MainView.Section? = null,
clear: Boolean = false
destination: Destination? = null,
startNewTask: Boolean = false
) = Intent(context, MainActivity::class.java).apply {
if (clear) flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK
startMenu?.let { putExtra(EXTRA_START_MENU, it.id) }
putExtra(EXTRA_START_DESTINATION, destination)
if (startNewTask) {
flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK
}
}
}
@ -106,42 +97,21 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
override val currentViewSubtitle get() = (navController.currentFrag as? MainView.TitledView)?.subtitleString
override var startMenuIndex = 0
override var startMenuMoreIndex = -1
private val moreMenuFragments = mapOf<Int, Fragment>(
MainView.Section.MESSAGE.id to MessageFragment.newInstance(),
MainView.Section.EXAM.id to ExamFragment.newInstance(),
MainView.Section.HOMEWORK.id to HomeworkFragment.newInstance(),
MainView.Section.NOTE.id to NoteFragment.newInstance(),
MainView.Section.CONFERENCE.id to ConferenceFragment.newInstance(),
MainView.Section.SCHOOL_ANNOUNCEMENT.id to SchoolAnnouncementFragment.newInstance(),
MainView.Section.LUCKY_NUMBER.id to LuckyNumberFragment.newInstance(),
)
private var savedInstanceState: Bundle? = null
@SuppressLint("NewApi")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityMainBinding.inflate(layoutInflater).apply { binding = this }.root)
setSupportActionBar(binding.mainToolbar)
this.savedInstanceState = savedInstanceState
messageContainer = binding.mainMessageContainer
updateHelper.messageContainer = binding.mainFragmentContainer
val section = MainView.Section.values()
.singleOrNull { it.id == intent.getIntExtra(EXTRA_START_MENU, -1) }
presenter.onAttachView(this, section)
with(navController) {
initialize(startMenuIndex, savedInstanceState)
pushFragment(moreMenuFragments[startMenuMoreIndex])
}
if (appInfo.systemVersion >= Build.VERSION_CODES.N_MR1) {
initShortcuts()
}
val destination = intent.getSerializableExtra(EXTRA_START_DESTINATION) as Destination?
?: shortcutsHelper.getDestination(intent)
presenter.onAttachView(this, destination)
updateHelper.checkAndInstallUpdates(this)
}
@ -157,54 +127,6 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
updateHelper.onActivityResult(requestCode, resultCode)
}
@RequiresApi(Build.VERSION_CODES.N_MR1)
fun initShortcuts() {
val shortcutsList = mutableListOf<ShortcutInfo>()
listOf(
Triple(
getString(R.string.grade_title),
R.drawable.ic_shortcut_grade,
MainView.Section.GRADE
),
Triple(
getString(R.string.attendance_title),
R.drawable.ic_shortcut_attendance,
MainView.Section.ATTENDANCE
),
Triple(
getString(R.string.exam_title),
R.drawable.ic_shortcut_exam,
MainView.Section.EXAM
),
Triple(
getString(R.string.timetable_title),
R.drawable.ic_shortcut_timetable,
MainView.Section.TIMETABLE
)
).forEach { (title, icon, enum) ->
shortcutsList.add(
ShortcutInfo.Builder(applicationContext, title)
.setShortLabel(title)
.setLongLabel(title)
.setIcon(Icon.createWithResource(applicationContext, icon))
.setIntents(
arrayOf(
Intent(applicationContext, MainActivity::class.java)
.setAction(Intent.ACTION_VIEW),
Intent(applicationContext, MainActivity::class.java)
.putExtra(EXTRA_START_MENU, enum.id)
.setAction(Intent.ACTION_VIEW)
.addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)
)
)
.build()
)
}
getSystemService<ShortcutManager>()?.dynamicShortcuts = shortcutsList
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.action_menu_main, menu)
accountMenu = menu?.findItem(R.id.mainMenuAccount)
@ -213,15 +135,38 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
return true
}
@SuppressLint("NewApi")
override fun initView() {
override fun initView(startMenuIndex: Int, rootDestinations: List<Destination>) {
initializeToolbar()
initializeBottomNavigation(startMenuIndex)
initializeNavController(startMenuIndex, rootDestinations)
}
private fun initializeNavController(startMenuIndex: Int, rootDestinations: List<Destination>) {
with(navController) {
setOnViewChangeListener { destinationView ->
presenter.onViewChange(destinationView)
analytics.setCurrentScreen(
this@MainActivity,
destinationView::class.java.simpleName
)
}
fragmentHideStrategy = HIDE
rootFragments = rootDestinations.map { it.fragment }
initialize(startMenuIndex, savedInstanceState)
}
}
private fun initializeToolbar() {
with(binding.mainToolbar) {
stateListAnimator = null
setBackgroundColor(
overlayProvider.compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(4f))
)
}
}
private fun initializeBottomNavigation(startMenuIndex: Int) {
with(binding.mainBottomNav) {
with(menu) {
add(Menu.NONE, 0, Menu.NONE, R.string.dashboard_title)
@ -239,36 +184,6 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
setOnItemSelectedListener { presenter.onTabSelected(it.itemId, false) }
setOnItemReselectedListener { presenter.onTabSelected(it.itemId, true) }
}
with(navController) {
setOnViewChangeListener { section, name ->
if (section == MainView.Section.ACCOUNT || section == MainView.Section.STUDENT_INFO) {
binding.mainBottomNav.isVisible = false
if (appInfo.systemVersion >= P) {
window.navigationBarColor = getThemeAttrColor(R.attr.colorSurface)
}
} else {
binding.mainBottomNav.isVisible = true
if (appInfo.systemVersion >= P) {
window.navigationBarColor =
getThemeAttrColor(android.R.attr.navigationBarColor)
}
}
analytics.setCurrentScreen(this@MainActivity, name)
presenter.onViewChange(section)
}
fragmentHideStrategy = HIDE
rootFragments = listOf(
DashboardFragment.newInstance(),
GradeFragment.newInstance(),
AttendanceFragment.newInstance(),
TimetableFragment.newInstance(),
MoreFragment.newInstance()
)
}
}
override fun onPreferenceStartFragment(
@ -317,6 +232,22 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
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)
}
}
}
override fun openMoreDestination(destination: Destination) {
pushView(destination.fragment)
}
override fun notifyMenuViewReselected() {
(navController.currentStack?.getOrNull(0) as? MainView.MainChildView)?.onFragmentReselected()
}
@ -373,6 +304,6 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
navController.onSaveInstanceState(outState)
intent.removeExtra(EXTRA_START_MENU)
intent.removeExtra(EXTRA_START_DESTINATION)
}
}

View File

@ -6,10 +6,15 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.services.sync.SyncManager
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.main.MainView.Section.GRADE
import io.github.wulkanowy.ui.modules.main.MainView.Section.MESSAGE
import io.github.wulkanowy.ui.modules.main.MainView.Section.SCHOOL
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.school.SchoolView
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.flowWithResource
import kotlinx.coroutines.flow.onEach
@ -27,19 +32,40 @@ class MainPresenter @Inject constructor(
private var studentsWitSemesters: List<StudentWithSemesters>? = null
fun onAttachView(view: MainView, initMenu: MainView.Section?) {
super.onAttachView(view)
view.apply {
getProperViewIndexes(initMenu).let { (main, more) ->
startMenuIndex = main
startMenuMoreIndex = more
private val rootDestinationTypeList = listOf(
Destination.Type.DASHBOARD,
Destination.Type.GRADE,
Destination.Type.ATTENDANCE,
Destination.Type.TIMETABLE,
Destination.Type.MORE
)
private val Destination?.startMenuIndex
get() = when {
this == null -> prefRepository.startMenuIndex
type in rootDestinationTypeList -> {
rootDestinationTypeList.indexOf(type)
}
initView()
Timber.i("Main view was initialized with $startMenuIndex menu index and $startMenuMoreIndex more index")
else -> 4
}
fun onAttachView(view: MainView, initDestination: Destination?) {
super.onAttachView(view)
val startMenuIndex = initDestination.startMenuIndex
val destinations = rootDestinationTypeList.map {
if (it == initDestination?.type) initDestination else it.defaultDestination
}
view.initView(startMenuIndex, destinations)
if (initDestination != null && startMenuIndex == 4) {
view.openMoreDestination(initDestination)
}
syncManager.startPeriodicSyncWorker()
analytics.logEvent("app_open", "destination" to initMenu?.name)
analytics.logEvent("app_open", "destination" to initDestination.toString())
Timber.i("Main view was initialized with $initDestination")
}
fun onActionMenuCreated() {
@ -64,9 +90,10 @@ class MainPresenter @Inject constructor(
}.launch("avatar")
}
fun onViewChange(section: MainView.Section?) {
fun onViewChange(destinationView: BaseView) {
view?.apply {
showActionBarElevation(section != GRADE && section != MESSAGE && section != SCHOOL)
showBottomNavigation(destinationView !is AccountView && destinationView !is StudentInfoView && destinationView !is AccountDetailsView)
showActionBarElevation(destinationView !is GradeView && destinationView !is MessageView && destinationView !is SchoolView)
currentViewTitle?.let { setViewTitle(it) }
currentViewSubtitle?.let { setViewSubTitle(it.ifBlank { null }) }
currentStackSize?.let {
@ -134,10 +161,4 @@ class MainPresenter @Inject constructor(
view?.showStudentAvatar(currentStudent)
}
private fun getProperViewIndexes(initMenu: MainView.Section?) = when (initMenu?.id) {
in 0..3 -> initMenu!!.id to -1
in 4..100 -> 4 to initMenu!!.id
else -> prefRepository.startMenuIndex to -1
}
}

View File

@ -3,13 +3,10 @@ package io.github.wulkanowy.ui.modules.main
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
interface MainView : BaseView {
var startMenuIndex: Int
var startMenuMoreIndex: Int
val isRootView: Boolean
val currentViewTitle: String?
@ -18,7 +15,7 @@ interface MainView : BaseView {
val currentStackSize: Int?
fun initView()
fun initView(startMenuIndex: Int, rootDestinations: List<Destination>)
fun switchMenuView(position: Int)
@ -28,6 +25,8 @@ interface MainView : BaseView {
fun showActionBarElevation(show: Boolean)
fun showBottomNavigation(show: Boolean)
fun notifyMenuViewReselected()
fun notifyMenuViewChanged()
@ -42,6 +41,8 @@ interface MainView : BaseView {
fun showInAppReview()
fun openMoreDestination(destination: Destination)
interface MainChildView {
fun onFragmentReselected()
@ -57,25 +58,4 @@ interface MainView : BaseView {
get() = ""
set(_) {}
}
enum class Section {
DASHBOARD,
GRADE,
ATTENDANCE,
TIMETABLE,
MORE,
MESSAGE,
EXAM,
HOMEWORK,
NOTE,
CONFERENCE,
SCHOOL_ANNOUNCEMENT,
SCHOOL,
LUCKY_NUMBER,
ACCOUNT,
STUDENT_INFO,
SETTINGS;
val id get() = ordinal
}
}

View File

@ -5,7 +5,6 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Notification
import io.github.wulkanowy.databinding.ItemNotificationsCenterBinding
import io.github.wulkanowy.services.sync.notifications.NotificationType
@ -28,26 +27,12 @@ class NotificationsCenterAdapter @Inject constructor() :
notificationsCenterItemTitle.text = item.title
notificationsCenterItemContent.text = item.content
notificationsCenterItemDate.text = item.date.toFormattedString("HH:mm, d MMM")
notificationsCenterItemIcon.setImageResource(item.type.toDrawableResId())
notificationsCenterItemIcon.setImageResource(item.type.icon)
root.setOnClickListener { onItemClickListener(item.type) }
}
}
private fun NotificationType.toDrawableResId() = when (this) {
NotificationType.NEW_CONFERENCE -> R.drawable.ic_more_conferences
NotificationType.NEW_EXAM -> R.drawable.ic_main_exam
NotificationType.NEW_GRADE_DETAILS -> R.drawable.ic_stat_grade
NotificationType.NEW_GRADE_PREDICTED -> R.drawable.ic_stat_grade
NotificationType.NEW_GRADE_FINAL -> R.drawable.ic_stat_grade
NotificationType.NEW_HOMEWORK -> R.drawable.ic_more_homework
NotificationType.NEW_LUCKY_NUMBER -> R.drawable.ic_stat_luckynumber
NotificationType.NEW_MESSAGE -> R.drawable.ic_stat_message
NotificationType.NEW_NOTE -> R.drawable.ic_stat_note
NotificationType.NEW_ANNOUNCEMENT -> R.drawable.ic_all_about
NotificationType.PUSH -> R.drawable.ic_stat_all
}
class ViewHolder(val binding: ItemNotificationsCenterBinding) :
RecyclerView.ViewHolder(binding.root)
@ -59,4 +44,4 @@ class NotificationsCenterAdapter @Inject constructor() :
override fun areItemsTheSame(oldItem: Notification, newItem: Notification) =
oldItem.id == newItem.id
}
}
}

View File

@ -11,6 +11,7 @@ import io.github.wulkanowy.data.db.entities.Notification
import io.github.wulkanowy.databinding.FragmentNotificationsCenterBinding
import io.github.wulkanowy.services.sync.notifications.NotificationType
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
import io.github.wulkanowy.ui.modules.exam.ExamFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment
@ -21,6 +22,7 @@ import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import javax.inject.Inject
@AndroidEntryPoint
@ -104,5 +106,7 @@ class NotificationsCenterFragment :
NotificationType.NEW_NOTE -> NoteFragment.newInstance()
NotificationType.NEW_ANNOUNCEMENT -> SchoolAnnouncementFragment.newInstance()
NotificationType.PUSH -> null
NotificationType.CHANGE_TIMETABLE -> TimetableFragment.newInstance()
NotificationType.NEW_ATTENDANCE -> AttendanceFragment.newInstance()
}
}
}

View File

@ -6,7 +6,7 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.ui.modules.main.MainView
import timber.log.Timber
class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView {
class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView, SettingsView {
companion object {
@ -19,4 +19,16 @@ class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView {
setPreferencesFromResource(R.xml.scheme_preferences, rootKey)
Timber.i("Settings view was initialized")
}
override fun showError(text: String, error: Throwable) {}
override fun showMessage(text: String) {}
override fun showExpiredDialog() {}
override fun openClearLoginView() {}
override fun showErrorDetailsDialog(error: Throwable) {}
override fun showChangePasswordSnackbar(redirectUrl: String) {}
}

View File

@ -0,0 +1,5 @@
package io.github.wulkanowy.ui.modules.settings
import io.github.wulkanowy.ui.base.BaseView
interface SettingsView : BaseView

View File

@ -24,8 +24,8 @@ import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.services.HiltBroadcastReceiver
import io.github.wulkanowy.services.widgets.TimetableWidgetService
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.utils.AnalyticsHelper
import io.github.wulkanowy.utils.capitalise
import io.github.wulkanowy.utils.createNameInitialsDrawable
@ -60,6 +60,8 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() {
companion object {
private const val TIMETABLE_PENDING_INTENT_ID = 201
private const val EXTRA_TOGGLED_WIDGET_ID = "extraToggledWidget"
private const val EXTRA_BUTTON_TYPE = "extraButtonType"
@ -174,8 +176,8 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() {
)
val appIntent = PendingIntent.getActivity(
context,
MainView.Section.TIMETABLE.id,
MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true),
TIMETABLE_PENDING_INTENT_ID,
MainActivity.getStartIntent(context, Destination.Timetable(), true),
FLAG_UPDATE_CURRENT
)