Update project to Android SDK 33 (#2011)

This commit is contained in:
Mikołaj Pich 2022-12-01 19:02:25 +01:00 committed by GitHub
parent 302d723cfb
commit 429fdfa4a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 496 additions and 182 deletions

View File

@ -16,13 +16,13 @@ apply from: 'hooks.gradle'
android { android {
namespace 'io.github.wulkanowy' namespace 'io.github.wulkanowy'
compileSdkVersion 32 compileSdkVersion 33
defaultConfig { defaultConfig {
applicationId "io.github.wulkanowy" applicationId "io.github.wulkanowy"
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 32 targetSdkVersion 33
versionCode 116 versionCode 116
versionName "1.8.1" versionName "1.8.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -193,9 +193,9 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
implementation "androidx.core:core-ktx:1.8.0" implementation "androidx.core:core-ktx:1.9.0"
implementation 'androidx.core:core-splashscreen:1.0.0' implementation 'androidx.core:core-splashscreen:1.0.0'
implementation "androidx.activity:activity-ktx:1.5.1" implementation "androidx.activity:activity-ktx:1.6.1"
implementation "androidx.appcompat:appcompat:1.5.1" implementation "androidx.appcompat:appcompat:1.5.1"
implementation "androidx.fragment:fragment-ktx:1.5.4" implementation "androidx.fragment:fragment-ktx:1.5.4"
implementation "androidx.annotation:annotation:1.5.0" implementation "androidx.annotation:annotation:1.5.0"
@ -271,9 +271,9 @@ dependencies {
testImplementation "com.google.dagger:hilt-android-testing:$hilt_version" testImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version" kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version"
androidTestImplementation "androidx.test:core:1.4.0" androidTestImplementation "androidx.test:core:1.5.0"
androidTestImplementation "androidx.test:runner:1.4.0" androidTestImplementation "androidx.test:runner:1.5.1"
androidTestImplementation "androidx.test.ext:junit:1.1.3" androidTestImplementation "androidx.test.ext:junit:1.1.4"
androidTestImplementation "io.mockk:mockk-android:$mockk" androidTestImplementation "io.mockk:mockk-android:$mockk"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
} }

View File

@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/colorPrimary" /> <background android:drawable="@color/colorPrimary" />
<foreground android:drawable="@drawable/ic_launcher_foreground_dev" /> <foreground android:drawable="@drawable/ic_launcher_foreground_dev" />
<monochrome android:drawable="@drawable/ic_launcher_foreground_dev_mono" />
</adaptive-icon> </adaptive-icon>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/colorPrimary" />
<foreground android:drawable="@drawable/ic_launcher_foreground_dev" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" /> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<queries> <queries>
<intent> <intent>
@ -36,13 +37,14 @@
<application <application
android:name=".WulkanowyApp" android:name=".WulkanowyApp"
android:allowBackup="false" android:allowBackup="false"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="false" android:supportsRtl="false"
android:theme="@style/WulkanowyTheme" android:theme="@style/WulkanowyTheme"
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute"> tools:ignore="DataExtractionRules,UnusedAttribute">
<activity <activity
android:name=".ui.modules.splash.SplashActivity" android:name=".ui.modules.splash.SplashActivity"
android:exported="true" android:exported="true"

View File

@ -37,7 +37,7 @@ class ErrorDialog : DialogFragment() {
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val error = requireArguments().getSerializable(ARGUMENT_KEY) as Throwable val error = requireArguments().serializable<Throwable>(ARGUMENT_KEY)
val binding = DialogErrorBinding.inflate(layoutInflater) val binding = DialogErrorBinding.inflate(layoutInflater)
binding.bindErrorDetails(error) binding.bindErrorDetails(error)

View File

@ -1,6 +1,9 @@
package io.github.wulkanowy.ui.base package io.github.wulkanowy.ui.base
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.GET_ACTIVITIES import android.content.pm.PackageManager.GET_ACTIVITIES
import android.os.Build
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
@ -41,9 +44,8 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
) )
} }
private fun isThemeApplicable(activity: AppCompatActivity) = private fun isThemeApplicable(activity: AppCompatActivity): Boolean =
activity.packageManager getPackageInfo(activity)
.getPackageInfo(activity.packageName, GET_ACTIVITIES)
.activities .activities
.singleOrNull { it.name == activity::class.java.canonicalName } .singleOrNull { it.name == activity::class.java.canonicalName }
?.theme ?.theme
@ -52,4 +54,14 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
|| it == R.style.WulkanowyTheme_Login || it == R.style.WulkanowyTheme_Login_Black || it == R.style.WulkanowyTheme_Login || it == R.style.WulkanowyTheme_Login_Black
|| it == R.style.WulkanowyTheme_MessageSend || it == R.style.WulkanowyTheme_MessageSend_Black || it == R.style.WulkanowyTheme_MessageSend || it == R.style.WulkanowyTheme_MessageSend_Black
} }
@Suppress("DEPRECATION")
private fun getPackageInfo(activity: AppCompatActivity): PackageInfo {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
activity.packageManager.getPackageInfo(
activity.packageName,
PackageManager.PackageInfoFlags.of(GET_ACTIVITIES.toLong())
)
} else activity.packageManager.getPackageInfo(activity.packageName, GET_ACTIVITIES)
}
} }

View File

@ -6,6 +6,7 @@ import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import androidx.core.view.get import androidx.core.view.get
import androidx.core.view.isVisible import androidx.core.view.isVisible
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -21,6 +22,7 @@ import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
import io.github.wulkanowy.utils.createNameInitialsDrawable import io.github.wulkanowy.utils.createNameInitialsDrawable
import io.github.wulkanowy.utils.nickOrName import io.github.wulkanowy.utils.nickOrName
import io.github.wulkanowy.utils.serializable
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -37,9 +39,8 @@ class AccountDetailsFragment :
private const val ARGUMENT_KEY = "Data" private const val ARGUMENT_KEY = "Data"
fun newInstance(student: Student) = fun newInstance(student: Student) = AccountDetailsFragment().apply {
AccountDetailsFragment().apply { arguments = bundleOf(ARGUMENT_KEY to student)
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, student) }
} }
} }
@ -52,7 +53,7 @@ class AccountDetailsFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding = FragmentAccountDetailsBinding.bind(view) binding = FragmentAccountDetailsBinding.bind(view)
presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student) presenter.onAttachView(this, requireArguments().serializable(ARGUMENT_KEY))
} }
override fun initView() { override fun initView() {

View File

@ -4,11 +4,13 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.databinding.DialogAccountEditBinding import io.github.wulkanowy.databinding.DialogAccountEditBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.ui.base.BaseDialogFragment
import io.github.wulkanowy.utils.serializable
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -24,11 +26,8 @@ class AccountEditDialog : BaseDialogFragment<DialogAccountEditBinding>(), Accoun
private const val ARGUMENT_KEY = "student_with_semesters" private const val ARGUMENT_KEY = "student_with_semesters"
fun newInstance(student: Student) = fun newInstance(student: Student) = AccountEditDialog().apply {
AccountEditDialog().apply { arguments = bundleOf(ARGUMENT_KEY to student)
arguments = Bundle().apply {
putSerializable(ARGUMENT_KEY, student)
}
} }
} }
@ -45,7 +44,7 @@ class AccountEditDialog : BaseDialogFragment<DialogAccountEditBinding>(), Accoun
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student) presenter.onAttachView(this, requireArguments().serializable(ARGUMENT_KEY))
} }
override fun initView() { override fun initView() {

View File

@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
@ -13,6 +14,7 @@ import io.github.wulkanowy.ui.modules.account.AccountAdapter
import io.github.wulkanowy.ui.modules.account.AccountFragment import io.github.wulkanowy.ui.modules.account.AccountFragment
import io.github.wulkanowy.ui.modules.account.AccountItem import io.github.wulkanowy.ui.modules.account.AccountItem
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.utils.serializable
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -30,9 +32,7 @@ class AccountQuickDialog : BaseDialogFragment<DialogAccountQuickBinding>(), Acco
fun newInstance(studentsWithSemesters: List<StudentWithSemesters>) = fun newInstance(studentsWithSemesters: List<StudentWithSemesters>) =
AccountQuickDialog().apply { AccountQuickDialog().apply {
arguments = Bundle().apply { arguments = bundleOf(STUDENTS_ARGUMENT_KEY to studentsWithSemesters.toTypedArray())
putSerializable(STUDENTS_ARGUMENT_KEY, studentsWithSemesters.toTypedArray())
}
} }
} }
@ -49,8 +49,8 @@ class AccountQuickDialog : BaseDialogFragment<DialogAccountQuickBinding>(), Acco
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val studentsWithSemesters = val studentsWithSemesters = requireArguments()
(requireArguments()[STUDENTS_ARGUMENT_KEY] as Array<StudentWithSemesters>).toList() .serializable<Array<StudentWithSemesters>>(STUDENTS_ARGUMENT_KEY).toList()
presenter.onAttachView(this, studentsWithSemesters) presenter.onAttachView(this, studentsWithSemesters)
} }

View File

@ -4,11 +4,13 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.databinding.DialogAttendanceBinding import io.github.wulkanowy.databinding.DialogAttendanceBinding
import io.github.wulkanowy.utils.descriptionRes import io.github.wulkanowy.utils.descriptionRes
import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
class AttendanceDialog : DialogFragment() { class AttendanceDialog : DialogFragment() {
@ -22,16 +24,14 @@ class AttendanceDialog : DialogFragment() {
private const val ARGUMENT_KEY = "Item" private const val ARGUMENT_KEY = "Item"
fun newInstance(exam: Attendance) = AttendanceDialog().apply { fun newInstance(exam: Attendance) = AttendanceDialog().apply {
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) } arguments = bundleOf(ARGUMENT_KEY to exam)
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0) setStyle(STYLE_NO_TITLE, 0)
arguments?.run { attendance = requireArguments().serializable(ARGUMENT_KEY)
attendance = getSerializable(ARGUMENT_KEY) as Attendance
}
} }
override fun onCreateView( override fun onCreateView(

View File

@ -4,11 +4,13 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.databinding.DialogConferenceBinding import io.github.wulkanowy.databinding.DialogConferenceBinding
import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
class ConferenceDialog : DialogFragment() { class ConferenceDialog : DialogFragment() {
@ -22,16 +24,14 @@ class ConferenceDialog : DialogFragment() {
private const val ARGUMENT_KEY = "item" private const val ARGUMENT_KEY = "item"
fun newInstance(conference: Conference) = ConferenceDialog().apply { fun newInstance(conference: Conference) = ConferenceDialog().apply {
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, conference) } arguments = bundleOf(ARGUMENT_KEY to conference)
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0) setStyle(STYLE_NO_TITLE, 0)
arguments?.let { conference = requireArguments().serializable(ARGUMENT_KEY)
conference = it.getSerializable(ARGUMENT_KEY) as Conference
}
} }
override fun onCreateView( override fun onCreateView(

View File

@ -4,12 +4,14 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.databinding.DialogExamBinding import io.github.wulkanowy.databinding.DialogExamBinding
import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.openCalendarEventAdd import io.github.wulkanowy.utils.openCalendarEventAdd
import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import java.time.LocalTime import java.time.LocalTime
@ -24,16 +26,14 @@ class ExamDialog : DialogFragment() {
private const val ARGUMENT_KEY = "Item" private const val ARGUMENT_KEY = "Item"
fun newInstance(exam: Exam) = ExamDialog().apply { fun newInstance(exam: Exam) = ExamDialog().apply {
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) } arguments = bundleOf(ARGUMENT_KEY to exam)
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0) setStyle(STYLE_NO_TITLE, 0)
arguments?.run { exam = requireArguments().serializable(ARGUMENT_KEY)
exam = getSerializable(ARGUMENT_KEY) as Exam
}
} }
override fun onCreateView( override fun onCreateView(

View File

@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
@ -27,22 +28,19 @@ class GradeDetailsDialog : DialogFragment() {
private const val COLOR_THEME_KEY = "Theme" private const val COLOR_THEME_KEY = "Theme"
fun newInstance(grade: Grade, colorTheme: GradeColorTheme) = fun newInstance(grade: Grade, colorTheme: GradeColorTheme) = GradeDetailsDialog().apply {
GradeDetailsDialog().apply { arguments = bundleOf(
arguments = Bundle().apply { ARGUMENT_KEY to grade,
putSerializable(ARGUMENT_KEY, grade) COLOR_THEME_KEY to colorTheme
putSerializable(COLOR_THEME_KEY, colorTheme) )
}
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0) setStyle(STYLE_NO_TITLE, 0)
arguments?.run { grade = requireArguments().serializable(ARGUMENT_KEY)
grade = getSerializable(ARGUMENT_KEY) as Grade gradeColorTheme = requireArguments().serializable(COLOR_THEME_KEY)
gradeColorTheme = getSerializable(COLOR_THEME_KEY) as GradeColorTheme
}
} }
override fun onCreateView( override fun onCreateView(

View File

@ -15,6 +15,7 @@ import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.grade.GradeView import io.github.wulkanowy.ui.modules.grade.GradeView
import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.setOnItemSelectedListener import io.github.wulkanowy.utils.setOnItemSelectedListener
import javax.inject.Inject import javax.inject.Inject
@ -48,8 +49,8 @@ class GradeStatisticsFragment :
messageContainer = binding.gradeStatisticsRecycler messageContainer = binding.gradeStatisticsRecycler
presenter.onAttachView( presenter.onAttachView(
view = this, view = this,
type = savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? GradeStatisticsItem.DataType, type = savedInstanceState?.serializable(SAVED_CHART_TYPE),
subjectName = savedInstanceState?.getSerializable(SAVED_SUBJECT_NAME) as? String, subjectName = savedInstanceState?.serializable(SAVED_SUBJECT_NAME),
) )
} }

View File

@ -7,6 +7,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
@ -14,6 +15,7 @@ import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.databinding.DialogHomeworkBinding import io.github.wulkanowy.databinding.DialogHomeworkBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.ui.base.BaseDialogFragment
import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.serializable
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -35,16 +37,14 @@ class HomeworkDetailsDialog : BaseDialogFragment<DialogHomeworkBinding>(), Homew
private const val ARGUMENT_KEY = "Item" private const val ARGUMENT_KEY = "Item"
fun newInstance(homework: Homework) = HomeworkDetailsDialog().apply { fun newInstance(homework: Homework) = HomeworkDetailsDialog().apply {
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, homework) } arguments = bundleOf(ARGUMENT_KEY to homework)
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0) setStyle(STYLE_NO_TITLE, 0)
arguments?.run { homework = requireArguments().serializable(ARGUMENT_KEY)
homework = getSerializable(ARGUMENT_KEY) as Homework
}
} }
override fun onCreateView( override fun onCreateView(

View File

@ -2,8 +2,11 @@ package io.github.wulkanowy.ui.modules.login
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build.VERSION_CODES.TIRAMISU
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.commit import androidx.fragment.app.commit
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -16,6 +19,9 @@ import io.github.wulkanowy.ui.modules.login.form.LoginFormFragment
import io.github.wulkanowy.ui.modules.login.recover.LoginRecoverFragment import io.github.wulkanowy.ui.modules.login.recover.LoginRecoverFragment
import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment import io.github.wulkanowy.ui.modules.login.studentselect.LoginStudentSelectFragment
import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment import io.github.wulkanowy.ui.modules.login.symbol.LoginSymbolFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.notifications.NotificationsFragment
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.UpdateHelper import io.github.wulkanowy.utils.UpdateHelper
import javax.inject.Inject import javax.inject.Inject
@ -28,6 +34,9 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
@Inject @Inject
lateinit var updateHelper: UpdateHelper lateinit var updateHelper: UpdateHelper
@Inject
lateinit var appInfo: AppInfo
companion object { companion object {
fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java) fun getStartIntent(context: Context) = Intent(context, LoginActivity::class.java)
} }
@ -55,7 +64,7 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) onBackPressed() if (item.itemId == android.R.id.home) onBackPressedDispatcher.onBackPressed()
return true return true
} }
@ -71,6 +80,22 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
openFragment(LoginStudentSelectFragment.newInstance(studentsWithSemesters)) openFragment(LoginStudentSelectFragment.newInstance(studentsWithSemesters))
} }
fun navigateToNotifications() {
val isNotificationsPermissionRequired = appInfo.systemVersion >= TIRAMISU
val isPermissionGranted = ContextCompat.checkSelfPermission(
this, "android.permission.POST_NOTIFICATIONS"
) == PackageManager.PERMISSION_GRANTED
if (isNotificationsPermissionRequired && !isPermissionGranted) {
openFragment(NotificationsFragment.newInstance(), clearBackStack = true)
} else navigateToFinish()
}
fun navigateToFinish() {
startActivity(MainActivity.getStartIntent(this))
finish()
}
fun onAdvancedLoginClick() { fun onAdvancedLoginClick() {
openFragment(LoginAdvancedFragment.newInstance()) openFragment(LoginAdvancedFragment.newInstance())
} }

View File

@ -98,7 +98,7 @@ class LoginRecoverFragment :
loginRecoverButton.setOnClickListener { presenter.onRecoverClick() } loginRecoverButton.setOnClickListener { presenter.onRecoverClick() }
loginRecoverErrorRetry.setOnClickListener { presenter.onRecoverClick() } loginRecoverErrorRetry.setOnClickListener { presenter.onRecoverClick() }
loginRecoverErrorDetails.setOnClickListener { presenter.onDetailsClick() } loginRecoverErrorDetails.setOnClickListener { presenter.onDetailsClick() }
loginRecoverLogin.setOnClickListener { (activity as LoginActivity).onBackPressed() } loginRecoverLogin.setOnClickListener { (activity as LoginActivity).onBackPressedDispatcher.onBackPressed() }
} }
with(bindingLocal.loginRecoverHost) { with(bindingLocal.loginRecoverHost) {

View File

@ -13,10 +13,10 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openEmailClient
import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.serializable
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -51,7 +51,7 @@ class LoginStudentSelectFragment :
binding = FragmentLoginStudentSelectBinding.bind(view) binding = FragmentLoginStudentSelectBinding.bind(view)
presenter.onAttachView( presenter.onAttachView(
view = this, view = this,
students = requireArguments().getSerializable(ARG_STUDENTS) as List<StudentWithSemesters>, students = requireArguments().serializable(ARG_STUDENTS),
) )
} }
@ -79,9 +79,8 @@ class LoginStudentSelectFragment :
} }
} }
override fun openMainView() { override fun navigateToNext() {
startActivity(MainActivity.getStartIntent(requireContext())) (requireActivity() as LoginActivity).navigateToNotifications()
requireActivity().finish()
} }
override fun showProgress(show: Boolean) { override fun showProgress(show: Boolean) {

View File

@ -100,7 +100,7 @@ class LoginStudentSelectPresenter @Inject constructor(
} }
is Resource.Success -> { is Resource.Success -> {
syncManager.startOneTimeSyncWorker(quiet = true) syncManager.startOneTimeSyncWorker(quiet = true)
view?.openMainView() view?.navigateToNext()
logRegisterEvent(studentsWithSemesters) logRegisterEvent(studentsWithSemesters)
} }
is Resource.Error -> { is Resource.Error -> {

View File

@ -9,7 +9,7 @@ interface LoginStudentSelectView : BaseView {
fun updateData(data: List<Pair<StudentWithSemesters, Boolean>>) fun updateData(data: List<Pair<StudentWithSemesters, Boolean>>)
fun openMainView() fun navigateToNext()
fun showProgress(show: Boolean) fun showProgress(show: Boolean)

View File

@ -18,11 +18,7 @@ import io.github.wulkanowy.databinding.FragmentLoginSymbolBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginData
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.hideSoftInput
import io.github.wulkanowy.utils.openEmailClient
import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.showSoftInput
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -54,7 +50,7 @@ class LoginSymbolFragment :
binding = FragmentLoginSymbolBinding.bind(view) binding = FragmentLoginSymbolBinding.bind(view)
presenter.onAttachView( presenter.onAttachView(
view = this, view = this,
loginData = requireArguments().getSerializable(SAVED_LOGIN_DATA) as LoginData, loginData = requireArguments().serializable(SAVED_LOGIN_DATA),
) )
} }

View File

@ -6,6 +6,8 @@ import android.os.Build.VERSION_CODES.P
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.OnBackPressedCallback
import androidx.activity.addCallback
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
@ -50,6 +52,8 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
@Inject @Inject
lateinit var appInfo: AppInfo lateinit var appInfo: AppInfo
private var onBackCallback: OnBackPressedCallback? = null
private var accountMenu: MenuItem? = null private var accountMenu: MenuItem? = null
private val overlayProvider by lazy { ElevationOverlayProvider(this) } private val overlayProvider by lazy { ElevationOverlayProvider(this) }
@ -88,6 +92,9 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
this.savedInstanceState = savedInstanceState this.savedInstanceState = savedInstanceState
messageContainer = binding.mainMessageContainer messageContainer = binding.mainMessageContainer
updateHelper.messageContainer = binding.mainFragmentContainer updateHelper.messageContainer = binding.mainFragmentContainer
onBackCallback = onBackPressedDispatcher.addCallback(this, enabled = false) {
presenter.onBackPressed()
}
val destination = intent.getStringExtra(EXTRA_START_DESTINATION) val destination = intent.getStringExtra(EXTRA_START_DESTINATION)
?.takeIf { savedInstanceState == null } ?.takeIf { savedInstanceState == null }
@ -266,6 +273,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName) analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName)
navController.pushFragment(fragment) navController.pushFragment(fragment)
onBackCallback?.isEnabled = !isRootView
} }
override fun popView(depth: Int) { override fun popView(depth: Int) {
@ -273,10 +281,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName) analytics.popCurrentScreen(navController.currentFrag!!::class.simpleName)
navController.safelyPopFragments(depth) navController.safelyPopFragments(depth)
} onBackCallback?.isEnabled = !isRootView
override fun onBackPressed() {
presenter.onBackPressed { super.onBackPressed() }
} }
override fun showStudentAvatar(student: Student) { override fun showStudentAvatar(student: Student) {

View File

@ -139,12 +139,9 @@ class MainPresenter @Inject constructor(
return true return true
} }
fun onBackPressed(default: () -> Unit) { fun onBackPressed() {
Timber.i("Back pressed in main view") Timber.i("Back pressed in main view")
view?.run { view?.popView()
if (isRootView) default()
else popView()
}
} }
fun onTabSelected(index: Int, wasSelected: Boolean): Boolean { fun onTabSelected(index: Int, wasSelected: Boolean): Boolean {

View File

@ -10,6 +10,7 @@ import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.databinding.DialogMailboxChooserBinding import io.github.wulkanowy.databinding.DialogMailboxChooserBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.ui.base.BaseDialogFragment
import io.github.wulkanowy.utils.parcelableArray
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -52,8 +53,7 @@ class MailboxChooserDialog : BaseDialogFragment<DialogMailboxChooserBinding>(),
presenter.onAttachView( presenter.onAttachView(
view = this, view = this,
requireMailbox = requireArguments().getBoolean(REQUIRED_KEY, false), requireMailbox = requireArguments().getBoolean(REQUIRED_KEY, false),
mailboxes = requireArguments().getParcelableArray(MAILBOX_KEY).orEmpty() mailboxes = requireArguments().parcelableArray<Mailbox>(MAILBOX_KEY).orEmpty().toList(),
.toList() as List<Mailbox>,
) )
} }

View File

@ -13,6 +13,7 @@ import android.webkit.WebResourceRequest
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
@ -23,6 +24,7 @@ import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.shareText import io.github.wulkanowy.utils.shareText
import javax.inject.Inject import javax.inject.Inject
@ -66,10 +68,8 @@ class MessagePreviewFragment :
companion object { companion object {
const val MESSAGE_ID_KEY = "message_id" const val MESSAGE_ID_KEY = "message_id"
fun newInstance(message: Message): MessagePreviewFragment { fun newInstance(message: Message) = MessagePreviewFragment().apply {
return MessagePreviewFragment().apply { arguments = bundleOf(MESSAGE_ID_KEY to message)
arguments = Bundle().apply { putSerializable(MESSAGE_ID_KEY, message) }
}
} }
} }
@ -84,8 +84,8 @@ class MessagePreviewFragment :
binding = FragmentMessagePreviewBinding.bind(view) binding = FragmentMessagePreviewBinding.bind(view)
messageContainer = binding.messagePreviewContainer messageContainer = binding.messagePreviewContainer
presenter.onAttachView( presenter.onAttachView(
this, view = this,
(savedInstanceState ?: arguments)?.getSerializable(MESSAGE_ID_KEY) as? Message message = (savedInstanceState ?: arguments)?.serializable(MESSAGE_ID_KEY),
) )
} }

View File

@ -28,6 +28,7 @@ import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialo
import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.LISTENER_KEY import io.github.wulkanowy.ui.modules.message.mailboxchooser.MailboxChooserDialog.Companion.LISTENER_KEY
import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.hideSoftInput
import io.github.wulkanowy.utils.nullableSerializable
import io.github.wulkanowy.utils.showSoftInput import io.github.wulkanowy.utils.showSoftInput
import javax.inject.Inject import javax.inject.Inject
@ -108,12 +109,12 @@ class SendMessageActivity : BaseActivity<SendMessagePresenter, ActivitySendMessa
presenter.onAttachView( presenter.onAttachView(
view = this, view = this,
reason = intent.getSerializableExtra(EXTRA_REASON) as? String, reason = intent.nullableSerializable(EXTRA_REASON),
message = intent.getSerializableExtra(EXTRA_MESSAGE) as? Message, message = intent.nullableSerializable(EXTRA_MESSAGE),
reply = intent.getSerializableExtra(EXTRA_REPLY) as? Boolean reply = intent.nullableSerializable(EXTRA_REPLY)
) )
supportFragmentManager.setFragmentResultListener(LISTENER_KEY, this) { _, bundle -> supportFragmentManager.setFragmentResultListener(LISTENER_KEY, this) { _, bundle ->
presenter.onMailboxSelected(bundle.getSerializable(MAILBOX_KEY) as? Mailbox) presenter.onMailboxSelected(bundle.nullableSerializable(MAILBOX_KEY))
} }
} }

View File

@ -9,6 +9,7 @@ import android.view.View.*
import android.widget.CompoundButton import android.widget.CompoundButton
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.os.bundleOf
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.setFragmentResultListener import androidx.fragment.app.setFragmentResultListener
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -27,6 +28,7 @@ import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.hideSoftInput import io.github.wulkanowy.utils.hideSoftInput
import io.github.wulkanowy.utils.nullableSerializable
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -43,12 +45,8 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
const val MESSAGE_TAB_FOLDER_ID = "message_tab_folder_id" const val MESSAGE_TAB_FOLDER_ID = "message_tab_folder_id"
fun newInstance(folder: MessageFolder): MessageTabFragment { fun newInstance(folder: MessageFolder) = MessageTabFragment().apply {
return MessageTabFragment().apply { arguments = bundleOf(MESSAGE_TAB_FOLDER_ID to folder.name)
arguments = Bundle().apply {
putString(MESSAGE_TAB_FOLDER_ID, folder.name)
}
}
} }
} }
@ -131,7 +129,7 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
setFragmentResultListener(requireArguments().getString(MESSAGE_TAB_FOLDER_ID)!!) { _, bundle -> setFragmentResultListener(requireArguments().getString(MESSAGE_TAB_FOLDER_ID)!!) { _, bundle ->
presenter.onMailboxSelected( presenter.onMailboxSelected(
mailbox = bundle.getSerializable(MailboxChooserDialog.MAILBOX_KEY) as? Mailbox, mailbox = bundle.nullableSerializable(MailboxChooserDialog.MAILBOX_KEY),
) )
} }
} }

View File

@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Note
@ -13,6 +14,7 @@ import io.github.wulkanowy.databinding.DialogNoteBinding
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
class NoteDialog : DialogFragment() { class NoteDialog : DialogFragment() {
@ -25,17 +27,15 @@ class NoteDialog : DialogFragment() {
private const val ARGUMENT_KEY = "Item" private const val ARGUMENT_KEY = "Item"
fun newInstance(exam: Note) = NoteDialog().apply { fun newInstance(note: Note) = NoteDialog().apply {
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) } arguments = bundleOf(ARGUMENT_KEY to note)
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0) setStyle(STYLE_NO_TITLE, 0)
arguments?.run { note = requireArguments().serializable(ARGUMENT_KEY)
note = getSerializable(ARGUMENT_KEY) as Note
}
} }
override fun onCreateView( override fun onCreateView(

View File

@ -0,0 +1,64 @@
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 dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.databinding.FragmentNotificationsBinding
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.utils.openNotificationSettings
@AndroidEntryPoint
class NotificationsFragment :
BaseFragment<FragmentNotificationsBinding>(R.layout.fragment_notifications) {
private val permission = "android.permission.POST_NOTIFICATIONS"
private val requestPermissionLauncher = registerForActivityResult(RequestPermission()) {
if (it) {
navigateToFinish()
} else showSettingsDialog()
}
companion object {
fun newInstance() = NotificationsFragment()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentNotificationsBinding.bind(view)
initView()
}
private fun initView() {
with(binding) {
notificationsSkip.setOnClickListener { navigateToFinish() }
notificationsEnable.setOnClickListener { requestPermission() }
}
}
private fun showSettingsDialog() {
AlertDialog.Builder(requireContext())
.setTitle(R.string.notifications_header_title)
.setMessage(R.string.notifications_header_description)
.setNegativeButton(R.string.notifications_skip) { dialog, _ ->
dialog.dismiss()
navigateToFinish()
}
.setPositiveButton(R.string.pref_notification_go_to_settings) { _, _ ->
requireActivity().openNotificationSettings()
}
.show()
}
private fun requestPermission() {
requestPermissionLauncher.launch(permission)
}
private fun navigateToFinish() {
(requireActivity() as LoginActivity).navigateToFinish()
}
}

View File

@ -4,11 +4,13 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.core.text.parseAsHtml import androidx.core.text.parseAsHtml
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.databinding.DialogSchoolAnnouncementBinding import io.github.wulkanowy.databinding.DialogSchoolAnnouncementBinding
import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.serializable
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
class SchoolAnnouncementDialog : DialogFragment() { class SchoolAnnouncementDialog : DialogFragment() {
@ -21,17 +23,15 @@ class SchoolAnnouncementDialog : DialogFragment() {
private const val ARGUMENT_KEY = "item" private const val ARGUMENT_KEY = "item"
fun newInstance(exam: SchoolAnnouncement) = SchoolAnnouncementDialog().apply { fun newInstance(announcement: SchoolAnnouncement) = SchoolAnnouncementDialog().apply {
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) } arguments = bundleOf(ARGUMENT_KEY to announcement)
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0) setStyle(STYLE_NO_TITLE, 0)
arguments?.run { announcement = requireArguments().serializable(ARGUMENT_KEY)
announcement = getSerializable(ARGUMENT_KEY) as SchoolAnnouncement
}
} }
override fun onCreateView( override fun onCreateView(

View File

@ -1,18 +1,16 @@
package io.github.wulkanowy.ui.modules.settings.notifications package io.github.wulkanowy.ui.modules.settings.notifications
import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Settings
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat import androidx.preference.SwitchPreferenceCompat
@ -26,7 +24,7 @@ import io.github.wulkanowy.ui.base.ErrorDialog
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.openInternetBrowser
import timber.log.Timber import io.github.wulkanowy.utils.openNotificationSettings
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -42,7 +40,14 @@ class NotificationsFragment : PreferenceFragmentCompat(),
override val titleStringId get() = R.string.pref_settings_notifications_title override val titleStringId get() = R.string.pref_settings_notifications_title
private val notificationsPermission = "android.permission.POST_NOTIFICATIONS"
override val isNotificationPermissionGranted: Boolean override val isNotificationPermissionGranted: Boolean
get() = ContextCompat.checkSelfPermission(
requireContext(), notificationsPermission
) == PackageManager.PERMISSION_GRANTED
override val isNotificationPiggybackPermissionGranted: Boolean
get() { get() {
val packageNameList = val packageNameList =
NotificationManagerCompat.getEnabledListenerPackages(requireContext()) NotificationManagerCompat.getEnabledListenerPackages(requireContext())
@ -51,6 +56,13 @@ class NotificationsFragment : PreferenceFragmentCompat(),
return appPackageName in packageNameList return appPackageName in packageNameList
} }
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
if (it) {
presenter.onNotificationsPermissionResult()
} else openNotificationsPermissionDialog()
}
private val notificationSettingsPiggybackContract = private val notificationSettingsPiggybackContract =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
presenter.onNotificationPiggybackPermissionResult() presenter.onNotificationPiggybackPermissionResult()
@ -156,25 +168,29 @@ class NotificationsFragment : PreferenceFragmentCompat(),
.show() .show()
} }
@SuppressLint("InlinedApi")
override fun openSystemSettings() { override fun openSystemSettings() {
val intent = if (appInfo.systemVersion >= Build.VERSION_CODES.O) { requireActivity().openNotificationSettings()
Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
putExtra("android.provider.extra.APP_PACKAGE", requireActivity().packageName)
}
} else {
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", requireActivity().packageName, null)
}
}
try {
requireActivity().startActivity(intent)
} catch (e: Exception) {
Timber.e(e)
}
} }
override fun openNotificationPermissionDialog() { override fun requestNotificationPermissions() {
requestPermissionLauncher.launch(notificationsPermission)
}
override fun openNotificationsPermissionDialog() {
AlertDialog.Builder(requireContext())
.setTitle(R.string.notifications_header_title)
.setMessage(R.string.notifications_header_description)
.setPositiveButton(R.string.pref_notification_go_to_settings) { _, _ ->
requireActivity().openNotificationSettings()
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
setNotificationPreferencesChecked(false)
}
.setOnDismissListener { setNotificationPreferencesChecked(false) }
.show()
}
override fun openNotificationPiggyBackPermissionDialog() {
AlertDialog.Builder(requireContext()) AlertDialog.Builder(requireContext())
.setTitle(getString(R.string.pref_notification_piggyback_popup_title)) .setTitle(getString(R.string.pref_notification_piggyback_popup_title))
.setMessage(getString(R.string.pref_notification_piggyback_popup_description)) .setMessage(getString(R.string.pref_notification_piggyback_popup_description))
@ -202,6 +218,11 @@ class NotificationsFragment : PreferenceFragmentCompat(),
.show() .show()
} }
override fun setNotificationPreferencesChecked(isChecked: Boolean) {
findPreference<SwitchPreferenceCompat>(getString(R.string.pref_key_notifications_enable))?.isChecked =
isChecked
}
override fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean) { override fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean) {
findPreference<SwitchPreferenceCompat>(getString(R.string.pref_key_notifications_piggyback))?.isChecked = findPreference<SwitchPreferenceCompat>(getString(R.string.pref_key_notifications_piggyback))?.isChecked =
isChecked isChecked

View File

@ -26,12 +26,13 @@ class NotificationsPresenter @Inject constructor(
with(view) { with(view) {
enableNotification( enableNotification(
preferencesRepository.notificationsEnableKey, notificationKey = preferencesRepository.notificationsEnableKey,
preferencesRepository.isServiceEnabled enable = preferencesRepository.isServiceEnabled
) )
initView(appInfo.isDebug) initView(appInfo.isDebug)
} }
checkNotificationsPermissionState()
checkNotificationPiggybackState() checkNotificationPiggybackState()
Timber.i("Settings notifications view was initialized") Timber.i("Settings notifications view was initialized")
@ -49,12 +50,17 @@ class NotificationsPresenter @Inject constructor(
view?.openNotificationExactAlarmSettings() view?.openNotificationExactAlarmSettings()
} }
} }
notificationsEnableKey -> {
if (isNotificationsEnable && view?.isNotificationPermissionGranted == false) {
view?.requestNotificationPermissions()
}
}
isDebugNotificationEnableKey -> { isDebugNotificationEnableKey -> {
chuckerCollector.showNotification = isDebugNotificationEnable chuckerCollector.showNotification = isDebugNotificationEnable
} }
isNotificationPiggybackEnabledKey -> { isNotificationPiggybackEnabledKey -> {
if (isNotificationPiggybackEnabled && view?.isNotificationPermissionGranted == false) { if (isNotificationPiggybackEnabled && view?.isNotificationPiggybackPermissionGranted == false) {
view?.openNotificationPermissionDialog() view?.openNotificationPiggyBackPermissionDialog()
} }
} }
} }
@ -70,9 +76,15 @@ class NotificationsPresenter @Inject constructor(
view?.openSystemSettings() view?.openSystemSettings()
} }
fun onNotificationsPermissionResult() {
view?.run {
setNotificationPreferencesChecked(isNotificationPermissionGranted)
}
}
fun onNotificationPiggybackPermissionResult() { fun onNotificationPiggybackPermissionResult() {
view?.run { view?.run {
setNotificationPiggybackPreferenceChecked(isNotificationPermissionGranted) setNotificationPiggybackPreferenceChecked(isNotificationPiggybackPermissionGranted)
} }
} }
@ -80,10 +92,18 @@ class NotificationsPresenter @Inject constructor(
view?.setUpcomingLessonsNotificationPreferenceChecked(timetableNotificationHelper.canScheduleExactAlarms()) view?.setUpcomingLessonsNotificationPreferenceChecked(timetableNotificationHelper.canScheduleExactAlarms())
} }
private fun checkNotificationsPermissionState() {
if (preferencesRepository.isNotificationsEnable) {
view?.run {
setNotificationPreferencesChecked(isNotificationPermissionGranted)
}
}
}
private fun checkNotificationPiggybackState() { private fun checkNotificationPiggybackState() {
if (preferencesRepository.isNotificationPiggybackEnabled) { if (preferencesRepository.isNotificationPiggybackEnabled) {
view?.run { view?.run {
setNotificationPiggybackPreferenceChecked(isNotificationPermissionGranted) setNotificationPiggybackPreferenceChecked(isNotificationPiggybackPermissionGranted)
} }
} }
} }

View File

@ -6,6 +6,8 @@ interface NotificationsView : BaseView {
val isNotificationPermissionGranted: Boolean val isNotificationPermissionGranted: Boolean
val isNotificationPiggybackPermissionGranted: Boolean
fun initView(showDebugNotificationSwitch: Boolean) fun initView(showDebugNotificationSwitch: Boolean)
fun showFixSyncDialog() fun showFixSyncDialog()
@ -14,10 +16,16 @@ interface NotificationsView : BaseView {
fun enableNotification(notificationKey: String, enable: Boolean) fun enableNotification(notificationKey: String, enable: Boolean)
fun openNotificationPermissionDialog() fun requestNotificationPermissions()
fun openNotificationsPermissionDialog()
fun openNotificationPiggyBackPermissionDialog()
fun openNotificationExactAlarmSettings() fun openNotificationExactAlarmSettings()
fun setNotificationPreferencesChecked(isChecked: Boolean)
fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean) fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean)
fun setUpcomingLessonsNotificationPreferenceChecked(isChecked: Boolean) fun setUpcomingLessonsNotificationPreferenceChecked(isChecked: Boolean)

View File

@ -8,6 +8,7 @@ import android.view.MenuInflater
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.os.bundleOf
import androidx.core.view.get import androidx.core.view.get
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -24,6 +25,8 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.capitalise import io.github.wulkanowy.utils.capitalise
import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.nullableSerializable
import io.github.wulkanowy.utils.serializable
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -38,7 +41,9 @@ class StudentInfoFragment :
lateinit var studentInfoAdapter: StudentInfoAdapter lateinit var studentInfoAdapter: StudentInfoAdapter
override val titleStringId: Int override val titleStringId: Int
get() = when (requireArguments().getSerializable(INFO_TYPE_ARGUMENT_KEY) as? StudentInfoView.Type) { get() = when (
requireArguments().nullableSerializable<StudentInfoView.Type>(INFO_TYPE_ARGUMENT_KEY)
) {
StudentInfoView.Type.PERSONAL -> R.string.account_personal_data StudentInfoView.Type.PERSONAL -> R.string.account_personal_data
StudentInfoView.Type.CONTACT -> R.string.account_contact StudentInfoView.Type.CONTACT -> R.string.account_contact
StudentInfoView.Type.ADDRESS -> R.string.account_address StudentInfoView.Type.ADDRESS -> R.string.account_address
@ -58,10 +63,10 @@ class StudentInfoFragment :
fun newInstance(type: StudentInfoView.Type, studentWithSemesters: StudentWithSemesters) = fun newInstance(type: StudentInfoView.Type, studentWithSemesters: StudentWithSemesters) =
StudentInfoFragment().apply { StudentInfoFragment().apply {
arguments = Bundle().apply { arguments = bundleOf(
putSerializable(INFO_TYPE_ARGUMENT_KEY, type) INFO_TYPE_ARGUMENT_KEY to type,
putSerializable(STUDENT_ARGUMENT_KEY, studentWithSemesters) STUDENT_ARGUMENT_KEY to studentWithSemesters
} )
} }
} }
@ -75,9 +80,9 @@ class StudentInfoFragment :
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding = FragmentStudentInfoBinding.bind(view) binding = FragmentStudentInfoBinding.bind(view)
presenter.onAttachView( presenter.onAttachView(
this, view = this,
requireArguments().getSerializable(INFO_TYPE_ARGUMENT_KEY) as StudentInfoView.Type, type = requireArguments().serializable(INFO_TYPE_ARGUMENT_KEY),
requireArguments().getSerializable(STUDENT_ARGUMENT_KEY) as StudentWithSemesters studentWithSemesters = requireArguments().serializable(STUDENT_ARGUMENT_KEY),
) )
} }
@ -154,7 +159,6 @@ class StudentInfoFragment :
) )
} }
@OptIn(ExperimentalStdlibApi::class)
override fun showFamilyTypeData(studentInfo: StudentInfo) { override fun showFamilyTypeData(studentInfo: StudentInfo) {
val items = buildList { val items = buildList {
add(studentInfo.firstGuardian?.let { add(studentInfo.firstGuardian?.let {

View File

@ -8,14 +8,12 @@ import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.databinding.DialogTimetableBinding import io.github.wulkanowy.databinding.DialogTimetableBinding
import io.github.wulkanowy.utils.capitalise import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.toFormattedString
import java.time.Instant import java.time.Instant
class TimetableDialog : DialogFragment() { class TimetableDialog : DialogFragment() {
@ -28,17 +26,15 @@ class TimetableDialog : DialogFragment() {
private const val ARGUMENT_KEY = "Item" private const val ARGUMENT_KEY = "Item"
fun newInstance(exam: Timetable) = TimetableDialog().apply { fun newInstance(lesson: Timetable) = TimetableDialog().apply {
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) } arguments = bundleOf(ARGUMENT_KEY to lesson)
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0) setStyle(STYLE_NO_TITLE, 0)
arguments?.run { lesson = requireArguments().serializable(ARGUMENT_KEY)
lesson = getSerializable(ARGUMENT_KEY) as Timetable
}
} }
override fun onCreateView( override fun onCreateView(

View File

@ -7,6 +7,7 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import androidx.core.os.bundleOf
import androidx.core.text.parseAsHtml import androidx.core.text.parseAsHtml
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -39,9 +40,7 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
private const val ARGUMENT_DATE_KEY = "ARGUMENT_DATE" private const val ARGUMENT_DATE_KEY = "ARGUMENT_DATE"
fun newInstance(date: LocalDate? = null) = TimetableFragment().apply { fun newInstance(date: LocalDate? = null) = TimetableFragment().apply {
arguments = Bundle().apply { arguments = date?.let { bundleOf(ARGUMENT_DATE_KEY to it.toEpochDay()) }
date?.let { putLong(ARGUMENT_DATE_KEY, it.toEpochDay()) }
}
} }
} }

View File

@ -4,10 +4,12 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.databinding.DialogLessonCompletedBinding import io.github.wulkanowy.databinding.DialogLessonCompletedBinding
import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.lifecycleAwareVariable
import io.github.wulkanowy.utils.serializable
class CompletedLessonDialog : DialogFragment() { class CompletedLessonDialog : DialogFragment() {
@ -19,17 +21,15 @@ class CompletedLessonDialog : DialogFragment() {
private const val ARGUMENT_KEY = "Item" private const val ARGUMENT_KEY = "Item"
fun newInstance(exam: CompletedLesson) = CompletedLessonDialog().apply { fun newInstance(lesson: CompletedLesson) = CompletedLessonDialog().apply {
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) } arguments = bundleOf(ARGUMENT_KEY to lesson)
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0) setStyle(STYLE_NO_TITLE, 0)
arguments?.run { completedLesson = requireArguments().serializable(ARGUMENT_KEY)
completedLesson = getSerializable(ARGUMENT_KEY) as CompletedLesson
}
} }
override fun onCreateView( override fun onCreateView(

View File

@ -0,0 +1,32 @@
package io.github.wulkanowy.utils
import android.content.Intent
import android.os.Build
import android.os.Bundle
import java.io.Serializable
inline fun <reified T : Serializable> Bundle.serializable(key: String): T = when {
Build.VERSION.SDK_INT >= 33 -> getSerializable(key, T::class.java)!!
else -> @Suppress("DEPRECATION") getSerializable(key) as T
}
inline fun <reified T : Serializable> Bundle.nullableSerializable(key: String): T? = when {
Build.VERSION.SDK_INT >= 33 -> getSerializable(key, T::class.java)
else -> @Suppress("DEPRECATION") getSerializable(key) as T?
}
@Suppress("DEPRECATION", "UNCHECKED_CAST")
inline fun <reified T : Serializable> Bundle.parcelableArray(key: String): Array<T>? = when {
Build.VERSION.SDK_INT >= 33 -> getParcelableArray(key, T::class.java)
else -> getParcelableArray(key) as Array<T>?
}
inline fun <reified T : Serializable> Intent.serializable(key: String): T = when {
Build.VERSION.SDK_INT >= 33 -> getSerializableExtra(key, T::class.java)!!
else -> @Suppress("DEPRECATION") getSerializableExtra(key) as T
}
inline fun <reified T : Serializable> Intent.nullableSerializable(key: String): T? = when {
Build.VERSION.SDK_INT >= 33 -> getSerializableExtra(key, T::class.java)
else -> @Suppress("DEPRECATION") getSerializableExtra(key) as T?
}

View File

@ -1,11 +1,15 @@
package io.github.wulkanowy.utils package io.github.wulkanowy.utils
import android.app.Activity
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import android.provider.CalendarContract import android.provider.CalendarContract
import android.provider.Settings
import io.github.wulkanowy.BuildConfig import io.github.wulkanowy.BuildConfig
import timber.log.Timber
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneId import java.time.ZoneId
@ -86,6 +90,23 @@ fun Context.openDialer(phone: String) {
} }
} }
fun Activity.openNotificationSettings() {
val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
putExtra("android.provider.extra.APP_PACKAGE", packageName)
}
} else {
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", packageName, null)
}
}
try {
startActivity(intent)
} catch (e: Exception) {
Timber.e(e)
}
}
fun Context.shareText(text: String, subject: String?) { fun Context.shareText(text: String, subject: String?) {
val sendIntent: Intent = Intent().apply { val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND action = Intent.ACTION_SEND

View File

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="1926"
android:viewportHeight="1926">
<path
android:fillColor="#fff"
android:pathData="M920,1357.7v-184.5h42.7q19.3,0.3 34.3,6.6 15.2,6.2 25.6,17.5 10.6,11.2 16,26.9 5.6,15.6 5.7, 34.6v13.5q-0.1,19 -5.7,34.7 -5.4,15.6 -16,26.9 -10.4,11.2 -25.6,17.5 -15,6.2 -34.3,6.3zM943.8,1192.5v146.1h18.9q15, -0.1 25.8,-5.2 11,-5.2 18,-14 7.3,-9 10.7,-21 3.5,-12 3.6,-26v-13.9q0,-14 -3.6,-26t-10.7,-20.7q-7,-9 -18,-14 -10.9,-5 -25.8,-5.3zM1179.5,1272.5h-77v65.4h89.8v19.9L1079,1357.8v-184.6h112.2v20h-88.7v59.4h77zM1288.9, 1320l43.6,-146.8h25l-58.2,184.6h-20.4l-58.3,-184.6h25z"
android:strokeWidth="1"
android:strokeColor="#000" />
<clip-path android:pathData="M 0 0 L 1926 0 L 1926 1111 L 850 1111 L 850 1926 L 0 1926 z" />
<path
android:fillColor="#fff"
android:pathData="M1082,1260v4l-6,73c0,5 -5,9 -10,9H879c-4,0 -8,-2 -10,-5l-31,-58 -29,-71v-8l48,-94c2,-3 2,-5 1, -8l-24,-65c-1,-2 0,-5 1,-7l39,-84c4,-8 17,-8 20,1l12,38 38,101c1,2 3,5 6,6l80,27c3,1 6,3 7,6l45,135zM852,717c0,-23 15,-43 35,-52l-2,-8c0,-11 13,-20 29,-20h1a38,38 0,0 1,36 -18l3,-2c11,-16 37,-27 68,-27 12,0 23,2 32,4 3,-3 8,-6 14,-6s12,3 14,8c8,-9 22,-16 38,-16 25,0 45,16 45,36l-1,7 3,4c16,6 27,17 27,30 0,14 -15,27 -34,32 -2,0 -3,1 -3,3 0,11 -11,21 -26,22v1c0,22 -41,40 -92,40 -11,0 -21,-1 -31,-3v1c0,9 -12,16 -26,16h-2l2,7c0,14 -18,25 -40,25h-7l-4, 2c-2,7 -8,12 -15,12 -9,0 -16,-7 -16,-16 0,-5 1,-8 4,-11l1,-4c-2,-3 -2,-5 -2,-8 0,-2 -2,-3 -3,-3 -27,-6 -48,-29 -48,-56z" />
<path
android:fillColor="#fff"
android:pathData="M1336,1346h-125c-3,0 -6,-1 -8,-4l-84,-118 -1,-2 -42,-122 -5,-5 -102,-52 -5,-6 -15,-75 -2,-3 -34,-51c-2,-3 -2,-6 -1,-9l21,-45c2,-4 5,-6 9,-6l39,-2c3,0 5,-1 6,-3l29,-22c5,-4 14,-2 17,4l98,201 1,2 13,81 2,5 197,216c6,6 1,16 -8,16zM801,1302c1,3 1,6 -1,8l-19,31c-2,3 -5,5 -9,5H591c-8,0 -13,-8 -9,-15l116,-171 2,-3 53, -228c1,-3 3,-5 5,-6l72,-40c3,-1 5,-4 5,-7l10,-42c2,-9 15,-11 20,-3l3,6c2,2 2,5 1,8l-66,190v5l16,91v5l-40,88v6l22,72z" />
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="1926"
android:viewportHeight="1926">
<path
android:fillColor="#fff"
android:pathData="M1082,1260v4l-6,73c0,5 -5,9 -10,9H879c-4,0 -8,-2 -10,-5l-31,-58 -29,-71v-8l48,-94c2,-3 2,-5 1, -8l-24,-65c-1,-2 0,-5 1,-7l39,-84c4,-8 17,-8 20,1l12,38 38,101c1,2 3,5 6,6l80,27c3,1 6,3 7,6l45,135zM852,717c0,-23 15,-43 35,-52l-2,-8c0,-11 13,-20 29,-20h1a38,38 0,0 1,36 -18l3,-2c11,-16 37,-27 68,-27 12,0 23,2 32,4 3,-3 8,-6 14,-6s12,3 14,8c8,-9 22,-16 38,-16 25,0 45,16 45,36l-1,7 3,4c16,6 27,17 27,30 0,14 -15,27 -34,32 -2,0 -3,1 -3,3 0,11 -11,21 -26,22v1c0,22 -41,40 -92,40 -11,0 -21,-1 -31,-3v1c0,9 -12,16 -26,16h-2l2,7c0,14 -18,25 -40,25h-7l-4, 2c-2,7 -8,12 -15,12 -9,0 -16,-7 -16,-16 0,-5 1,-8 4,-11l1,-4c-2,-3 -2,-5 -2,-8 0,-2 -2,-3 -3,-3 -27,-6 -48,-29 -48,-56z" />
<path
android:fillColor="#fff"
android:pathData="M1336,1346h-125c-3,0 -6,-1 -8,-4l-84,-118 -1,-2 -42,-122 -5,-5 -102,-52 -5,-6 -15,-75 -2,-3 -34,-51c-2,-3 -2,-6 -1,-9l21,-45c2,-4 5,-6 9,-6l39,-2c3,0 5,-1 6,-3l29,-22c5,-4 14,-2 17,4l98,201 1,2 13,81 2,5 197,216c6,6 1,16 -8,16zM801,1302c1,3 1,6 -1,8l-19,31c-2,3 -5,5 -9,5H591c-8,0 -13,-8 -9,-15l116,-171 2,-3 53, -228c1,-3 3,-5 5,-6l72,-40c3,-1 5,-4 5,-7l10,-42c2,-9 15,-11 20,-3l3,6c2,2 2,5 1,8l-66,190v5l16,91v5l-40,88v6l22,72z" />
</vector>

View File

@ -0,0 +1,69 @@
<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="match_parent"
tools:context=".ui.modules.notifications.NotificationsFragment">
<ImageView
android:id="@+id/notifications_header_icon"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginTop="32dp"
android:src="@drawable/ic_settings_notifications"
app:layout_constraintBottom_toTopOf="@id/notifications_header_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.4"
app:layout_constraintVertical_chainStyle="packed"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/notifications_header_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginVertical="16dp"
android:gravity="center_horizontal"
android:text="@string/notifications_header_title"
android:textColor="?android:textColorPrimary"
android:textSize="24sp"
app:layout_constraintBottom_toTopOf="@id/notifications_header_description"
app:layout_constraintTop_toBottomOf="@id/notifications_header_icon"
app:lineHeight="30sp" />
<TextView
android:id="@+id/notifications_header_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:gravity="center_horizontal"
android:text="@string/notifications_header_description"
android:textColor="?android:textColorSecondary"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@id/notifications_skip"
app:layout_constraintTop_toBottomOf="@id/notifications_header_title"
app:lineHeight="24sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/notifications_skip"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/notifications_skip"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/notifications_enable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/notifications_enable"
app:icon="@drawable/ic_settings_notifications"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/colorPrimary" /> <background android:drawable="@color/colorPrimary" />
<foreground android:drawable="@drawable/ic_launcher_foreground" /> <foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground_mono" />
</adaptive-icon> </adaptive-icon>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/colorPrimary" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -74,6 +74,11 @@
<string name="login_recover">Recover</string> <string name="login_recover">Recover</string>
<string name="login_signed_in">Student is already signed in</string> <string name="login_signed_in">Student is already signed in</string>
<string name="login_host_standard">Standard</string> <string name="login_host_standard">Standard</string>
<!--Notifications-->
<string name="notifications_header_title">Enable notifications</string>
<string name="notifications_header_description">Enable notifications so you don\'t miss message from teacher or new grade</string>
<string name="notifications_skip">Skip</string>
<string name="notifications_enable">Enable</string>
<!--Main--> <!--Main-->
<string name="main_account_picker">Account manager</string> <string name="main_account_picker">Account manager</string>
<string name="main_log_in">Log in</string> <string name="main_log_in">Log in</string>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
<cloud-backup>
<exclude domain="file" />
<exclude domain="database" />
<exclude domain="sharedpref" />
<exclude domain="external" />
<exclude domain="root" />
</cloud-backup>
<device-transfer>
<exclude domain="file" />
<exclude domain="database" />
<exclude domain="sharedpref" />
<exclude domain="external" />
<exclude domain="root" />
</device-transfer>
</data-extraction-rules>

View File

@ -90,14 +90,14 @@ class LoginStudentSelectPresenterTest {
studentRepository.saveStudents(listOf(StudentWithSemesters(testStudent, emptyList()))) studentRepository.saveStudents(listOf(StudentWithSemesters(testStudent, emptyList())))
} just Runs } just Runs
every { loginStudentSelectView.openMainView() } just Runs every { loginStudentSelectView.navigateToNext() } just Runs
presenter.onItemSelected(StudentWithSemesters(testStudent, emptyList()), false) presenter.onItemSelected(StudentWithSemesters(testStudent, emptyList()), false)
presenter.onSignIn() presenter.onSignIn()
verify { loginStudentSelectView.showContent(false) } verify { loginStudentSelectView.showContent(false) }
verify { loginStudentSelectView.showProgress(true) } verify { loginStudentSelectView.showProgress(true) }
verify { loginStudentSelectView.openMainView() } verify { loginStudentSelectView.navigateToNext() }
} }
@Test @Test