Added notifications for upcoming exams; updated app logo

This commit is contained in:
Deniz Düzgören
2020-03-09 20:17:34 +01:00
parent 31b1bc5d4e
commit 80f737a96c
41 changed files with 646 additions and 226 deletions
+4 -2
View File
@@ -38,11 +38,11 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
implementation "androidx.fragment:fragment-ktx:1.2.1"
implementation "androidx.fragment:fragment-ktx:1.2.2"
testImplementation 'junit:junit:4.12'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.google.android.material:material:1.2.0-alpha04'
implementation 'com.google.android.material:material:1.2.0-alpha05'
implementation 'androidx.preference:preference-ktx:1.1.0'
@@ -54,6 +54,8 @@ dependencies {
implementation "androidx.room:room-runtime:2.2.3"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.work:work-runtime-ktx:2.3.3"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 23 KiB

@@ -1,27 +1,22 @@
package com.denizd.qwark.activity
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.View
import android.view.ViewAnimationUtils
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatDelegate
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentTransaction
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.work.*
import com.denizd.lawrence.util.viewBinding
import com.denizd.qwark.R
import com.denizd.qwark.database.QwarkRepository
import com.denizd.qwark.databinding.ActivityMainBinding
import com.denizd.qwark.fragment.*
import com.denizd.qwark.util.Dependencies
import com.denizd.qwark.util.QwarkPreferences
import com.denizd.qwark.util.ExamNotificationWorker
import com.denizd.qwark.viewmodel.MainViewModel
import kotlin.math.hypot
import java.util.concurrent.TimeUnit
import kotlin.random.Random
class MainActivity : FragmentActivity() {
@@ -68,25 +63,44 @@ class MainActivity : FragmentActivity() {
// TODO reselected listener for scrolling up
resources.getStringArray(R.array.greetings).also { array ->
binding.greetingTextView.text = String.format(array[Random.nextInt(array.size)], "TODO")
binding.greetingTextView.text = String.format(
array[Random.nextInt(array.size)],
Dependencies.repo.prefs.getScoreProfileName()
)
}
Handler().postDelayed({
binding.splashScreen.apply {
val cx = width / 2
val cy = height / 2
val initialRadius = hypot(cx.toDouble(), cy.toDouble()).toFloat()
// splash screen
// Handler().postDelayed({
// binding.splashScreen.apply {
// val cx = width / 2
// val cy = height / 2
// val initialRadius = hypot(cx.toDouble(), cy.toDouble()).toFloat()
//
// ViewAnimationUtils.createCircularReveal(this, cx, cy, initialRadius, 0f).also { anim ->
// anim.addListener(object : AnimatorListenerAdapter() {
// override fun onAnimationEnd(animation: Animator) {
// super.onAnimationEnd(animation)
// visibility = View.GONE
// }
// })
// anim.start()
// }
// }
// }, 1200)
ViewAnimationUtils.createCircularReveal(this, cx, cy, initialRadius, 0f).also { anim ->
anim.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
visibility = View.GONE
}
})
anim.start()
Dependencies.repo.allCoursesWithExams.observe(this, Observer { courses ->
with (WorkManager.getInstance(this)) {
for (course in courses) {
beginUniqueWork(
"${course.name}-${course.courseId}",
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequestBuilder<ExamNotificationWorker>()
.setInitialDelay(getTimeUntil(course.examTime), TimeUnit.MILLISECONDS)
.setInputData(Data.Builder().putString("course", course.name).build())
.build()
).enqueue()
}
}
}, 1200)
})
}
override fun onResume() {
@@ -105,9 +119,9 @@ class MainActivity : FragmentActivity() {
}
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment, type)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit()
.replace(R.id.fragment_container, fragment, type)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit()
// with (supportFragmentManager) {
// beginTransaction()
@@ -139,4 +153,7 @@ class MainActivity : FragmentActivity() {
}
}
}
// TODO add option to set time of notification (currently: 7 AM)
private fun getTimeUntil(examTime: Long): Long = examTime + viewModel.getExamNotificationTime() - System.currentTimeMillis()
}
@@ -20,6 +20,9 @@ interface QwarkDao {
@Query("SELECT course.*, (SELECT MIN(exam_time) FROM grade WHERE grade.course_id = course.course_id AND exam_time >= :time) AS exam_time FROM course GROUP BY course.course_id ORDER BY time")
fun getAllCourseExams(time: Long = QwarkUtil.timeAtMidnight): List<CourseExam>
@Query("SELECT course.*, (SELECT MIN(exam_time) FROM grade WHERE grade.course_id = course.course_id AND exam_time >= :time) AS exam_time FROM course WHERE exam_time > :time GROUP BY course.course_id ORDER BY time")
fun getAllCoursesWithExams(time: Long = QwarkUtil.timeAtMidnight + 25200000L): LiveData<List<CourseExam>>
@Query("SELECT *, -1 AS exam_time FROM course WHERE course_id != :u ORDER BY time")
fun getAllCourses(u: Int = -1): List<CourseExam> // the u variable is an unnecessary workaround to return a list instead of LiveData
@@ -83,7 +83,6 @@ class QwarkRepository(app: Application) {
prefs.setInt(PreferenceKey.SCORE_PROFILE_ID, scoreProfile.scoreProfileId)
.setString(PreferenceKey.SCORE_PROFILE_NAME, scoreProfile.name)
}
fun setCurrentSchoolYear(schoolYear: SchoolYear) {
prefs.setInt(PreferenceKey.SCHOOL_YEAR_ID, schoolYear.yearId)
.setString(PreferenceKey.SCHOOL_YEAR_NAME, schoolYear.year)
@@ -122,7 +121,6 @@ class QwarkRepository(app: Application) {
dao.update(SchoolYear(name, prefs.getCurrentSchoolYearId()))
setCurrentSchoolYear(SchoolYear(name, prefs.getCurrentSchoolYearId()))
}
fun updateScoreProfile(name: String) {
dao.update(ScoreProfile(name, prefs.getScoreProfileId()))
setCurrentScoreProfile(ScoreProfile(name, prefs.getScoreProfileId()))
@@ -137,7 +135,6 @@ class QwarkRepository(app: Application) {
dao.deleteSchoolYear(prefs.getCurrentSchoolYearId())
setCurrentSchoolYear(SchoolYear("", 0))
}
fun deleteScoreProfile() {
dao.deleteScoreProfile(prefs.getScoreProfileId())
setCurrentScoreProfile(ScoreProfile("", 0))
@@ -148,6 +145,7 @@ class QwarkRepository(app: Application) {
val allCoursesForToday: LiveData<List<Course>> = dao.getCoursesByDayType(getDayType(), prefs.getSchoolYear())
val allYears: LiveData<List<SchoolYear>> = dao.allYears
val allScoreProfiles: LiveData<List<ScoreProfile>> = dao.allScoreProfiles
val allCoursesWithExams: LiveData<List<CourseExam>> = dao.getAllCoursesWithExams()
// get
fun getCourse(courseId: Int): CourseExam = dao.getCourse(courseId)
@@ -166,7 +164,7 @@ class QwarkRepository(app: Application) {
(participations.map { it.timesSpoken }.sum())
)
}
fun getDayType(): String = if (dayId in 1..5) days[dayId - 1] else days[1]
private fun getDayType(): String = if (dayId in 1..5) days[dayId - 1] else days[1]
private fun getMaxFinalScoreId(increment: Boolean): Int {
if (increment) {
prefs.setInt(PreferenceKey.MAX_SCORE_PROFILE_ID, getMaxFinalScoreId(false) + 1)
@@ -1,8 +1,10 @@
package com.denizd.qwark.fragment
import android.app.TimePickerDialog
import android.os.Bundle
import android.view.View
import android.widget.ArrayAdapter
import android.widget.TimePicker
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import com.denizd.lawrence.util.viewBinding
@@ -13,10 +15,12 @@ import com.denizd.qwark.sheet.ScoreProfileCreateSheet
import com.denizd.qwark.sheet.YearCreateSheet
import com.denizd.qwark.databinding.SettingsFragmentBinding
import com.denizd.qwark.model.SchoolYear
import com.denizd.qwark.util.toHourAndSecond
import com.denizd.qwark.util.toMillis
import com.denizd.qwark.viewmodel.SettingsViewModel
import com.google.android.material.snackbar.Snackbar
class SettingsFragment : QwarkFragment(R.layout.settings_fragment) {
class SettingsFragment : QwarkFragment(R.layout.settings_fragment), TimePickerDialog.OnTimeSetListener {
private val binding: SettingsFragmentBinding by viewBinding(SettingsFragmentBinding::bind)
private val viewModel: SettingsViewModel by viewModels()
@@ -166,7 +170,7 @@ class SettingsFragment : QwarkFragment(R.layout.settings_fragment) {
viewModel.allScoreProfiles.observe(viewLifecycleOwner, Observer { scoreProfiles ->
binding.scoreProfileDropdown.apply {
setAdapter(
ArrayAdapter<String>(
ArrayAdapter(
context,
R.layout.dropdown_item,
scoreProfiles.map { s -> s.name }
@@ -203,6 +207,24 @@ class SettingsFragment : QwarkFragment(R.layout.settings_fragment) {
openBottomSheet(deleteScoreProfileSheet)
}
}
setExamNotificationTimeText()
binding.setExamNotificationTimeButton.setOnClickListener {
val time = viewModel.getExamNotificationTime().toHourAndSecond()
TimePickerDialog(context, this, time.first, time.second, true).show()
}
}
override fun onTimeSet(view: TimePicker?, hourOfDay: Int, minute: Int) {
viewModel.setExamNotificationTime(Pair(hourOfDay, minute).toMillis())
setExamNotificationTimeText()
}
private fun setExamNotificationTimeText() {
val time = viewModel.getExamNotificationTime().toHourAndSecond()
val examNotificationTime = with (time) {
"${first.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}"
}
binding.examNotificationTimeTextView.text = getString(R.string.exam_notification_time_placeholder, examNotificationTime)
}
fun copyCourses(yearId: Int) {
@@ -0,0 +1,7 @@
package com.denizd.qwark.util
fun Pair<Int, Int>.toMillis(): Long = (first.toLong() * 60 * 60 * 1000) + (second.toLong() * 60 * 1000)
fun Long.toHourAndSecond(): Pair<Int, Int> {
val minutes = (this / (1000 * 60)).toInt()
return Pair(minutes / 60, minutes % 60)
}
@@ -0,0 +1,66 @@
package com.denizd.qwark.util
import android.annotation.TargetApi
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.denizd.qwark.R
import com.denizd.qwark.activity.MainActivity
private const val notificationChannelId = "qwark_exam_notification"
class ExamNotificationWorker(private val context: Context, workerParams: WorkerParameters) : Worker(context.applicationContext, workerParams) {
override fun doWork(): Result {
sendNotification(course = inputData.getString("course") ?: "error")
return Result.success()
}
// TODO launch fragment from intent and launch the fragment for the course
private fun sendNotification(course: String) {
val openAppPending = PendingIntent.getActivity(
context, 0,
Intent(context, MainActivity::class.java).also { intent ->
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + Intent.FLAG_ACTIVITY_CLEAR_TASK
}, 0
)
val notificationService = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel(context, notificationService)
}
val notification = NotificationCompat.Builder(context, notificationChannelId)
// .setStyle(NotificationCompat.BigTextStyle())
.setContentTitle(context.getString(R.string.exam_reminder, course))
.setContentText(context.getString(R.string.writing_an_exam_today, course))
.setContentIntent(openAppPending)
.setAutoCancel(true)
.setColor(context.getColor(R.color.colorAccent))
.setSmallIcon(R.drawable.bolt)
.build()
notificationService.notify(42, notification)
}
@TargetApi(26)
private fun createNotificationChannel(context: Context, notificationManager: NotificationManager) {
if (!Dependencies.repo.prefs.getIsNotificationChannelCreated()) {
notificationManager.createNotificationChannel(NotificationChannel(
notificationChannelId, context.getString(R.string.exams), NotificationManager.IMPORTANCE_DEFAULT
).also { channel ->
channel.enableLights(true)
channel.lightColor = Color.GREEN
})
Dependencies.repo.prefs.setIsNotificationChannelCreated(true)
}
}
}
@@ -18,5 +18,9 @@ enum class PreferenceKey(val value: String) {
// Bools
SHOW_GRADE_AVERAGE("show_grade_average"),
FIRST_LAUNCH("first_launch")
FIRST_LAUNCH("first_launch"),
IS_NOTIFICATION_CHANNEL_CREATED("is_notification_channel_created"),
// Longs
EXAM_NOTIFICATION_TIME("exam_notification_time")
}
@@ -12,6 +12,7 @@ class QwarkPreferences(context: Context) {
fun getString(key: PreferenceKey, default: String = ""): String = prefs.getString(key.value, default) ?: default
fun getInt(key: PreferenceKey, default: Int = 0): Int = prefs.getInt(key.value, default)
fun getBool(key: PreferenceKey, default: Boolean = false): Boolean = prefs.getBoolean(key.value, default)
fun getLong(key: PreferenceKey, default: Long = 0L): Long = prefs.getLong(key.value, default)
fun setString(key: PreferenceKey, value: String): QwarkPreferences {
edit.putString(key.value, value).apply()
@@ -25,6 +26,10 @@ class QwarkPreferences(context: Context) {
edit.putBoolean(key.value, value).apply()
return this
}
fun setLong(key: PreferenceKey, value: Long): QwarkPreferences {
edit.putLong(key.value, value).apply()
return this
}
// TODO remove all of these
fun getShowGradeAverage(): Boolean = getBool(PreferenceKey.SHOW_GRADE_AVERAGE)
@@ -40,6 +45,8 @@ class QwarkPreferences(context: Context) {
fun getFinalScoreId(): Int = getInt(PreferenceKey.SCORE_PROFILE_ID)
fun getFirstLaunch(): Boolean = getBool(PreferenceKey.FIRST_LAUNCH)
fun getCurrentSchoolYearId(): Int = getInt(PreferenceKey.SCHOOL_YEAR_ID)
fun getIsNotificationChannelCreated(): Boolean = getBool(PreferenceKey.IS_NOTIFICATION_CHANNEL_CREATED)
fun getExamNotificationTime(): Long = getLong(PreferenceKey.EXAM_NOTIFICATION_TIME, 25200000L)
fun setCourseSortType(value: Int) { setInt(PreferenceKey.COURSE_SORT_TYPE, value) }
fun setParticipationDisplay(value: Int) { setInt(PreferenceKey.PARTICIPATION_DISPLAY, value) }
@@ -48,4 +55,6 @@ class QwarkPreferences(context: Context) {
fun setFirstLaunch(value: Boolean) { setBool(PreferenceKey.FIRST_LAUNCH, value) }
fun setShowGradeAverage(value: Boolean) { setBool(PreferenceKey.SHOW_GRADE_AVERAGE, value) }
fun setGradeType(value: Int) { setInt(PreferenceKey.GRADE_TYPE, value) }
fun setIsNotificationChannelCreated(value: Boolean) { setBool(PreferenceKey.IS_NOTIFICATION_CHANNEL_CREATED, value) }
fun setExamNotificationTime(value: Long) { setLong(PreferenceKey.EXAM_NOTIFICATION_TIME, value) }
}
@@ -59,4 +59,6 @@ class MainViewModel(val app: Application) : QwarkViewModel(app) {
repo.prefs.setFirstLaunch(false)
}
}
fun getExamNotificationTime(): Long = repo.prefs.getExamNotificationTime()
}
@@ -5,6 +5,7 @@ import androidx.lifecycle.LiveData
import com.denizd.qwark.database.QwarkClient
import com.denizd.qwark.model.ScoreProfile
import com.denizd.qwark.model.SchoolYear
import com.denizd.qwark.util.PreferenceKey
class SettingsViewModel(private val app: Application) : QwarkViewModel(app) {
@@ -21,6 +22,7 @@ class SettingsViewModel(private val app: Application) : QwarkViewModel(app) {
fun getCurrentSchoolYearId(): Int = repo.prefs.getCurrentSchoolYearId()
fun getParticipationDisplay(): Int = repo.prefs.getParticipationDisplay()
fun getParticipationDay(): Int = repo.prefs.getParticipationDay()
fun getExamNotificationTime(): Long = repo.prefs.getExamNotificationTime()
// Preference setters
fun setAppTheme(value: Int) { repo.prefs.setAppTheme(value) }
@@ -31,6 +33,7 @@ class SettingsViewModel(private val app: Application) : QwarkViewModel(app) {
fun updateSchoolYear(schoolYear: SchoolYear) { repo.setCurrentSchoolYear(schoolYear) }
fun setParticipationDisplay(value: Int) { repo.prefs.setParticipationDisplay(value) }
fun setParticipationDay(value: Int) { repo.prefs.setParticipationDay(value) }
fun setExamNotificationTime(value: Long) = repo.prefs.setExamNotificationTime(value)
// Database functions
fun insert(schoolYear: SchoolYear) = doAsync { repo.insert(schoolYear) }
+8 -4
View File
@@ -1,5 +1,9 @@
<vector android:height="250dp" android:viewportHeight="80.79835"
android:viewportWidth="53.145966" android:width="165dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@color/colorSplashLogo"
android:pathData="m13.1664,80.416c-0.3983,-0.2161 -0.8014,-0.7006 -0.8958,-1.0765 -0.1244,-0.4955 1.4236,-5.6423 5.6281,-18.7126C23.6983,42.5979 23.6983,42.5979 12.4059,42.4656 1.7759,42.3411 1.0807,42.3038 0.5567,41.8293 0.2087,41.5142 0,41.0033 0,40.4664c0,-1.3788 12.2864,-39.2443 12.9487,-39.9066 0.5548,-0.5548 0.6746,-0.5598 13.6215,-0.5598 12.2693,0 13.0941,0.0293 13.5949,0.4825 1.1973,1.0835 1.1729,1.1538 -4.7012,13.5387 -3.0613,6.4544 -5.5659,11.8038 -5.5659,11.8877 0,0.0839 4.9799,0.1525 11.0664,0.1525 11.0664,0 11.0664,0 11.6606,0.6325 0.411,0.4375 0.568,0.8836 0.5091,1.4469 -0.0614,0.5874 -5.2732,7.9334 -18.7031,26.3618 -10.2399,14.0511 -18.8687,25.7229 -19.175,25.9375 -0.6914,0.4843 -1.1627,0.4788 -2.0895,-0.024zM36.4322,30.0933C25.9694,30.0251 25.8162,30.0163 25.2154,29.4519 24.8587,29.1168 24.6063,28.5702 24.6063,28.133c0,-0.4239 2.39,-5.7794 5.529,-12.3893 5.529,-11.6426 5.529,-11.6426 -4.4337,-11.6426 -9.9627,0 -9.9627,0 -15.3537,16.7349 -2.965,9.2042 -5.4434,16.88 -5.5075,17.0574 -0.0902,0.2497 2.4609,0.3393 11.3035,0.3969 11.42,0.0743 11.42,0.0743 11.9256,0.699 0.2781,0.3435 0.5056,0.9045 0.5056,1.2466 0,0.3421 -1.8455,6.3614 -4.101,13.3763 -2.2556,7.0149 -4.101,12.8701 -4.101,13.0115 0,0.1414 6.0001,-8.0045 13.3335,-18.102C47.0398,30.1625 47.0398,30.1625 36.4322,30.0933Z" android:strokeWidth="0.26458335"/>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512.0014">
<path
android:fillColor="@color/colorText"
android:pathData="m406,211h-124.145l107.168,-188.559c2.652,-4.645 2.637,-10.344 -0.043,-14.973 -2.695,-4.625 -7.633,-7.469 -12.98,-7.469h-180c-6.457,0 -12.188,4.133 -14.223,10.254l-90,271c-1.539,4.57 -0.762,9.609 2.051,13.52 2.828,3.914 7.355,6.227 12.172,6.227h127.254l-81.035,190.098c-2.902,6.754 -0.469,14.621 5.742,18.559 6.109,3.906 14.316,2.879 19.246,-2.691l240,-271c3.926,-4.41 4.895,-10.727 2.477,-16.102 -2.418,-5.391 -7.777,-8.863 -13.684,-8.863zM406,211"/>
</vector>
@@ -1,170 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>
+1 -1
View File
@@ -8,7 +8,7 @@
<item
android:drawable="@drawable/bolt"
android:gravity="center"
android:width="120dp"
android:width="184dp"
android:height="184dp"
android:scaleType="centerInside">
</item>
+2 -1
View File
@@ -128,7 +128,8 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="false">
android:animateLayoutChanges="false"
android:visibility="gone">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/splash_screen"
+31 -3
View File
@@ -97,7 +97,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="@string/grades"
android:text="@string/courses"
android:textColor="@color/colorAccent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@@ -106,6 +106,34 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
style="@style/SelectableItemBackground"
android:id="@+id/set_exam_notification_time_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:paddingVertical="8dp"
android:paddingHorizontal="16dp"
android:focusable="true"
android:clickable="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/set_exam_notification_time"/>
<TextView
android:id="@+id/exam_notification_time_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/colorHint"
android:textSize="12sp"/>
</LinearLayout>
<TextView
android:id="@+id/show_grade_average_textview"
android:layout_width="wrap_content"
@@ -114,7 +142,7 @@
android:text="@string/show_grade_average_title"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="@+id/set_exam_notification_time_button"
app:layout_constraintStart_toStartOf="parent"/>
<TextView
@@ -133,7 +161,7 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="@+id/set_exam_notification_time_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 954 B

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 13 KiB

+5
View File
@@ -16,6 +16,9 @@
<string name="alert_create_school_year_title">Erstelle ein Schuljahr</string>
<string name="alert_create_school_year_desc">Bevor du Kurse und Abschlussnoten hinzufügst, gehe in die Einstellungen und erstelle ein Schuljahr und ein Abschlussprofil!</string>
<string name="to_be_implemented">Wird noch hinzugefügt</string>
<string name="exams">Klausuren</string>
<string name="exam_reminder">Klausurerinnerung: %s</string>
<string name="writing_an_exam_today">Heute schreibst du eine Klausur in %s!</string>
<!-- CourseFragment.kt -->
<string name="course">Kurs</string>
@@ -145,6 +148,8 @@
<!-- SettingsFragment.kt -->
<string name="settings">Einstellungen</string>
<string name="set_exam_notification_time">Stelle Zeit für Klausurbenachrichtigung</string>
<string name="exam_notification_time_placeholder">Derzeit eingestellt auf %s</string>
<string name="course_sort_type">Sortiere Kurse nach</string>
<string-array name="course_sort_type_options">
<item>Zeit hinzugefügt (aufsteigend)</item>
+5
View File
@@ -18,6 +18,9 @@
<string name="to_be_implemented">To be implemented</string>
<string name="powered_by_qwark" translatable="false">Powered by Qwark</string>
<string name="made_by_deniz" translatable="false">Made by Deniz</string>
<string name="exams">Exams</string>
<string name="exam_reminder">Exam reminder: %s</string>
<string name="writing_an_exam_today">You\'re writing an exam in %s today!</string>
<string-array name="greetings">
<item>Hello %s!</item>
@@ -211,6 +214,8 @@
<!-- SettingsFragment.kt -->
<string name="settings">Settings</string>
<string name="set_exam_notification_time">Set exam notification time</string>
<string name="exam_notification_time_placeholder">Currently set to %s</string>
<string name="course_sort_type">Sort courses by</string>
<string-array name="course_sort_type_options">
<item>Time added (ascending)</item>