Фикс невозможности запуска службы переднего плана из LinkUpdateWorker.

Небольшие визуальные изменения.
This commit is contained in:
2024-10-10 01:27:26 +04:00
parent 3da65a3327
commit 2a7e63dce4
21 changed files with 388 additions and 103 deletions

View File

@@ -33,8 +33,8 @@ android {
applicationId = "ru.n08i40k.polytechnic.next" applicationId = "ru.n08i40k.polytechnic.next"
minSdk = 26 minSdk = 26
targetSdk = 35 targetSdk = 35
versionCode = 12 versionCode = 13
versionName = "1.7.0" versionName = "1.7.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {

View File

@@ -2,11 +2,20 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<!-- bruh -->
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<!-- For posting notifications from FCM and CLV services -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- For CLV service able to work -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<!-- For schedule CLV service-->
<!-- <uses-permission android:name="android.permission.USE_EXACT_ALARM" />-->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application <application
android:name=".PolytechnicApplication" android:name=".PolytechnicApplication"
android:allowBackup="true" android:allowBackup="true"
@@ -18,6 +27,16 @@
android:theme="@style/Theme.PolytechnicNext" android:theme="@style/Theme.PolytechnicNext"
tools:targetApi="35"> tools:targetApi="35">
<receiver
android:name=".receiver.BootCompletedBroadcastReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".receiver.AlarmReceiver" />
<service <service
android:name=".service.MyFirebaseMessagingService" android:name=".service.MyFirebaseMessagingService"
android:exported="false"> android:exported="false">

View File

@@ -0,0 +1,5 @@
package ru.n08i40k.polytechnic.next
object IntentRequestCodes {
const val ALARM_CLV = 1337
}

View File

@@ -1,13 +1,20 @@
package ru.n08i40k.polytechnic.next package ru.n08i40k.polytechnic.next
import android.Manifest import android.Manifest
import android.app.AlarmManager
import android.app.Application import android.app.Application
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
import ru.n08i40k.polytechnic.next.data.AppContainer import ru.n08i40k.polytechnic.next.data.AppContainer
import ru.n08i40k.polytechnic.next.model.Group
import ru.n08i40k.polytechnic.next.receiver.AlarmReceiver
import ru.n08i40k.polytechnic.next.utils.or import ru.n08i40k.polytechnic.next.utils.or
import java.util.Calendar
import javax.inject.Inject import javax.inject.Inject
@HiltAndroidApp @HiltAndroidApp
@@ -27,4 +34,84 @@ class PolytechnicApplication : Application() {
|| ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) || ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
== PackageManager.PERMISSION_GRANTED) == PackageManager.PERMISSION_GRANTED)
} }
private fun getDate(group: Group): Calendar? {
val javaCalendar = Calendar.getInstance()
val currentMinutes = javaCalendar.get(Calendar.HOUR_OF_DAY) * 60 +
javaCalendar.get(Calendar.MINUTE)
var startDayIdx = javaCalendar.get(Calendar.DAY_OF_WEEK) - 2
println("Current day is $startDayIdx")
val currentDay = group.days[startDayIdx]
if (currentDay != null) {
val firstLesson = currentDay.first
if (firstLesson == null || firstLesson.time.start < currentMinutes) {
println("Current day already started or ended!")
++startDayIdx
}
}
for (dayIdx in startDayIdx..5) {
println("Trying $dayIdx day...")
val day = group.days[dayIdx] ?: continue
println("Day isn't null")
val firstLesson = day.first ?: continue
println("Day isn't empty")
val executeMinutes = (firstLesson.time.start - 15).coerceAtLeast(0)
println("Schedule minutes at $executeMinutes")
return Calendar.getInstance().apply {
set(Calendar.DAY_OF_WEEK, dayIdx + 2) // sunday is first + index from 0
set(Calendar.HOUR_OF_DAY, executeMinutes / 60)
set(Calendar.MINUTE, executeMinutes % 60)
set(Calendar.SECOND, 0)
// set(Calendar.MINUTE, get(Calendar.MINUTE) + 1)
}
}
return null
}
fun scheduleClvService(group: Group) {
// -1 = вс
// 0 = пн
// 1 = вт
// 2 = ср
// 3 = чт
// 4 = пт
// 5 = сб
println("Getting date...")
val date = getDate(group) ?: return
println("Alarm on this week!")
val alarmManager = applicationContext
.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
val pendingIntent =
Intent(applicationContext, AlarmReceiver::class.java).let {
PendingIntent.getBroadcast(
applicationContext,
IntentRequestCodes.ALARM_CLV,
it,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
}
if (alarmManager != null)
println("Alarm manager isn't null.")
alarmManager?.cancel(pendingIntent)
alarmManager?.set(
AlarmManager.RTC_WAKEUP,
date.timeInMillis,
pendingIntent
)
}
} }

View File

@@ -3,10 +3,11 @@ package ru.n08i40k.polytechnic.next.model
import android.os.Parcelable import android.os.Parcelable
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import ru.n08i40k.polytechnic.next.utils.getDayMinutes
import java.util.Calendar import java.util.Calendar
@Parcelize @Parcelize
@Suppress("unused") @Suppress("unused", "MemberVisibilityCanBePrivate")
@Serializable @Serializable
class Day( class Day(
val name: String, val name: String,
@@ -15,7 +16,7 @@ class Day(
val customIndices: ArrayList<Int>, val customIndices: ArrayList<Int>,
val lessons: ArrayList<Lesson?> val lessons: ArrayList<Lesson?>
) : Parcelable { ) : Parcelable {
fun getDistanceToNextByMinutes(from: Int): Map.Entry<Int, Int>? { fun distanceToNextByMinutes(from: Int): Pair<Int, Int>? {
val toIdx = lessons val toIdx = lessons
.map { if (it?.time == null) null else it.time.start } .map { if (it?.time == null) null else it.time.start }
.indexOfFirst { if (it == null) false else it > from } .indexOfFirst { if (it == null) false else it > from }
@@ -23,53 +24,79 @@ class Day(
if (toIdx == -1) if (toIdx == -1)
return null return null
return object : Map.Entry<Int, Int> { return Pair(toIdx, lessons[toIdx]!!.time.start - from)
override val key: Int
get() = toIdx
override val value: Int
get() = lessons[toIdx]!!.time!!.start - from
}
} }
fun getDistanceToNextByIdx(from: Int? = null): Map.Entry<Int, Int>? { fun distanceToNextByIdx(from: Int? = null): Pair<Int, Int>? {
val fromLesson = if (from != null) lessons[from] else null val fromLesson = if (from != null) lessons[from] else null
if (from != null && fromLesson?.time == null) if (from != null && fromLesson == null)
throw NullPointerException("Lesson (by given index) and it's time should be non-null!") throw NullPointerException("Lesson (by given index) and it's time should be non-null!")
val fromTime = val fromTime =
if (from != null) if (from != null)
fromLesson!!.time!!.end fromLesson!!.time.end
else else
Calendar.getInstance() Calendar.getInstance()
.get(Calendar.HOUR_OF_DAY) * 60 + Calendar.getInstance() .get(Calendar.HOUR_OF_DAY) * 60 + Calendar.getInstance()
.get(Calendar.MINUTE) .get(Calendar.MINUTE)
return getDistanceToNextByMinutes(fromTime) return distanceToNextByMinutes(fromTime)
} }
fun getCurrentLesson(): Map.Entry<Int, Lesson>? { // current
val minutes = Calendar.getInstance() val currentIdx: Int?
.get(Calendar.HOUR_OF_DAY) * 60 + Calendar.getInstance() get() {
.get(Calendar.MINUTE) val minutes = Calendar.getInstance().getDayMinutes()
for (lessonIdx in 0..<lessons.size) { for (lessonIdx in nonNullIndices) {
val lesson = lessons[lessonIdx] ?: continue val lesson = lessons[lessonIdx]!!
if (lesson.time == null if (lesson.time.start <= minutes && minutes < lesson.time.end)
|| minutes < lesson.time.start return lessonIdx
|| minutes >= lesson.time.end
)
continue
return object : Map.Entry<Int, Lesson> {
override val key: Int
get() = lessonIdx
override val value: Lesson
get() = lesson
} }
return null
} }
return null val current: Lesson?
} get() {
return lessons[currentIdx ?: return null]
}
val currentKV: Pair<Int, Lesson>?
get() {
val idx = currentIdx ?: return null
return Pair(idx, lessons[idx]!!)
}
// first
val firstIdx: Int?
get() = nonNullIndices.getOrNull(0)
val first: Lesson?
get() {
return lessons[firstIdx ?: return null]!!
}
val firstKV: Pair<Int, Lesson>?
get() {
val idx = firstIdx ?: return null
return Pair(idx, lessons[idx]!!)
}
// last
val lastIdx: Int?
get() = nonNullIndices.getOrNull(nonNullIndices.size - 1)
val last: Lesson?
get() {
return lessons[lastIdx ?: return null]!!
}
val lastKV: Pair<Int, Lesson>?
get() {
val idx = lastIdx ?: return null
return Pair(idx, lessons[idx]!!)
}
} }

View File

@@ -5,23 +5,31 @@ import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.util.Calendar import java.util.Calendar
@Suppress("MemberVisibilityCanBePrivate")
@Parcelize @Parcelize
@Serializable @Serializable
data class Group( data class Group(
val name: String, val name: String,
val days: ArrayList<Day?> val days: ArrayList<Day?>
) : Parcelable { ) : Parcelable {
fun getCurrentDay(): Map.Entry<Int, Day?>? { val currentIdx: Int?
val currentDay = (Calendar.getInstance().get(Calendar.DAY_OF_WEEK) - 2) get() {
val currentDay = (Calendar.getInstance().get(Calendar.DAY_OF_WEEK) - 2)
if (currentDay < 0 || currentDay > days.size - 1) if (currentDay < 0 || currentDay > days.size - 1)
return null return null
return object : Map.Entry<Int, Day?> { return currentDay
override val key: Int }
get() = currentDay
override val value: Day? val current: Day?
get() = days[currentDay] get() {
return days.getOrNull(currentIdx ?: return null)
}
val currentKV: Pair<Int, Day?>?
get() {
val idx = currentIdx ?: return null
return Pair(idx, days[idx])
} }
}
} }

View File

@@ -13,16 +13,14 @@ data class Lesson(
val type: LessonType, val type: LessonType,
val defaultIndex: Int, val defaultIndex: Int,
val name: String, val name: String,
val time: LessonTime?, val time: LessonTime,
val cabinets: ArrayList<String>, val cabinets: ArrayList<String>,
val teacherNames: ArrayList<String> val teacherNames: ArrayList<String>
) : Parcelable { ) : Parcelable {
fun getDuration(): Int? { val duration: Int
if (this.time == null) get() {
return null return time.end - time.start
}
return time.end - time.start
}
fun getNameAndCabinetsShort(context: Context): String { fun getNameAndCabinetsShort(context: Context): String {
val limitedName = name limit 15 val limitedName = name limit 15

View File

@@ -0,0 +1,42 @@
package ru.n08i40k.polytechnic.next.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.work.Constraints
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import ru.n08i40k.polytechnic.next.service.CurrentLessonViewService
import ru.n08i40k.polytechnic.next.work.ScheduleClvAlarm
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
println("Hi from AlarmReceiver")
if (intent == null) {
println("No intend provided!")
return
}
if (context == null) {
println("No context provided!")
return
}
println(intent.action)
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val rescheduleRequest = OneTimeWorkRequestBuilder<ScheduleClvAlarm>()
.setConstraints(constraints)
.build()
WorkManager
.getInstance(context)
.enqueue(rescheduleRequest)
CurrentLessonViewService.startService(context.applicationContext)
}
}

View File

@@ -0,0 +1,45 @@
package ru.n08i40k.polytechnic.next.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.work.Constraints
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import ru.n08i40k.polytechnic.next.work.ScheduleClvAlarm
import java.util.logging.Logger
class BootCompletedBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val logger = Logger.getLogger("BootCompletedBroadcastReceiver")
if (context == null) {
logger.warning("No context provided!")
return
}
if (intent == null) {
logger.warning("No intend provided!")
return
}
if (intent.action != "android.intent.action.BOOT_COMPLETED") {
logger.warning("Strange intent action passed!")
logger.warning(intent.action)
return
}
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val request = OneTimeWorkRequestBuilder<ScheduleClvAlarm>()
.setConstraints(constraints)
.build()
WorkManager
.getInstance(context)
.enqueue(request)
}
}

View File

@@ -19,9 +19,11 @@ import ru.n08i40k.polytechnic.next.model.Day
import ru.n08i40k.polytechnic.next.model.Group import ru.n08i40k.polytechnic.next.model.Group
import ru.n08i40k.polytechnic.next.model.Lesson import ru.n08i40k.polytechnic.next.model.Lesson
import ru.n08i40k.polytechnic.next.utils.fmtAsClock import ru.n08i40k.polytechnic.next.utils.fmtAsClock
import ru.n08i40k.polytechnic.next.utils.getDayMinutes
import ru.n08i40k.polytechnic.next.work.StartClvService import ru.n08i40k.polytechnic.next.work.StartClvService
import java.util.Calendar import java.util.Calendar
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
class CurrentLessonViewService : Service() { class CurrentLessonViewService : Service() {
companion object { companion object {
private const val NOTIFICATION_STATUS_ID = 1337 private const val NOTIFICATION_STATUS_ID = 1337
@@ -58,16 +60,16 @@ class CurrentLessonViewService : Service() {
.get(Calendar.HOUR_OF_DAY) * 60 + Calendar.getInstance() .get(Calendar.HOUR_OF_DAY) * 60 + Calendar.getInstance()
.get(Calendar.MINUTE) .get(Calendar.MINUTE)
val currentLessonEntry = day!!.getCurrentLesson() val currentLessonEntry = day!!.currentKV
val currentLessonIdx: Int? = currentLessonEntry?.key val currentLessonIdx: Int? = currentLessonEntry?.first
val currentLesson: Lesson? = currentLessonEntry?.value val currentLesson: Lesson? = currentLessonEntry?.second
val nextLessonEntry = day!!.getDistanceToNextByIdx(currentLessonIdx) val nextLessonEntry = day!!.distanceToNextByIdx(currentLessonIdx)
val nextLesson = val nextLesson =
if (nextLessonEntry == null) if (nextLessonEntry == null)
null null
else else
day!!.lessons[nextLessonEntry.key] day!!.lessons[nextLessonEntry.first]
if (currentLesson == null && nextLesson == null) { if (currentLesson == null && nextLesson == null) {
val notification = NotificationCompat val notification = NotificationCompat
@@ -83,13 +85,13 @@ class CurrentLessonViewService : Service() {
return return
} }
val firstLessonIdx = day!!.getDistanceToNextByMinutes(0)?.key val firstLessonIdx = day!!.distanceToNextByMinutes(0)?.first
?: throw NullPointerException("Is this even real?") ?: throw NullPointerException("Is this even real?")
val distanceToFirst = day!!.lessons[firstLessonIdx]!!.time!!.start - currentMinutes val distanceToFirst = day!!.lessons[firstLessonIdx]!!.time!!.start - currentMinutes
val currentLessonDelay = val currentLessonDelay =
if (currentLesson == null) // Если эта пара - перемена, то конец перемены через (результат getDistanceToNext) if (currentLesson == null) // Если эта пара - перемена, то конец перемены через (результат getDistanceToNext)
nextLessonEntry!!.value nextLessonEntry!!.second
else // Если эта пара - обычная пара, то конец пары через (конец этой пары - текущее кол-во минут) else // Если эта пара - обычная пара, то конец пары через (конец этой пары - текущее кол-во минут)
currentLesson.time!!.end - currentMinutes currentLesson.time!!.end - currentMinutes
@@ -157,32 +159,29 @@ class CurrentLessonViewService : Service() {
return getSystemService(NOTIFICATION_SERVICE) as NotificationManager return getSystemService(NOTIFICATION_SERVICE) as NotificationManager
} }
fun updateSchedule(group: Group?): Boolean { private fun updateSchedule(group: Group?): Boolean {
if (group == null) { if (group == null) {
stopSelf() stopSelf()
return false return false
} }
val day = group.getCurrentDay() val currentDay = group.current
if (day?.value == null) { if (currentDay == null || currentDay.nonNullIndices.isEmpty()) {
stopSelf() stopSelf()
return false return false
} }
val dayValue = day.value!!
if (this.day == null) { if (this.day == null) {
if (dayValue.lessons[dayValue.defaultIndices[dayValue.defaultIndices.lastIndex]]!!.time!!.end val nowMinutes = Calendar.getInstance().getDayMinutes()
<= Calendar.getInstance()
.get(Calendar.HOUR_OF_DAY) * 60 + Calendar.getInstance() if (currentDay.first!!.time.start - nowMinutes > 30
.get(Calendar.MINUTE) || currentDay.last!!.time.end < nowMinutes) {
) {
stopSelf() stopSelf()
return false return false
} }
} }
this.day = dayValue this.day = currentDay
this.handler.removeCallbacks(updateRunnable) this.handler.removeCallbacks(updateRunnable)
updateRunnable.run() updateRunnable.run()

View File

@@ -20,6 +20,7 @@ import com.google.firebase.messaging.RemoteMessage
import ru.n08i40k.polytechnic.next.NotificationChannels import ru.n08i40k.polytechnic.next.NotificationChannels
import ru.n08i40k.polytechnic.next.R import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.work.FcmSetTokenWorker import ru.n08i40k.polytechnic.next.work.FcmSetTokenWorker
import ru.n08i40k.polytechnic.next.work.ScheduleClvAlarm
import java.time.Duration import java.time.Duration
class MyFirebaseMessagingService : FirebaseMessagingService() { class MyFirebaseMessagingService : FirebaseMessagingService() {
@@ -100,6 +101,18 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
NotificationCompat.PRIORITY_DEFAULT, NotificationCompat.PRIORITY_DEFAULT,
message.data["etag"] message.data["etag"]
) )
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val request = OneTimeWorkRequestBuilder<ScheduleClvAlarm>()
.setConstraints(constraints)
.build()
WorkManager
.getInstance(applicationContext)
.enqueue(request)
} }
"app-update" -> { "app-update" -> {

View File

@@ -10,6 +10,7 @@ import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -18,6 +19,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -30,6 +33,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@Composable @Composable
@@ -53,7 +57,8 @@ fun ExpandableCard(
onExpandedChange() onExpandedChange()
transitionState.targetState = expanded transitionState.targetState = expanded
}, },
shape = RectangleShape colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
border = BorderStroke(Dp.Hairline, MaterialTheme.colorScheme.inverseSurface)
) { ) {
Column { Column {
ExpandableCardHeader(title, transition) ExpandableCardHeader(title, transition)

View File

@@ -3,7 +3,6 @@ package ru.n08i40k.polytechnic.next.ui
import android.Manifest import android.Manifest
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
@@ -38,7 +37,6 @@ import ru.n08i40k.polytechnic.next.NotificationChannels
import ru.n08i40k.polytechnic.next.PolytechnicApplication import ru.n08i40k.polytechnic.next.PolytechnicApplication
import ru.n08i40k.polytechnic.next.R import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.data.MyResult import ru.n08i40k.polytechnic.next.data.MyResult
import ru.n08i40k.polytechnic.next.service.CurrentLessonViewService
import ru.n08i40k.polytechnic.next.settings.settingsDataStore import ru.n08i40k.polytechnic.next.settings.settingsDataStore
import ru.n08i40k.polytechnic.next.work.FcmUpdateCallbackWorker import ru.n08i40k.polytechnic.next.work.FcmUpdateCallbackWorker
import ru.n08i40k.polytechnic.next.work.LinkUpdateWorker import ru.n08i40k.polytechnic.next.work.LinkUpdateWorker
@@ -103,10 +101,7 @@ class MainActivity : ComponentActivity() {
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
} }
private fun startTestService() { private fun scheduleAlarm() {
if (!(applicationContext as PolytechnicApplication).hasNotificationPermission())
return
lifecycleScope.launch { lifecycleScope.launch {
val schedule = (applicationContext as PolytechnicApplication) val schedule = (applicationContext as PolytechnicApplication)
.container .container
@@ -116,11 +111,8 @@ class MainActivity : ComponentActivity() {
if (schedule is MyResult.Failure) if (schedule is MyResult.Failure)
return@launch return@launch
val intent = Intent(this@MainActivity, CurrentLessonViewService::class.java) (applicationContext as PolytechnicApplication)
.apply { .scheduleClvService((schedule as MyResult.Success).data)
putExtra("group", (schedule as MyResult.Success).data)
}
startForegroundService(intent)
} }
} }
@@ -190,7 +182,7 @@ class MainActivity : ComponentActivity() {
setupFirebaseConfig() setupFirebaseConfig()
handleUpdate() handleUpdate()
startTestService() scheduleAlarm()
setContent { setContent {
Box(Modifier.windowInsetsPadding(WindowInsets.safeContent.only(WindowInsetsSides.Top))) { Box(Modifier.windowInsetsPadding(WindowInsets.safeContent.only(WindowInsetsSides.Top))) {

View File

@@ -7,8 +7,9 @@ import androidx.activity.ComponentActivity
import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearOutSlowInEasing import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideIn import androidx.compose.animation.slideIn
import androidx.compose.animation.slideOut
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@@ -95,19 +96,25 @@ private fun NavHostContainer(
enterTransition = { enterTransition = {
slideIn( slideIn(
animationSpec = tween( animationSpec = tween(
500, 400,
delayMillis = 250, delayMillis = 250,
easing = LinearOutSlowInEasing easing = LinearOutSlowInEasing
) )
) { fullSize -> IntOffset(-fullSize.width, 0) } ) { fullSize -> IntOffset(0, fullSize.height / 16) } + fadeIn(
animationSpec = tween(
400,
delayMillis = 250,
easing = LinearOutSlowInEasing
)
)
}, },
exitTransition = { exitTransition = {
slideOut( fadeOut(
animationSpec = tween( animationSpec = tween(
500, 250,
easing = FastOutSlowInEasing easing = FastOutSlowInEasing
) )
) { fullSize -> IntOffset(fullSize.width, 0) } )
}, },
builder = { builder = {
composable("profile") { composable("profile") {

View File

@@ -66,9 +66,7 @@ fun calculateCurrentLessonIdx(lessons: ArrayList<Lesson?>): Int {
val filteredLessons = lessons val filteredLessons = lessons
.filterNotNull() .filterNotNull()
.filter { .filter {
it.time != null it.time.start <= currentMinutes && it.time.end >= currentMinutes
&& it.time.start <= currentMinutes
&& it.time.end >= currentMinutes
} }
if (filteredLessons.isEmpty()) if (filteredLessons.isEmpty())
@@ -101,16 +99,20 @@ fun DayCard(
modifier = modifier, modifier = modifier,
colors = CardDefaults.cardColors( colors = CardDefaults.cardColors(
containerColor = containerColor =
if (current) MaterialTheme.colorScheme.surfaceContainerHighest if (current) MaterialTheme.colorScheme.inverseSurface
else MaterialTheme.colorScheme.surfaceContainerLowest else MaterialTheme.colorScheme.surface
) ),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.inverseSurface)
) { ) {
if (day == null) { if (day == null) {
Text( Text(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
text = stringResource(R.string.day_null) text = stringResource(R.string.day_null),
color =
if (current) MaterialTheme.colorScheme.inverseOnSurface
else MaterialTheme.colorScheme.onSurface
) )
return@Card return@Card
} }

View File

@@ -37,10 +37,10 @@ fun DayPager(group: Group = FakeScheduleRepository.exampleGroup) {
val offset = pagerState.getOffsetDistanceInPages(page).absoluteValue val offset = pagerState.getOffsetDistanceInPages(page).absoluteValue
lerp( lerp(
start = 0.95f, stop = 1f, fraction = 1f - offset.coerceIn(0f, 1f) start = 1f, stop = 0.95f, fraction = 1f - offset.coerceIn(0f, 1f)
).also { scale -> ).also { scale ->
scaleX = 1F - scale + 0.95F scaleX = scale
scaleY = 1F - scale + 0.95F scaleY = scale
} }
alpha = lerp( alpha = lerp(
start = 0.5f, stop = 1f, fraction = 1f - offset.coerceIn(0f, 1f) start = 0.5f, stop = 1f, fraction = 1f - offset.coerceIn(0f, 1f)

View File

@@ -79,7 +79,7 @@ fun LessonExtraInfo(
append(stringResource(R.string.lesson_duration)) append(stringResource(R.string.lesson_duration))
append(" - ") append(" - ")
val duration = val duration =
if (lesson.time != null) lesson.time.end - lesson.time.start else 0 lesson.time.end - lesson.time.start
append(duration / 60) append(duration / 60)
append(stringResource(R.string.hours)) append(stringResource(R.string.hours))
@@ -215,9 +215,7 @@ fun FreeLessonRow(
) { ) {
LessonViewRow( LessonViewRow(
-1, -1,
if (lesson.time != null && nextLesson.time != null) LessonTime( LessonTime(lesson.time.end, nextLesson.time.start),
lesson.time.end, nextLesson.time.start
) else null,
LessonTimeFormat.ONLY_MINUTES_DURATION, LessonTimeFormat.ONLY_MINUTES_DURATION,
stringResource(R.string.lesson_break), stringResource(R.string.lesson_break),
arrayListOf(), arrayListOf(),

View File

@@ -2,7 +2,9 @@ package ru.n08i40k.polytechnic.next.ui.main.schedule
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -15,6 +17,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
@@ -79,6 +82,7 @@ fun ScheduleScreen(
val hasSchedule = uiState as ScheduleUiState.HasSchedule val hasSchedule = uiState as ScheduleUiState.HasSchedule
UpdateInfo(hasSchedule.lastUpdateAt, hasSchedule.updateDates) UpdateInfo(hasSchedule.lastUpdateAt, hasSchedule.updateDates)
Spacer(Modifier.height(10.dp))
DayPager(hasSchedule.group) DayPager(hasSchedule.group)
} }
} }

View File

@@ -1,5 +1,7 @@
package ru.n08i40k.polytechnic.next.utils package ru.n08i40k.polytechnic.next.utils
import java.util.Calendar
infix fun <T> T?.or(data: T): T { infix fun <T> T?.or(data: T): T {
if (this == null) if (this == null)
return data return data
@@ -26,3 +28,6 @@ infix fun String.limit(count: Int): String {
.trimEnd() .trimEnd()
.plus("") .plus("")
} }
fun Calendar.getDayMinutes(): Int =
this.get(Calendar.HOUR_OF_DAY) * 60 + this.get(Calendar.MINUTE)

View File

@@ -17,7 +17,7 @@ class LinkUpdateWorker(context: Context, params: WorkerParameters) :
.getGroup() .getGroup()
} }
CurrentLessonViewService.startService(applicationContext) // CurrentLessonViewService.startService(applicationContext)
return Result.success() return Result.success()
} }

View File

@@ -0,0 +1,29 @@
package ru.n08i40k.polytechnic.next.work
import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import kotlinx.coroutines.runBlocking
import ru.n08i40k.polytechnic.next.PolytechnicApplication
import ru.n08i40k.polytechnic.next.data.MyResult
class ScheduleClvAlarm(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
override fun doWork(): Result {
val application = applicationContext as PolytechnicApplication
val result = runBlocking {
application
.container
.scheduleRepository
.getGroup()
}
if (result is MyResult.Failure)
return Result.failure()
application.scheduleClvService((result as MyResult.Success).data)
return Result.success()
}
}