Возвращено и исправлено отображение текущих пар в уведомлении.

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

Исправлен баг с неправильным отображением "сегодня", "завтра" и т.п.
This commit is contained in:
2025-01-31 22:25:15 +04:00
parent 0ab25e68a3
commit 4593a67c28
18 changed files with 357 additions and 158 deletions

View File

@@ -46,8 +46,8 @@ android {
applicationId = "ru.n08i40k.polytechnic.next" applicationId = "ru.n08i40k.polytechnic.next"
minSdk = 26 minSdk = 26
targetSdk = 35 targetSdk = 35
versionCode = 25 versionCode = 27
versionName = "3.0.1" versionName = "3.1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
@@ -55,6 +55,8 @@ android {
buildTypes { buildTypes {
release { release {
isMinifyEnabled = true isMinifyEnabled = true
isShrinkResources = true
proguardFiles( proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro" "proguard-rules.pro"

View File

@@ -13,11 +13,11 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<application <application
android:name=".Application"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:name=".Application"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
@@ -32,6 +32,15 @@
</intent-filter> </intent-filter>
</service> </service>
<service
android:name=".service.DayViewService"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Service for viewing current lessons in notification." />
</service>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"

View File

@@ -1,6 +1,10 @@
package ru.n08i40k.polytechnic.next package ru.n08i40k.polytechnic.next
import android.Manifest
import android.app.Application import android.app.Application
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.content.ContextCompat
import com.google.android.gms.tasks.OnCompleteListener import com.google.android.gms.tasks.OnCompleteListener
import com.google.android.gms.tasks.Task import com.google.android.gms.tasks.Task
import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.FirebaseMessaging
@@ -36,16 +40,24 @@ class Application : Application() {
get() = applicationContext.packageManager get() = applicationContext.packageManager
.getPackageInfo(this.packageName, 0) .getPackageInfo(this.packageName, 0)
.versionName!! .versionName!!
// val version
// get() = "2.0.2"
private fun scheduleUpdateLinkWorker() { // permissions
val hasNotificationPermission: Boolean
get() =
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
|| ContextCompat.checkSelfPermission(
this,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
private fun setupFirebase() {
fun scheduleUpdateLinkWorker() {
container.remoteConfig.activate().addOnCompleteListener { container.remoteConfig.activate().addOnCompleteListener {
UpdateLinkWorker.schedule(this@Application) UpdateLinkWorker.schedule(this@Application)
} }
} }
private fun fixupToken() { fun fixupToken() {
if (runBlocking { settings.data.map { it.fcmToken }.first() }.isNotEmpty()) if (runBlocking { settings.data.map { it.fcmToken }.first() }.isNotEmpty())
return return
@@ -60,16 +72,11 @@ class Application : Application() {
}) })
} }
override fun onCreate() {
super.onCreate()
VKID.init(this)
val remoteConfig = container.remoteConfig val remoteConfig = container.remoteConfig
remoteConfig.setConfigSettingsAsync(remoteConfigSettings { remoteConfig.setConfigSettingsAsync(
minimumFetchIntervalInSeconds = 3600 remoteConfigSettings { minimumFetchIntervalInSeconds = 3600 }
}) )
remoteConfig.setDefaultsAsync(R.xml.remote_config_defaults) remoteConfig.setDefaultsAsync(R.xml.remote_config_defaults)
remoteConfig.addOnConfigUpdateListener(object : ConfigUpdateListener { remoteConfig.addOnConfigUpdateListener(object : ConfigUpdateListener {
@@ -86,4 +93,12 @@ class Application : Application() {
scheduleUpdateLinkWorker() scheduleUpdateLinkWorker()
fixupToken() fixupToken()
} }
override fun onCreate() {
super.onCreate()
VKID.init(this)
setupFirebase()
}
} }

View File

@@ -3,8 +3,6 @@ package ru.n08i40k.polytechnic.next
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.pm.PackageManager
import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
@@ -19,7 +17,6 @@ import androidx.compose.foundation.layout.safeContent
import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
@@ -28,6 +25,7 @@ import ru.n08i40k.polytechnic.next.app.NotificationChannels
import ru.n08i40k.polytechnic.next.settings.settings import ru.n08i40k.polytechnic.next.settings.settings
import ru.n08i40k.polytechnic.next.ui.PolytechnicApp import ru.n08i40k.polytechnic.next.ui.PolytechnicApp
import ru.n08i40k.polytechnic.next.ui.theme.AppTheme import ru.n08i40k.polytechnic.next.ui.theme.AppTheme
import ru.n08i40k.polytechnic.next.utils.app
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@@ -44,9 +42,6 @@ class MainActivity : ComponentActivity() {
} }
private fun createNotificationChannels() { private fun createNotificationChannels() {
if (!hasNotificationPermission())
return
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
createNotificationChannel( createNotificationChannel(
@@ -63,37 +58,33 @@ class MainActivity : ComponentActivity() {
NotificationChannels.APP_UPDATE NotificationChannels.APP_UPDATE
) )
// createNotificationChannel( createNotificationChannel(
// notificationManager, notificationManager,
// getString(R.string.lesson_view_channel_name), getString(R.string.day_view_channel_name),
// getString(R.string.lesson_view_channel_description), getString(R.string.day_view_channel_description),
// NotificationChannels.LESSON_VIEW NotificationChannels.DAY_VIEW
// ) )
} }
private val requestPermissionLauncher = private val notificationRPL =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { registerForActivityResult(ActivityResultContracts.RequestPermission()) {
if (it) createNotificationChannels() if (it) createNotificationChannels()
} }
private fun askNotificationPermission() { private fun setupNotifications() {
if (hasNotificationPermission()) if (app.hasNotificationPermission) {
createNotificationChannels()
return return
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
} }
private fun hasNotificationPermission(): Boolean = notificationRPL.launch(Manifest.permission.POST_NOTIFICATIONS)
(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU }
|| ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
== PackageManager.PERMISSION_GRANTED)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
askNotificationPermission() setupNotifications()
createNotificationChannels()
lifecycleScope.launch { lifecycleScope.launch {
settings.data.first() settings.data.first()

View File

@@ -3,4 +3,5 @@ package ru.n08i40k.polytechnic.next.app
object NotificationChannels { object NotificationChannels {
const val SCHEDULE_UPDATE = "schedule-update" const val SCHEDULE_UPDATE = "schedule-update"
const val APP_UPDATE = "app-update" const val APP_UPDATE = "app-update"
const val DAY_VIEW = "day-view"
} }

View File

@@ -31,12 +31,4 @@ data class GroupOrTeacher(
get() { get() {
return days.getOrNull(currentIdx ?: return null) return days.getOrNull(currentIdx ?: return null)
} }
// TODO: вернуть
@Suppress("unused")
val currentKV: Pair<Int, Day>?
get() {
val idx = currentIdx ?: return null
return Pair(idx, days[idx])
}
} }

View File

@@ -5,7 +5,6 @@ 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.R import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.utils.dayMinutes
import ru.n08i40k.polytechnic.next.utils.limit import ru.n08i40k.polytechnic.next.utils.limit
@Parcelize @Parcelize
@@ -18,25 +17,22 @@ data class Lesson(
val group: String? = null, val group: String? = null,
val subGroups: List<SubGroup> val subGroups: List<SubGroup>
) : Parcelable { ) : Parcelable {
// TODO: вернуть fun getShortName(context: Context): String {
@Suppress("unused")
val duration get() = time.end.dayMinutes - time.start.dayMinutes
@Suppress("unused")
fun getNameAndCabinetsShort(context: Context): String {
val name = val name =
if (type == LessonType.BREAK) context.getString( if (type == LessonType.BREAK)
context.getString(
if (group == null) if (group == null)
R.string.student_break R.string.student_break
else else
R.string.teacher_break R.string.teacher_break
) )
else this.name else
this.name
val shortName = name!! limit 15 val shortName = name!! limit 15
val cabinetList = subGroups.map { it.cabinet } val cabinetList = subGroups.map { it.cabinet }
if (cabinetList.isEmpty()) if (cabinetList.isEmpty() || (cabinetList.size == 1 && cabinetList[0].isEmpty()))
return shortName return shortName
if (cabinetList.size == 1 && cabinetList[0] == "с") if (cabinetList.size == 1 && cabinetList[0] == "с")

View File

@@ -3,7 +3,6 @@ package ru.n08i40k.polytechnic.next.network.request
import com.android.volley.AuthFailureError import com.android.volley.AuthFailureError
import com.android.volley.Response import com.android.volley.Response
import com.android.volley.VolleyError import com.android.volley.VolleyError
import jakarta.inject.Singleton
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@@ -22,30 +21,12 @@ open class AuthorizedRequest(
method, method,
url, url,
listener, listener,
@Singleton
object : Response.ErrorListener { object : Response.ErrorListener {
override fun onErrorResponse(error: VolleyError?) { override fun onErrorResponse(error: VolleyError?) {
val context = appContainer.context if (!canBeUnauthorized && error is AuthFailureError)
if (!canBeUnauthorized && error is AuthFailureError) {
runBlocking {
context.settings.updateData { currentSettings ->
currentSettings
.toBuilder()
.clear()
.build()
}
}
// TODO: если не авторизован
// if (context.profileViewModel != null)
// context.profileViewModel!!.onUnauthorized()
}
runBlocking { appContainer.profileRepository.signOut() } runBlocking { appContainer.profileRepository.signOut() }
errorListener?.onErrorResponse(error) errorListener?.onErrorResponse(error)
} }
}) { }) {
@@ -58,15 +39,11 @@ open class AuthorizedRequest(
.first() .first()
} }
// TODO: если не авторизован
// if (accessToken.isEmpty() && context.profileViewModel != null)
// context.profileViewModel!!.onUnauthorized()
val headers = super.getHeaders() val headers = super.getHeaders()
headers["Authorization"] = "Bearer $accessToken" headers["Authorization"] = "Bearer $accessToken"
return headers return headers
} }
val appContext get() = appContainer.context protected val appContext get() = appContainer.context
} }

View File

@@ -87,7 +87,6 @@ open class CachedRequest(
} }
override fun send(context: Context) { override fun send(context: Context) {
// TODO: network cache
val logger = Logger.getLogger("CachedRequest") val logger = Logger.getLogger("CachedRequest")
val cache = appContainer.networkCacheRepository val cache = appContainer.networkCacheRepository

View File

@@ -0,0 +1,223 @@
package ru.n08i40k.polytechnic.next.service
import android.app.Notification
import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat.startForegroundService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import kotlinx.datetime.LocalDateTime
import ru.n08i40k.polytechnic.next.Application
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.app.NotificationChannels
import ru.n08i40k.polytechnic.next.app.appContainer
import ru.n08i40k.polytechnic.next.model.Day
import ru.n08i40k.polytechnic.next.model.UserRole
import ru.n08i40k.polytechnic.next.utils.MyResult
import ru.n08i40k.polytechnic.next.utils.dayMinutes
import ru.n08i40k.polytechnic.next.utils.fmtAsClock
import ru.n08i40k.polytechnic.next.utils.now
import java.util.logging.Logger
class DayViewService : Service() {
private val coroutineScope = CoroutineScope(Job() + Dispatchers.Main)
companion object {
private val logger = Logger.getLogger("DayView")
private const val NOTIFICATION_MAIN_ID = 3141_00
private const val NOTIFICATION_END_ID = 3141_59
private const val UPDATE_INTERVAL_MILLIS = 1_000L
fun start(app: Application) {
if (!app.hasNotificationPermission) {
logger.warning("Cannot start service, because app don't have notifications permission!")
return
}
val intent = Intent(app, DayViewService::class.java)
app.stopService(intent)
startForegroundService(app, intent)
}
}
private lateinit var day: Day
private val handler = Handler(Looper.getMainLooper())
private fun onLessonsEnd() {
notificationManager.notify(
NOTIFICATION_END_ID,
NotificationCompat
.Builder(this, NotificationChannels.DAY_VIEW)
.setSmallIcon(R.drawable.schedule)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentTitle(getString(R.string.day_view_end_title))
.setContentText(getString(R.string.day_view_end_description))
.build()
)
handler.removeCallbacks(runnable)
stopSelf()
}
private val runnable = object : Runnable {
override fun run() {
val (currentIndex, current) = day.currentKV ?: (null to null)
val (nextIndex, distanceToNext) = day.distanceToNext(currentIndex) ?: (null to null)
if (current == null && nextIndex == null) {
onLessonsEnd()
return
}
handler.postDelayed(this, UPDATE_INTERVAL_MILLIS)
val context = this@DayViewService
val next = nextIndex?.let { day.lessons[nextIndex] }
val nextName = next?.getShortName(context) ?: getString(R.string.day_view_lessons_end)
val nowMinutes = LocalDateTime.now().dayMinutes
val eventMinutes = (current?.time?.end ?: next!!.time.start).dayMinutes
// Если следующая пара - первая.
// Пока что вариантов, когда текущая пара null, а следующая нет я не видел.
if (current == null) {
notificationManager.notify(
NOTIFICATION_MAIN_ID,
createNotification(
getString(
R.string.day_view_wait_for_begin_title,
(eventMinutes - nowMinutes) / 60,
(eventMinutes - nowMinutes) % 60
),
getString(
R.string.day_view_going_description,
getString(R.string.day_view_not_started),
eventMinutes.fmtAsClock(),
nextName,
),
)
)
return
}
notificationManager.notify(
NOTIFICATION_MAIN_ID,
createNotification(
getString(
R.string.day_view_going_title,
(eventMinutes - nowMinutes) / 60,
(eventMinutes - nowMinutes) % 60
),
getString(
R.string.day_view_going_description,
current.getShortName(context),
eventMinutes.fmtAsClock(),
nextName,
),
)
)
}
}
private lateinit var notificationManager: NotificationManager
private fun createNotification(
title: String,
description: String
): Notification {
return NotificationCompat
.Builder(this, NotificationChannels.DAY_VIEW)
.setSmallIcon(R.drawable.schedule)
.setContentTitle(title)
.setContentText(description)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setOngoing(true)
.setSilent(true)
.build()
}
private suspend fun loadSchedule(): Boolean {
val profileRepository = appContainer.profileRepository
val scheduleRepository = appContainer.scheduleRepository
val profile = when (val result = profileRepository.getProfile()) {
is MyResult.Success -> result.data
else -> {
logger.warning("Cannot start service, because get profile request failed!")
return false
}
}
val schedule = when (
val result = when (profile.role) {
UserRole.TEACHER -> {
// TODO: implement schedule breaks for teachers on server-side.
return false
}
else -> scheduleRepository.getGroup()
}
) {
is MyResult.Success -> result.data
else -> {
logger.warning("Cannot start service, because get schedule request failed!")
return false
}
}
val currentDay = schedule.current
if (currentDay == null || currentDay.lessons.isEmpty()) {
logger.warning("Cannot start service, because no lessons today!")
return false
}
if (Clock.System.now() > currentDay.lessons.first().time.start && currentDay.current == null) {
logger.warning("Cannot start service, because it started after lessons end!")
return false
}
this.day = currentDay
return true
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val notification = createNotification(
getString(R.string.day_view_title),
getString(R.string.day_view_description)
)
startForeground(NOTIFICATION_MAIN_ID, notification)
coroutineScope
.launch {
if (!loadSchedule()) {
stopSelf()
return@launch
}
this@DayViewService.handler.removeCallbacks(runnable)
this@DayViewService.runnable.run()
}
return START_STICKY
}
override fun onBind(p0: Intent?): IBinder? {
return null
}
}

View File

@@ -11,19 +11,21 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage import com.google.firebase.messaging.RemoteMessage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import ru.n08i40k.polytechnic.next.R import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.app.NotificationChannels import ru.n08i40k.polytechnic.next.app.NotificationChannels
import ru.n08i40k.polytechnic.next.utils.app
import ru.n08i40k.polytechnic.next.worker.UpdateFCMTokenWorker import ru.n08i40k.polytechnic.next.worker.UpdateFCMTokenWorker
private interface MessageHandler {
fun execute(service: FCMService)
}
private data class ScheduleUpdateData( private data class ScheduleUpdateData(
val type: String, val type: String,
val replaced: Boolean, val replaced: Boolean,
val etag: String val etag: String
) { ) : MessageHandler {
constructor(message: RemoteMessage) : this( constructor(message: RemoteMessage) : this(
type = message.data["type"] type = message.data["type"]
?: throw IllegalArgumentException("Type is missing in RemoteMessage"), ?: throw IllegalArgumentException("Type is missing in RemoteMessage"),
@@ -33,7 +35,7 @@ private data class ScheduleUpdateData(
?: throw IllegalArgumentException("Etag is missing in RemoteMessage") ?: throw IllegalArgumentException("Etag is missing in RemoteMessage")
) )
fun handleMessage(service: FCMService) { override fun execute(service: FCMService) {
service.sendNotification( service.sendNotification(
NotificationChannels.SCHEDULE_UPDATE, NotificationChannels.SCHEDULE_UPDATE,
R.drawable.schedule, R.drawable.schedule,
@@ -51,20 +53,14 @@ private data class ScheduleUpdateData(
private data class LessonsStartData( private data class LessonsStartData(
val type: String val type: String
) { ) : MessageHandler {
constructor(message: RemoteMessage) : this( constructor(message: RemoteMessage) : this(
type = message.data["type"] type = message.data["type"]
?: throw IllegalArgumentException("Type is missing in RemoteMessage") ?: throw IllegalArgumentException("Type is missing in RemoteMessage")
) )
// TODO: вернуть override fun execute(service: FCMService) {
@Suppress("unused") DayViewService.start(service.app)
fun handleMessage(service: FCMService) {
// Uncomment and implement if needed
// service.scope.launch {
// CurrentLessonViewService
// .startService(service.applicationContext as PolytechnicApplication)
// }
} }
} }
@@ -72,7 +68,7 @@ private data class AppUpdateData(
val type: String, val type: String,
val version: String, val version: String,
val downloadLink: String val downloadLink: String
) { ) : MessageHandler {
constructor(message: RemoteMessage) : this( constructor(message: RemoteMessage) : this(
type = message.data["type"] type = message.data["type"]
?: throw IllegalArgumentException("Type is missing in RemoteMessage"), ?: throw IllegalArgumentException("Type is missing in RemoteMessage"),
@@ -82,7 +78,7 @@ private data class AppUpdateData(
?: throw IllegalArgumentException("DownloadLink is missing in RemoteMessage") ?: throw IllegalArgumentException("DownloadLink is missing in RemoteMessage")
) )
fun handleMessage(service: FCMService) { override fun execute(service: FCMService) {
service.sendNotification( service.sendNotification(
NotificationChannels.APP_UPDATE, NotificationChannels.APP_UPDATE,
R.drawable.download, R.drawable.download,
@@ -95,10 +91,6 @@ private data class AppUpdateData(
} }
class FCMService : FirebaseMessagingService() { class FCMService : FirebaseMessagingService() {
// TODO: вернуть
@Suppress("unused")
private val scope = CoroutineScope(Job() + Dispatchers.Main)
override fun onNewToken(token: String) { override fun onNewToken(token: String) {
super.onNewToken(token) super.onNewToken(token)
@@ -149,10 +141,11 @@ class FCMService : FirebaseMessagingService() {
val type = message.data["type"] val type = message.data["type"]
when (type) { when (type) {
"schedule-update" -> ScheduleUpdateData(message).handleMessage(this) "schedule-update" -> ScheduleUpdateData(message)
"lessons-start" -> LessonsStartData(message).handleMessage(this) "lessons-start" -> LessonsStartData(message)
"app-update" -> AppUpdateData(message).handleMessage(this) "app-update" -> AppUpdateData(message)
} else -> null
}?.execute(this)
super.onMessageReceived(message) super.onMessageReceived(message)
} }

View File

@@ -54,8 +54,6 @@ class ProfileViewModel @Inject constructor(
refresh() refresh()
} }
// TODO: сделать хук на unauthorized и сделать так что бы waiter удалялся, если сход контекст
fun refresh(): SingleHook<Profile?> { fun refresh(): SingleHook<Profile?> {
val singleHook = SingleHook<Profile?>() val singleHook = SingleHook<Profile?>()

View File

@@ -82,7 +82,7 @@ fun DayCard(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
day: Day = MockScheduleRepository.exampleTeacher.days[0] day: Day = MockScheduleRepository.exampleTeacher.days[0]
) { ) {
val offset = remember { getDayOffset(day) } val offset = remember(day) { getDayOffset(day) }
val defaultCardColors = CardDefaults.cardColors( val defaultCardColors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer, containerColor = MaterialTheme.colorScheme.secondaryContainer,

View File

@@ -89,4 +89,15 @@
<string name="schedule_channel_description">Уведомления об обновлении расписания</string> <string name="schedule_channel_description">Уведомления об обновлении расписания</string>
<string name="app_update_channel_name">Обновления приложения</string> <string name="app_update_channel_name">Обновления приложения</string>
<string name="app_update_channel_description">Уведомления о выходе новой версии этого приложения</string> <string name="app_update_channel_description">Уведомления о выходе новой версии этого приложения</string>
<string name="day_view_title">Загрузка расписания…</string>
<string name="day_view_description">Это уведомление обновится в течение нескольких секунд!</string>
<string name="day_view_not_started">Пары ещё не начались</string>
<string name="day_view_going_title">До конца %1$d ч. %2$d мин.</string>
<string name="day_view_wait_for_begin_title">До начала пар %1$d ч. %2$d мин.</string>
<string name="day_view_lessons_end">Конец пар</string>
<string name="day_view_going_description">%1$s\n| Далее в %2$s - %3$s</string>
<string name="day_view_end_title">Пары закончились!</string>
<string name="day_view_end_description">Ура, можно идти домой! Наверное :(</string>
<string name="day_view_channel_name">Текущая пара</string>
<string name="day_view_channel_description">Отображает текущую пару или перемену в уведомлении</string>
</resources> </resources>

View File

@@ -89,4 +89,15 @@
<string name="schedule_channel_description">Inform when schedule has been updated</string> <string name="schedule_channel_description">Inform when schedule has been updated</string>
<string name="app_update_channel_name">Application update</string> <string name="app_update_channel_name">Application update</string>
<string name="app_update_channel_description">Inform about a new version of this app has been released</string> <string name="app_update_channel_description">Inform about a new version of this app has been released</string>
<string name="day_view_title">Loading schedule…</string>
<string name="day_view_description">This notification will be updated in several seconds!</string>
<string name="day_view_wait_for_begin_title">%1$d h. %2$d min. before lessons start</string>
<string name="day_view_lessons_end">Lessons end</string>
<string name="day_view_going_description">%1$s\n| After in %2$s - %3$s</string>
<string name="day_view_not_started">Lessons haven\'t started yet</string>
<string name="day_view_going_title">To end %1$d h. %2$d min.</string>
<string name="day_view_end_title">Lessons finished!</string>
<string name="day_view_end_description">ya ne budu eto perevidit\'</string>
<string name="day_view_channel_name">Current lesson</string>
<string name="day_view_channel_description">View the current lesson and breaks in notification</string>
</resources> </resources>

View File

@@ -2,7 +2,7 @@
<defaults> <defaults>
<entry> <entry>
<key>serverVersion</key> <key>serverVersion</key>
<value>2.2.2</value> <value>3.0.1</value>
</entry> </entry>
<entry> <entry>
<key>linkUpdateDelay</key> <key>linkUpdateDelay</key>
@@ -10,7 +10,7 @@
</entry> </entry>
<entry> <entry>
<key>minVersion</key> <key>minVersion</key>
<value>2.0.1</value> <value>3.0.0</value>
</entry> </entry>
<entry> <entry>
<key>telegramLink</key> <key>telegramLink</key>
@@ -22,10 +22,10 @@
</entry> </entry>
<entry> <entry>
<key>downloadLink</key> <key>downloadLink</key>
<value>https://t.me/polytechnic_next/68</value> <value>https://t.me/polytechnic_next/99</value>
</entry> </entry>
<entry> <entry>
<key>currVersion</key> <key>currVersion</key>
<value>2.2.1</value> <value>3.1.0</value>
</entry> </entry>
</defaults> </defaults>

View File

@@ -1,23 +1,4 @@
# Project-wide Gradle settings. org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true android.nonTransitiveRClass=true

View File

@@ -8,17 +8,17 @@ junitVersion = "1.2.1"
espressoCore = "3.6.1" espressoCore = "3.6.1"
lifecycleRuntimeKtx = "2.8.7" lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.10.0" activityCompose = "1.10.0"
composeBom = "2025.01.00" composeBom = "2025.01.01"
accompanistSwiperefresh = "0.36.0" accompanistSwiperefresh = "0.36.0"
firebaseBom = "33.8.0" firebaseBom = "33.8.0"
hiltAndroid = "2.53.1" hiltAndroid = "2.55"
hiltAndroidCompiler = "2.53.1" hiltAndroidCompiler = "2.55"
hiltNavigationCompose = "1.2.0" hiltNavigationCompose = "1.2.0"
kotlinxSerializationJson = "1.7.3" kotlinxSerializationJson = "1.8.0"
protobufLite = "3.0.1" protobufLite = "3.0.1"
volley = "1.2.1" volley = "1.2.1"
datastore = "1.1.2" datastore = "1.1.2"
navigationCompose = "2.8.5" navigationCompose = "2.8.6"
googleFirebaseCrashlytics = "3.0.2" googleFirebaseCrashlytics = "3.0.2"
workRuntime = "2.10.0" workRuntime = "2.10.0"
vkid = "2.2.2" vkid = "2.2.2"