diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml
index cb7184b..cecc928 100644
--- a/.idea/appInsightsSettings.xml
+++ b/.idea/appInsightsSettings.xml
@@ -15,8 +15,27 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index b268ef3..e21cb14 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,6 +4,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 9231046..6367d4f 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -33,8 +33,8 @@ android {
applicationId = "ru.n08i40k.polytechnic.next"
minSdk = 26
targetSdk = 35
- versionCode = 13
- versionName = "1.7.1"
+ versionCode = 15
+ versionName = "1.8.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 737a600..75b9177 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -12,31 +12,17 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 0000000..9630d62
Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/PolytechnicApplication.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/PolytechnicApplication.kt
index 7fc259b..e255faa 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/PolytechnicApplication.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/PolytechnicApplication.kt
@@ -1,20 +1,13 @@
package ru.n08i40k.polytechnic.next
import android.Manifest
-import android.app.AlarmManager
import android.app.Application
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.content.ContextCompat
import dagger.hilt.android.HiltAndroidApp
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 java.util.Calendar
import javax.inject.Inject
@HiltAndroidApp
@@ -34,84 +27,4 @@ class PolytechnicApplication : Application() {
|| ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
== 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
- )
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/data/schedule/impl/RemoteScheduleRepository.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/data/schedule/impl/RemoteScheduleRepository.kt
index 8e9cbeb..528908c 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/data/schedule/impl/RemoteScheduleRepository.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/data/schedule/impl/RemoteScheduleRepository.kt
@@ -2,30 +2,18 @@ package ru.n08i40k.polytechnic.next.data.schedule.impl
import android.content.Context
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import ru.n08i40k.polytechnic.next.data.MyResult
import ru.n08i40k.polytechnic.next.data.schedule.ScheduleRepository
import ru.n08i40k.polytechnic.next.model.Group
import ru.n08i40k.polytechnic.next.network.request.schedule.ScheduleGet
import ru.n08i40k.polytechnic.next.network.tryFuture
-import ru.n08i40k.polytechnic.next.settings.settingsDataStore
class RemoteScheduleRepository(private val context: Context) : ScheduleRepository {
- override suspend fun getGroup(): MyResult {
- return withContext(Dispatchers.IO) {
- val groupName = runBlocking {
- context.settingsDataStore.data.map { settings -> settings.group }.first()
- }
-
- if (groupName.isEmpty())
- return@withContext MyResult.Failure(IllegalArgumentException("No group name provided!"))
-
+ override suspend fun getGroup(): MyResult =
+ withContext(Dispatchers.IO) {
val response = tryFuture {
ScheduleGet(
- ScheduleGet.RequestDto(groupName),
context,
it,
it
@@ -37,5 +25,4 @@ class RemoteScheduleRepository(private val context: Context) : ScheduleRepositor
is MyResult.Success -> MyResult.Success(response.data.group)
}
}
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/RequestBase.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/RequestBase.kt
index d24a638..c4622d2 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/RequestBase.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/RequestBase.kt
@@ -20,7 +20,6 @@ open class RequestBase(
override fun getHeaders(): MutableMap {
val headers = mutableMapOf()
headers["Content-Type"] = "application/json; charset=utf-8"
- headers["version"] = "1"
return headers
}
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/RequestUtils.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/RequestUtils.kt
index 0fad533..6e7cdc8 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/RequestUtils.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/RequestUtils.kt
@@ -26,4 +26,11 @@ fun tryGet(future: RequestFuture): MyResult {
} catch (exception: TimeoutException) {
MyResult.Failure(exception)
}
+}
+
+fun unwrapException(exception: Exception): Throwable {
+ if (exception is ExecutionException && exception.cause != null)
+ return exception.cause!!
+
+ return exception
}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/CachedRequest.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/CachedRequest.kt
index dfde96c..6e88d97 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/CachedRequest.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/CachedRequest.kt
@@ -26,7 +26,7 @@ open class CachedRequest(
private val listener: Response.Listener,
errorListener: Response.ErrorListener?,
) : AuthorizedRequest(context, method, url, {
- runBlocking {
+ runBlocking(Dispatchers.IO) {
(context as PolytechnicApplication)
.container.networkCacheRepository.put(url, it)
}
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/auth/AuthLogin.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/auth/AuthSignIn.kt
similarity index 80%
rename from app/src/main/java/ru/n08i40k/polytechnic/next/network/request/auth/AuthLogin.kt
rename to app/src/main/java/ru/n08i40k/polytechnic/next/network/request/auth/AuthSignIn.kt
index d6b7fb7..4c03021 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/auth/AuthLogin.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/auth/AuthSignIn.kt
@@ -7,7 +7,7 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import ru.n08i40k.polytechnic.next.network.RequestBase
-class AuthLogin(
+class AuthSignIn(
private val data: RequestDto,
context: Context,
listener: Response.Listener,
@@ -23,9 +23,16 @@ class AuthLogin(
data class RequestDto(val username: String, val password: String)
@Serializable
- data class ResponseDto(val id: String, val accessToken: String)
+ data class ResponseDto(val id: String, val accessToken: String, val group: String)
override fun getBody(): ByteArray {
return Json.encodeToString(data).toByteArray()
}
+
+ override fun getHeaders(): MutableMap {
+ val headers = super.getHeaders()
+ headers["version"] = "2"
+
+ return headers
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/auth/AuthRegister.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/auth/AuthSignUp.kt
similarity index 98%
rename from app/src/main/java/ru/n08i40k/polytechnic/next/network/request/auth/AuthRegister.kt
rename to app/src/main/java/ru/n08i40k/polytechnic/next/network/request/auth/AuthSignUp.kt
index 53db1b0..ee01ee7 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/auth/AuthRegister.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/auth/AuthSignUp.kt
@@ -8,7 +8,7 @@ import kotlinx.serialization.json.Json
import ru.n08i40k.polytechnic.next.model.UserRole
import ru.n08i40k.polytechnic.next.network.RequestBase
-class AuthRegister(
+class AuthSignUp(
private val data: RequestDto,
context: Context,
listener: Response.Listener,
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/schedule/ScheduleGet.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/schedule/ScheduleGet.kt
index e0de8db..c6619d4 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/schedule/ScheduleGet.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/schedule/ScheduleGet.kt
@@ -3,13 +3,11 @@ package ru.n08i40k.polytechnic.next.network.request.schedule
import android.content.Context
import com.android.volley.Response
import kotlinx.serialization.Serializable
-import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import ru.n08i40k.polytechnic.next.model.Group
import ru.n08i40k.polytechnic.next.network.request.CachedRequest
class ScheduleGet(
- private val data: RequestDto,
context: Context,
listener: Response.Listener,
errorListener: Response.ErrorListener? = null
@@ -29,8 +27,4 @@ class ScheduleGet(
val group: Group,
val lastChangedDays: ArrayList,
)
-
- override fun getBody(): ByteArray {
- return Json.encodeToString(data).toByteArray()
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/schedule/ScheduleGetCacheStatus.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/schedule/ScheduleGetCacheStatus.kt
index 97873e5..c06fdee 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/schedule/ScheduleGetCacheStatus.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/schedule/ScheduleGetCacheStatus.kt
@@ -24,4 +24,11 @@ class ScheduleGetCacheStatus(
val lastCacheUpdate: Long,
val lastScheduleUpdate: Long,
)
+
+ override fun getHeaders(): MutableMap {
+ val headers = super.getHeaders()
+ headers["version"] = "1"
+
+ return headers
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/schedule/ScheduleGetGroupNames.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/schedule/ScheduleGetGroupNames.kt
index b7e7922..5cbf15e 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/schedule/ScheduleGetGroupNames.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/schedule/ScheduleGetGroupNames.kt
@@ -4,13 +4,13 @@ import android.content.Context
import com.android.volley.Response
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
-import ru.n08i40k.polytechnic.next.network.request.CachedRequest
+import ru.n08i40k.polytechnic.next.network.RequestBase
class ScheduleGetGroupNames(
context: Context,
listener: Response.Listener,
errorListener: Response.ErrorListener? = null
-) : CachedRequest(
+) : RequestBase(
context,
Method.GET,
"schedule/get-group-names",
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/schedule/ScheduleUpdate.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/schedule/ScheduleUpdate.kt
index ad3f4f5..e6ea89c 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/schedule/ScheduleUpdate.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/request/schedule/ScheduleUpdate.kt
@@ -25,4 +25,11 @@ class ScheduleUpdate(
override fun getBody(): ByteArray {
return Json.encodeToString(data).toByteArray()
}
+
+ override fun getHeaders(): MutableMap {
+ val headers = super.getHeaders()
+ headers["version"] = "1"
+
+ return headers
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/receiver/AlarmReceiver.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/receiver/AlarmReceiver.kt
deleted file mode 100644
index 5f1a394..0000000
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/receiver/AlarmReceiver.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-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()
- .setConstraints(constraints)
- .build()
-
- WorkManager
- .getInstance(context)
- .enqueue(rescheduleRequest)
-
- CurrentLessonViewService.startService(context.applicationContext)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/receiver/BootCompletedBroadcastReceiver.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/receiver/BootCompletedBroadcastReceiver.kt
deleted file mode 100644
index 823c6c4..0000000
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/receiver/BootCompletedBroadcastReceiver.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-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()
- .setConstraints(constraints)
- .build()
-
- WorkManager
- .getInstance(context)
- .enqueue(request)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/service/CurrentLessonViewService.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/service/CurrentLessonViewService.kt
index 2191488..56aa1f2 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/service/CurrentLessonViewService.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/service/CurrentLessonViewService.kt
@@ -3,25 +3,24 @@ package ru.n08i40k.polytechnic.next.service
import android.app.Notification
import android.app.NotificationManager
import android.app.Service
-import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import androidx.core.app.NotificationCompat
-import androidx.work.OneTimeWorkRequestBuilder
-import androidx.work.WorkManager
+import androidx.core.content.ContextCompat.startForegroundService
import ru.n08i40k.polytechnic.next.NotificationChannels
import ru.n08i40k.polytechnic.next.PolytechnicApplication
import ru.n08i40k.polytechnic.next.R
+import ru.n08i40k.polytechnic.next.data.MyResult
import ru.n08i40k.polytechnic.next.model.Day
import ru.n08i40k.polytechnic.next.model.Group
import ru.n08i40k.polytechnic.next.model.Lesson
import ru.n08i40k.polytechnic.next.utils.fmtAsClock
import ru.n08i40k.polytechnic.next.utils.getDayMinutes
-import ru.n08i40k.polytechnic.next.work.StartClvService
import java.util.Calendar
+import java.util.logging.Logger
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
class CurrentLessonViewService : Service() {
@@ -30,19 +29,31 @@ class CurrentLessonViewService : Service() {
private const val NOTIFICATION_END_ID = NOTIFICATION_STATUS_ID + 1
private const val UPDATE_INTERVAL = 60_000L
- fun startService(appContext: Context) {
- if (!(appContext as PolytechnicApplication).hasNotificationPermission())
+ suspend fun startService(application: PolytechnicApplication) {
+ if (!application.hasNotificationPermission())
return
- if (Calendar.getInstance()
- .get(Calendar.HOUR_OF_DAY) * 60 + Calendar.getInstance()
- .get(Calendar.MINUTE) < 420)
+ val schedule =
+ application
+ .container
+ .scheduleRepository
+ .getGroup()
+
+ if (schedule is MyResult.Failure)
return
- val request = OneTimeWorkRequestBuilder()
- .build()
+ val intent = Intent(application, CurrentLessonViewService::class.java)
+ .apply {
+ putExtra("group", (schedule as MyResult.Success).data)
+ }
- WorkManager.getInstance(appContext).enqueue(request)
+ application.stopService(
+ Intent(
+ application,
+ CurrentLessonViewService::class.java
+ )
+ )
+ startForegroundService(application, intent)
}
}
@@ -51,7 +62,10 @@ class CurrentLessonViewService : Service() {
private val handler = Handler(Looper.getMainLooper())
private val updateRunnable = object : Runnable {
override fun run() {
+ val logger = Logger.getLogger("CLV.updateRunnable")
+
if (day == null || day!!.nonNullIndices.isEmpty()) {
+ logger.warning("Stopping, because day is null or empty!")
stopSelf()
return
}
@@ -74,7 +88,7 @@ class CurrentLessonViewService : Service() {
if (currentLesson == null && nextLesson == null) {
val notification = NotificationCompat
.Builder(applicationContext, NotificationChannels.LESSON_VIEW)
- .setSmallIcon(R.drawable.logo)
+ .setSmallIcon(R.drawable.schedule)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentTitle(getString(R.string.lessons_end_notification_title))
.setContentText(getString(R.string.lessons_end_notification_description))
@@ -146,7 +160,7 @@ class CurrentLessonViewService : Service() {
): Notification {
return NotificationCompat
.Builder(applicationContext, NotificationChannels.LESSON_VIEW)
- .setSmallIcon(R.drawable.logo)
+ .setSmallIcon(R.drawable.schedule)
.setContentTitle(title ?: getString(R.string.lesson_notification_title))
.setContentText(description ?: getString(R.string.lesson_notification_description))
.setPriority(NotificationCompat.PRIORITY_HIGH)
@@ -159,26 +173,29 @@ class CurrentLessonViewService : Service() {
return getSystemService(NOTIFICATION_SERVICE) as NotificationManager
}
- private fun updateSchedule(group: Group?): Boolean {
+ private fun updateSchedule(group: Group?) {
+ val logger = Logger.getLogger("CLV")
+
if (group == null) {
+ logger.warning("Stopping, because group is null")
stopSelf()
- return false
+ return
}
val currentDay = group.current
if (currentDay == null || currentDay.nonNullIndices.isEmpty()) {
+ logger.warning("Stopping, because current day is null or empty")
stopSelf()
- return false
+ return
}
- if (this.day == null) {
- val nowMinutes = Calendar.getInstance().getDayMinutes()
-
- if (currentDay.first!!.time.start - nowMinutes > 30
- || currentDay.last!!.time.end < nowMinutes) {
- stopSelf()
- return false
- }
+ val nowMinutes = Calendar.getInstance().getDayMinutes()
+ if (nowMinutes < ((5 * 60) + 30)
+ || currentDay.last!!.time.end < nowMinutes
+ ) {
+ logger.warning("Stopping, because service started outside of acceptable time range!")
+ stopSelf()
+ return
}
this.day = currentDay
@@ -186,7 +203,7 @@ class CurrentLessonViewService : Service() {
this.handler.removeCallbacks(updateRunnable)
updateRunnable.run()
- return true
+ logger.info("Running...")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@@ -201,16 +218,14 @@ class CurrentLessonViewService : Service() {
val notification = createNotification()
startForeground(NOTIFICATION_STATUS_ID, notification)
- if (!updateSchedule(
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- intent.getParcelableExtra("group", Group::class.java)
- } else {
- @Suppress("DEPRECATION")
- intent.getParcelableExtra("group")
- }
- )
+ updateSchedule(
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ intent.getParcelableExtra("group", Group::class.java)
+ } else {
+ @Suppress("DEPRECATION")
+ intent.getParcelableExtra("group")
+ }
)
- updateRunnable.run()
return START_STICKY
}
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/service/MyFirebaseMessagingService.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/service/MyFirebaseMessagingService.kt
index 70b5202..1e5bc52 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/service/MyFirebaseMessagingService.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/service/MyFirebaseMessagingService.kt
@@ -17,13 +17,19 @@ import androidx.work.WorkManager
import androidx.work.workDataOf
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
import ru.n08i40k.polytechnic.next.NotificationChannels
+import ru.n08i40k.polytechnic.next.PolytechnicApplication
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.work.FcmSetTokenWorker
-import ru.n08i40k.polytechnic.next.work.ScheduleClvAlarm
import java.time.Duration
class MyFirebaseMessagingService : FirebaseMessagingService() {
+ val scope = CoroutineScope(Job() + Dispatchers.Main)
+
override fun onNewToken(token: String) {
super.onNewToken(token)
@@ -79,7 +85,6 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
}
notify(id.hashCode(), notification)
- CurrentLessonViewService.startService(applicationContext)
}
}
@@ -90,7 +95,7 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
"schedule-update" -> {
sendNotification(
NotificationChannels.SCHEDULE_UPDATE,
- R.drawable.logo,
+ R.drawable.schedule,
getString(R.string.schedule_update_title),
getString(
if (message.data["replaced"] == "true")
@@ -101,24 +106,19 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
NotificationCompat.PRIORITY_DEFAULT,
message.data["etag"]
)
+ }
- val constraints = Constraints.Builder()
- .setRequiredNetworkType(NetworkType.CONNECTED)
- .build()
-
- val request = OneTimeWorkRequestBuilder()
- .setConstraints(constraints)
- .build()
-
- WorkManager
- .getInstance(applicationContext)
- .enqueue(request)
+ "lessons-start" -> {
+ scope.launch {
+ CurrentLessonViewService
+ .startService(applicationContext as PolytechnicApplication)
+ }
}
"app-update" -> {
sendNotification(
NotificationChannels.APP_UPDATE,
- R.drawable.logo,
+ R.drawable.download,
getString(R.string.app_update_title, message.data["version"]),
getString(R.string.app_update_description),
NotificationCompat.PRIORITY_DEFAULT,
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/MainActivity.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/MainActivity.kt
index 7a24bde..8ceb827 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/MainActivity.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/MainActivity.kt
@@ -36,7 +36,6 @@ import kotlinx.coroutines.launch
import ru.n08i40k.polytechnic.next.NotificationChannels
import ru.n08i40k.polytechnic.next.PolytechnicApplication
import ru.n08i40k.polytechnic.next.R
-import ru.n08i40k.polytechnic.next.data.MyResult
import ru.n08i40k.polytechnic.next.settings.settingsDataStore
import ru.n08i40k.polytechnic.next.work.FcmUpdateCallbackWorker
import ru.n08i40k.polytechnic.next.work.LinkUpdateWorker
@@ -101,21 +100,6 @@ class MainActivity : ComponentActivity() {
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
- private fun scheduleAlarm() {
- lifecycleScope.launch {
- val schedule = (applicationContext as PolytechnicApplication)
- .container
- .scheduleRepository
- .getGroup()
-
- if (schedule is MyResult.Failure)
- return@launch
-
- (applicationContext as PolytechnicApplication)
- .scheduleClvService((schedule as MyResult.Success).data)
- }
- }
-
fun scheduleLinkUpdate(intervalInMinutes: Long) {
val tag = "schedule-update"
@@ -182,7 +166,6 @@ class MainActivity : ComponentActivity() {
setupFirebaseConfig()
handleUpdate()
- scheduleAlarm()
setContent {
Box(Modifier.windowInsetsPadding(WindowInsets.safeContent.only(WindowInsetsSides.Top))) {
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/auth/AuthScreen.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/auth/AuthScreen.kt
index eb93c57..727977f 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/auth/AuthScreen.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/auth/AuthScreen.kt
@@ -1,504 +1,113 @@
package ru.n08i40k.polytechnic.next.ui.auth
-import android.content.Context
-import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.LinearOutSlowInEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.slideIn
+import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.material3.Button
import androidx.compose.material3.Card
-import androidx.compose.material3.DropdownMenuItem
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.ExposedDropdownMenuBox
-import androidx.compose.material3.ExposedDropdownMenuDefaults
-import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.MenuAnchorType
-import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextButton
-import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalFocusManager
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
-import com.android.volley.AuthFailureError
-import com.android.volley.ClientError
-import com.android.volley.TimeoutError
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
-import ru.n08i40k.polytechnic.next.R
-import ru.n08i40k.polytechnic.next.model.UserRole
-import ru.n08i40k.polytechnic.next.model.UserRole.Companion.AcceptableUserRoles
-import ru.n08i40k.polytechnic.next.network.request.auth.AuthLogin
-import ru.n08i40k.polytechnic.next.network.request.auth.AuthRegister
-import ru.n08i40k.polytechnic.next.network.request.profile.ProfileMe
import ru.n08i40k.polytechnic.next.settings.settingsDataStore
-@Preview(showBackground = true)
-@Composable
-private fun LoginForm(
- mutableVisible: MutableState = mutableStateOf(true),
- navController: NavHostController = rememberNavController(),
- scope: CoroutineScope = rememberCoroutineScope(),
- snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
-) {
- val context = LocalContext.current
- val focusManager = LocalFocusManager.current
-
- val mutableIsLoading = remember { mutableStateOf(false) }
-
- var username by remember { mutableStateOf("") }
- var password by remember { mutableStateOf("") }
- var visible by mutableVisible
-
- Text(
- text = stringResource(R.string.login_title),
- modifier = Modifier.padding(10.dp),
- style = MaterialTheme.typography.displaySmall,
- fontWeight = FontWeight.ExtraBold
- )
-
- Spacer(modifier = Modifier.size(10.dp))
-
- val mutableUsernameError = remember { mutableStateOf(false) }
- val mutablePasswordError = remember { mutableStateOf(false) }
-
- var usernameError by mutableUsernameError
- var passwordError by mutablePasswordError
-
- OutlinedTextField(
- value = username,
- singleLine = true,
- onValueChange = {
- username = it
- usernameError = false
- },
- label = { Text(stringResource(R.string.username)) },
- isError = usernameError
- )
-
- OutlinedTextField(
- value = password,
- singleLine = true,
- visualTransformation = PasswordVisualTransformation(),
- onValueChange = {
- passwordError = false
- password = it
- },
- label = { Text(stringResource(R.string.password)) },
- isError = passwordError
- )
-
- TextButton(onClick = { visible = false }) {
- Text(text = stringResource(R.string.not_registered))
- }
-
- Button(
- enabled = !mutableIsLoading.value,
- onClick = {
- if (username.length < 4) usernameError = true
- if (password.isEmpty()) passwordError = true
-
- if (usernameError || passwordError) return@Button
-
- tryLogin(
- username,
- password,
- mutableUsernameError,
- mutablePasswordError,
- mutableIsLoading,
- context,
- snackbarHostState,
- scope,
- navController
- )
-
- focusManager.clearFocus()
- }
- ) {
- Text(
- text = stringResource(R.string.login),
- style = MaterialTheme.typography.bodyLarge
- )
- }
-}
-
-@Preview(showBackground = true)
-@Composable
-private fun RegisterForm(
- mutableVisible: MutableState = mutableStateOf(true),
- navController: NavHostController = rememberNavController(),
- scope: CoroutineScope = rememberCoroutineScope(),
- snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
-) {
- val context = LocalContext.current
- val focusManager = LocalFocusManager.current
-
- val mutableIsLoading = remember { mutableStateOf(false) }
-
- var username by remember { mutableStateOf("") }
- var password by remember { mutableStateOf("") }
- var group by remember { mutableStateOf("") }
- val mutableRole = remember { mutableStateOf(UserRole.STUDENT) }
-
- var visible by mutableVisible
-
- Text(
- text = stringResource(R.string.register_title),
- modifier = Modifier.padding(10.dp),
- style = MaterialTheme.typography.displaySmall,
- fontWeight = FontWeight.ExtraBold
- )
-
- Spacer(modifier = Modifier.size(10.dp))
-
- val mutableUsernameError = remember { mutableStateOf(false) }
- var usernameError by mutableUsernameError
-
- var passwordError by remember { mutableStateOf(false) }
-
- val mutableGroupError = remember { mutableStateOf(false) }
- var groupError by mutableGroupError
-
- OutlinedTextField(
- value = username,
- singleLine = true,
- onValueChange = {
- username = it
- usernameError = false
- },
- label = { Text(stringResource(R.string.username)) },
- isError = usernameError
- )
-
- OutlinedTextField(
- value = password,
- singleLine = true,
- visualTransformation = PasswordVisualTransformation(),
- onValueChange = {
- passwordError = false
- password = it
- },
- label = { Text(stringResource(R.string.password)) },
- isError = passwordError
- )
-
- OutlinedTextField(
- value = group,
- singleLine = true,
- onValueChange = {
- groupError = false
- group = it
- },
- label = { Text(stringResource(R.string.group)) },
- isError = groupError
- )
-
- RoleSelector(mutableRole)
-
- TextButton(onClick = { visible = false }) {
- Text(text = stringResource(R.string.already_registered))
- }
-
- Button(
- enabled = !mutableIsLoading.value,
- onClick = {
- if (username.length < 4) usernameError = true
- if (password.isEmpty()) passwordError = true
- if (group.isEmpty()) groupError = true
-
- if (usernameError || passwordError || groupError) return@Button
-
- tryRegister(
- username,
- password,
- group,
- mutableRole.value,
- mutableUsernameError,
- mutableGroupError,
- mutableIsLoading,
- context,
- snackbarHostState,
- scope,
- navController
- )
-
- focusManager.clearFocus()
- }
- ) {
- Text(
- text = stringResource(R.string.register),
- style = MaterialTheme.typography.bodyLarge
- )
- }
-}
@Preview(showBackground = true)
@Composable
fun AuthForm(
- mutableIsLogin: MutableState = mutableStateOf(true),
- navController: NavHostController = rememberNavController(),
- scope: CoroutineScope = rememberCoroutineScope(),
- snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
+ appNavController: NavHostController = rememberNavController(),
+ onPendingSnackbar: (String) -> Unit = {},
) {
- var isLogin by mutableIsLogin
+ val navController = rememberNavController()
- val mutableVisible = remember { mutableStateOf(true) }
- var visible by mutableVisible
+ val modifier = Modifier.fillMaxSize()
- val animatedAlpha by animateFloatAsState(
- targetValue = if (visible) 1.0f else 0f, label = "alpha"
- )
-
- Column(
- modifier = Modifier
- .padding(10.dp)
- .graphicsLayer {
- alpha = animatedAlpha
- if (alpha == 0F) {
- if (!visible) isLogin = isLogin.not()
- visible = true
- }
- },
- horizontalAlignment = Alignment.CenterHorizontally,
- ) {
- if (isLogin)
- LoginForm(mutableVisible, navController, scope, snackbarHostState)
- else
- RegisterForm(mutableVisible, navController, scope, snackbarHostState)
- }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Preview(showBackground = true)
-@Composable
-fun RoleSelector(mutableRole: MutableState = mutableStateOf(UserRole.STUDENT)) {
- var expanded by remember { mutableStateOf(false) }
-
- var role by mutableRole
-
- Box(
- modifier = Modifier.wrapContentSize()
- ) {
- ExposedDropdownMenuBox(
- expanded = expanded,
- onExpandedChange = { expanded = !expanded }
- ) {
- TextField(
- label = { Text(stringResource(R.string.role)) },
- modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryEditable),
- value = stringResource(role.stringId),
- leadingIcon = {
- Icon(
- imageVector = role.icon,
- contentDescription = "role icon"
- )
- },
- onValueChange = {},
- readOnly = true,
- trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }
- )
-
- ExposedDropdownMenu(
- expanded = expanded,
- onDismissRequest = { expanded = false }) {
- AcceptableUserRoles.forEach {
- DropdownMenuItem(
- text = { Text(stringResource(it.stringId)) },
- onClick = {
- role = it
- expanded = false
- }
- )
- }
- }
- }
- }
-}
-
-fun tryLogin(
- // data
- username: String,
- password: String,
-
- // errors
- mutableUsernameError: MutableState,
- mutablePasswordError: MutableState,
-
- // additional
- mutableIsLoading: MutableState,
- context: Context,
- snackbarHostState: SnackbarHostState,
- scope: CoroutineScope,
- navController: NavHostController
-) {
- var isLoading by mutableIsLoading
-
- isLoading = true
-
- AuthLogin(AuthLogin.RequestDto(username, password), context, {
- runBlocking {
- context.settingsDataStore.updateData { currentSettings ->
- currentSettings
- .toBuilder()
- .setUserId(it.id)
- .setAccessToken(it.accessToken)
- .build()
- }
- }
-
- ProfileMe(context, {
- scope.launch { snackbarHostState.showSnackbar("Cool!") }
-
- runBlocking {
- context.settingsDataStore.updateData { currentSettings ->
- currentSettings
- .toBuilder()
- .setGroup(it.group)
- .build()
- }
- }
-
- navController.navigate("main")
- }, null).send()
- }, {
- isLoading = false
-
- if (it is TimeoutError) {
- scope.launch { snackbarHostState.showSnackbar("Request timed out!") }
- }
-
- if (it is ClientError && it.networkResponse.statusCode == 400) scope.launch {
- snackbarHostState.showSnackbar("Request schema not identical!")
- }
-
- if (it is AuthFailureError) scope.launch {
- mutableUsernameError.value = true
- mutablePasswordError.value = true
- snackbarHostState.showSnackbar("Invalid credentials!")
- }
-
-
- it.printStackTrace()
- }).send()
-}
-
-fun tryRegister(
- // data
- username: String,
- password: String,
- group: String,
- role: UserRole,
-
- // errors
- mutableUsernameError: MutableState,
- mutableGroupError: MutableState,
-
- // additional
- mutableIsLoading: MutableState,
- context: Context,
- snackbarHostState: SnackbarHostState,
- scope: CoroutineScope,
- navController: NavHostController
-) {
- var isLoading by mutableIsLoading
-
- isLoading = true
-
- AuthRegister(
- AuthRegister.RequestDto(
- username,
- password,
- group,
- role
- ), context, {
- scope.launch { snackbarHostState.showSnackbar("Cool!") }
-
- runBlocking {
- context.settingsDataStore.updateData { currentSettings ->
- currentSettings.toBuilder().setUserId(it.id)
- .setAccessToken(it.accessToken).build()
- }
- }
-
- runBlocking {
- context.settingsDataStore.updateData { currentSettings ->
- currentSettings
- .toBuilder()
- .setGroup(group)
- .build()
- }
- }
-
- navController.navigate("main")
- }, {
- isLoading = false
-
- if (it is TimeoutError) {
- scope.launch { snackbarHostState.showSnackbar("Request timed out!") }
- }
-
- if (it is ClientError) scope.launch {
- val statusCode = it.networkResponse.statusCode
-
- when (statusCode) {
- 400 -> snackbarHostState.showSnackbar("Request schema not identical!")
- 409 -> {
- mutableUsernameError.value = true
- snackbarHostState.showSnackbar("User already exists!")
- }
-
- 404 -> {
- mutableGroupError.value = true
- snackbarHostState.showSnackbar("Group doesn't exists!")
- }
- }
- }
-
- if (it is AuthFailureError) scope.launch {
- snackbarHostState.showSnackbar(
- "Invalid credentials!"
+ NavHost(
+ modifier = Modifier.fillMaxSize(),
+ navController = navController,
+ startDestination = "sign-in",
+ enterTransition = {
+ slideIn(
+ animationSpec = tween(
+ 400,
+ delayMillis = 250,
+ easing = LinearOutSlowInEasing
)
+ ) { fullSize -> IntOffset(0, fullSize.height / 16) } + fadeIn(
+ animationSpec = tween(
+ 400,
+ delayMillis = 250,
+ easing = LinearOutSlowInEasing
+ )
+ )
+ },
+ exitTransition = {
+ fadeOut(
+ animationSpec = tween(
+ 250,
+ easing = FastOutSlowInEasing
+ )
+ )
+ },
+ ) {
+ composable("sign-in") {
+ Row(
+ modifier,
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Card(border = BorderStroke(Dp.Hairline, MaterialTheme.colorScheme.inverseSurface)) {
+ LoginForm(appNavController, navController, onPendingSnackbar)
+ }
}
+ }
+ composable("sign-up") {
+ Row(
+ modifier,
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Card(border = BorderStroke(Dp.Hairline, MaterialTheme.colorScheme.inverseSurface)) {
+ RegisterForm(appNavController, navController, onPendingSnackbar)
+ }
+ }
+ }
+ }
-
- it.printStackTrace()
- }).send()
}
+
@Preview(showBackground = true)
@Composable
-fun AuthScreen(navController: NavHostController = rememberNavController()) {
+fun AuthScreen(appNavController: NavHostController = rememberNavController()) {
val context = LocalContext.current
LaunchedEffect(Unit) {
@@ -506,14 +115,20 @@ fun AuthScreen(navController: NavHostController = rememberNavController()) {
context.settingsDataStore.data.map { settings -> settings.accessToken }.first()
}
- if (accessToken.isNotEmpty()) navController.navigate("main")
+ if (accessToken.isNotEmpty()) {
+ appNavController.navigate("main") {
+ popUpTo("auth") { inclusive = true }
+ }
+ }
}
- val mutableIsLogin = remember { mutableStateOf(true) }
-
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
+ val onPendingSnackbar: (String) -> Unit = {
+ scope.launch { snackbarHostState.showSnackbar(it, duration = SnackbarDuration.Long) }
+ }
+
Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) },
modifier = Modifier.fillMaxSize(),
contentWindowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp),
@@ -525,14 +140,10 @@ fun AuthScreen(navController: NavHostController = rememberNavController()) {
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically
) {
- Card {
- AuthForm(
- mutableIsLogin,
- navController,
- scope,
- snackbarHostState
- )
- }
+ AuthForm(
+ appNavController,
+ onPendingSnackbar
+ )
}
})
}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/auth/SignInForm.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/auth/SignInForm.kt
new file mode 100644
index 0000000..d0be085
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/auth/SignInForm.kt
@@ -0,0 +1,143 @@
+package ru.n08i40k.polytechnic.next.ui.auth
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.rememberNavController
+import ru.n08i40k.polytechnic.next.R
+
+
+@Preview(showBackground = true)
+@Composable
+internal fun LoginForm(
+ appNavController: NavHostController = rememberNavController(),
+ navController: NavHostController = rememberNavController(),
+ onPendingSnackbar: (String) -> Unit = {}
+) {
+ val context = LocalContext.current
+ val focusManager = LocalFocusManager.current
+
+ var loading by remember { mutableStateOf(false) }
+
+ var username by remember { mutableStateOf("") }
+ var password by remember { mutableStateOf("") }
+
+ var usernameError by remember { mutableStateOf(false) }
+ var passwordError by remember { mutableStateOf(false) }
+
+ val onClick = fun() {
+ focusManager.clearFocus()
+
+ if (username.length < 4) usernameError = true
+ if (password.isEmpty()) passwordError = true
+
+ if (usernameError || passwordError) return
+
+ loading = true
+
+ trySignIn(
+ context,
+ username,
+ password,
+ {
+ loading = false
+
+ val stringRes = when (it) {
+ SignInError.INVALID_CREDENTIALS -> {
+ usernameError = true
+ passwordError = true
+
+ R.string.invalid_credentials
+ }
+
+ SignInError.TIMED_OUT -> R.string.timed_out
+ SignInError.NO_CONNECTION -> R.string.no_connection
+ SignInError.APPLICATION_TOO_OLD -> R.string.app_too_old
+ SignInError.UNKNOWN -> R.string.unknown_error
+ }
+
+ onPendingSnackbar(context.getString(stringRes))
+ },
+ {
+ loading = false
+
+ appNavController.navigate("main") {
+ popUpTo("auth") { inclusive = true }
+ }
+ }
+ )
+ }
+
+ Column(
+ modifier = Modifier.padding(20.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+
+ Text(
+ text = stringResource(R.string.sign_in_title),
+ modifier = Modifier.padding(10.dp),
+ style = MaterialTheme.typography.displaySmall,
+ fontWeight = FontWeight.ExtraBold
+ )
+
+ Spacer(modifier = Modifier.size(10.dp))
+
+ OutlinedTextField(
+ value = username,
+ singleLine = true,
+ onValueChange = {
+ username = it
+ usernameError = false
+ },
+ label = { Text(stringResource(R.string.username)) },
+ isError = usernameError
+ )
+
+ OutlinedTextField(
+ value = password,
+ singleLine = true,
+ visualTransformation = PasswordVisualTransformation(),
+ onValueChange = {
+ passwordError = false
+ password = it
+ },
+ label = { Text(stringResource(R.string.password)) },
+ isError = passwordError
+ )
+
+ TextButton(onClick = { navController.navigate("sign-up") }) {
+ Text(text = stringResource(R.string.not_registered))
+ }
+
+ Button(
+ enabled = !loading && !(usernameError || passwordError),
+ onClick = onClick
+ ) {
+ Text(
+ text = stringResource(R.string.proceed),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/auth/SignUpForm.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/auth/SignUpForm.kt
new file mode 100644
index 0000000..76ccc02
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/auth/SignUpForm.kt
@@ -0,0 +1,166 @@
+package ru.n08i40k.polytechnic.next.ui.auth
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.rememberNavController
+import ru.n08i40k.polytechnic.next.R
+import ru.n08i40k.polytechnic.next.model.UserRole
+import ru.n08i40k.polytechnic.next.ui.widgets.GroupSelector
+import ru.n08i40k.polytechnic.next.ui.widgets.RoleSelector
+
+
+@Preview(showBackground = true)
+@Composable
+internal fun RegisterForm(
+ appNavController: NavHostController = rememberNavController(),
+ navController: NavHostController = rememberNavController(),
+ onPendingSnackbar: (String) -> Unit = {}
+) {
+ val context = LocalContext.current
+ val focusManager = LocalFocusManager.current
+
+ var loading by remember { mutableStateOf(false) }
+
+ var username by remember { mutableStateOf("") }
+ var password by remember { mutableStateOf("") }
+ var group by remember { mutableStateOf(null) }
+ var role by remember { mutableStateOf(UserRole.STUDENT) }
+
+ var usernameError by remember { mutableStateOf(false) }
+ var passwordError by remember { mutableStateOf(false) }
+ var groupError by remember { mutableStateOf(false) }
+
+ val onClick = fun() {
+ focusManager.clearFocus()
+
+ if (username.length < 4) usernameError = true
+ if (password.isEmpty()) passwordError = true
+
+ if (usernameError || passwordError || groupError) return
+
+ loading = true
+
+ trySignUp(
+ context,
+ username,
+ password,
+ group!!,
+ role,
+ {
+ loading = false
+
+ val stringRes = when (it) {
+ SignUpError.UNKNOWN -> R.string.unknown_error
+ SignUpError.ALREADY_EXISTS -> R.string.already_exists
+ SignUpError.APPLICATION_TOO_OLD -> R.string.app_too_old
+ SignUpError.TIMED_OUT -> R.string.timed_out
+ SignUpError.NO_CONNECTION -> R.string.no_connection
+ SignUpError.GROUP_DOES_NOT_EXISTS -> R.string.group_does_not_exists
+ }
+
+ onPendingSnackbar(context.getString(stringRes))
+ },
+ {
+ loading = false
+
+ appNavController.navigate("main") {
+ popUpTo("auth") { inclusive = true }
+ }
+ }
+ )
+ }
+
+ Column(
+ modifier = Modifier.padding(20.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ text = stringResource(R.string.sign_up_title),
+ modifier = Modifier.padding(10.dp),
+ style = MaterialTheme.typography.displaySmall,
+ fontWeight = FontWeight.ExtraBold
+ )
+
+ Spacer(modifier = Modifier.size(10.dp))
+
+ OutlinedTextField(
+ value = username,
+ singleLine = true,
+ onValueChange = {
+ username = it
+ usernameError = false
+ },
+ label = { Text(stringResource(R.string.username)) },
+ isError = usernameError,
+ readOnly = loading
+ )
+
+ OutlinedTextField(
+ value = password,
+ singleLine = true,
+ visualTransformation = PasswordVisualTransformation(),
+ onValueChange = {
+ passwordError = false
+ password = it
+ },
+ label = { Text(stringResource(R.string.password)) },
+ isError = passwordError,
+ readOnly = loading
+ )
+
+ Spacer(modifier = Modifier.size(10.dp))
+
+ GroupSelector(
+ value = group,
+ isError = groupError,
+ readOnly = loading
+ ) {
+ groupError = false
+ group = it
+ }
+
+ Spacer(modifier = Modifier.size(10.dp))
+
+ RoleSelector(
+ value = role,
+ isError = false,
+ readOnly = loading
+ ) { role = it }
+
+ TextButton(onClick = { navController.navigate("sign-in") }) {
+ Text(text = stringResource(R.string.already_registered))
+ }
+
+ Button(
+ enabled = !loading && group != null && !(usernameError || passwordError || groupError),
+ onClick = onClick
+ ) {
+ Text(
+ text = stringResource(R.string.proceed),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/auth/TrySignIn.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/auth/TrySignIn.kt
new file mode 100644
index 0000000..2c7d3e1
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/auth/TrySignIn.kt
@@ -0,0 +1,68 @@
+package ru.n08i40k.polytechnic.next.ui.auth
+
+import android.content.Context
+import com.android.volley.AuthFailureError
+import com.android.volley.ClientError
+import com.android.volley.NoConnectionError
+import com.android.volley.TimeoutError
+import com.google.firebase.logger.Logger
+import kotlinx.coroutines.runBlocking
+import ru.n08i40k.polytechnic.next.network.request.auth.AuthSignIn
+import ru.n08i40k.polytechnic.next.network.unwrapException
+import ru.n08i40k.polytechnic.next.settings.settingsDataStore
+import java.util.concurrent.TimeoutException
+
+internal enum class SignInError {
+ INVALID_CREDENTIALS,
+ TIMED_OUT,
+ NO_CONNECTION,
+ APPLICATION_TOO_OLD,
+ UNKNOWN
+}
+
+internal fun trySignIn(
+ context: Context,
+
+ username: String,
+ password: String,
+
+ onError: (SignInError) -> Unit,
+ onSuccess: () -> Unit,
+) {
+ AuthSignIn(AuthSignIn.RequestDto(username, password), context, {
+ runBlocking {
+ context.settingsDataStore.updateData { currentSettings ->
+ currentSettings
+ .toBuilder()
+ .setUserId(it.id)
+ .setAccessToken(it.accessToken)
+ .setGroup(it.group)
+ .build()
+ }
+ }
+
+ onSuccess()
+ }, {
+ val error = when (val exception = unwrapException(it)) {
+ is TimeoutException -> SignInError.TIMED_OUT
+ is TimeoutError -> SignInError.TIMED_OUT
+ is NoConnectionError -> SignInError.NO_CONNECTION
+ is AuthFailureError -> SignInError.INVALID_CREDENTIALS
+ is ClientError -> {
+ if (exception.networkResponse.statusCode == 400)
+ SignInError.APPLICATION_TOO_OLD
+ else
+ SignInError.UNKNOWN
+ }
+
+ else -> SignInError.UNKNOWN
+ }
+
+ if (error == SignInError.UNKNOWN) {
+ Logger.getLogger("tryLogin")
+ .error("Unknown exception while trying to login!", it)
+ }
+
+ onError(error)
+ }).send()
+}
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/auth/TrySignUp.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/auth/TrySignUp.kt
new file mode 100644
index 0000000..8d60614
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/auth/TrySignUp.kt
@@ -0,0 +1,78 @@
+package ru.n08i40k.polytechnic.next.ui.auth
+
+import android.content.Context
+import com.android.volley.ClientError
+import com.android.volley.NoConnectionError
+import com.android.volley.TimeoutError
+import com.google.firebase.logger.Logger
+import kotlinx.coroutines.runBlocking
+import ru.n08i40k.polytechnic.next.model.UserRole
+import ru.n08i40k.polytechnic.next.network.request.auth.AuthSignUp
+import ru.n08i40k.polytechnic.next.network.unwrapException
+import ru.n08i40k.polytechnic.next.settings.settingsDataStore
+import java.util.concurrent.TimeoutException
+
+internal enum class SignUpError {
+ ALREADY_EXISTS,
+ GROUP_DOES_NOT_EXISTS,
+ TIMED_OUT,
+ NO_CONNECTION,
+ APPLICATION_TOO_OLD,
+ UNKNOWN
+}
+
+internal fun trySignUp(
+ context: Context,
+
+ username: String,
+ password: String,
+ group: String,
+ role: UserRole,
+
+ onError: (SignUpError) -> Unit,
+ onSuccess: () -> Unit,
+) {
+ AuthSignUp(
+ AuthSignUp.RequestDto(
+ username,
+ password,
+ group,
+ role
+ ), context, {
+ runBlocking {
+ context.settingsDataStore.updateData { currentSettings ->
+ currentSettings
+ .toBuilder()
+ .setUserId(it.id)
+ .setAccessToken(it.accessToken)
+ .setGroup(group)
+ .build()
+ }
+ }
+
+ onSuccess()
+ }, {
+ val error = when (val exception = unwrapException(it)) {
+ is TimeoutException -> SignUpError.TIMED_OUT
+ is NoConnectionError -> SignUpError.NO_CONNECTION
+ is TimeoutError -> SignUpError.UNKNOWN
+ is ClientError -> {
+ when (exception.networkResponse.statusCode) {
+ 400 -> SignUpError.APPLICATION_TOO_OLD
+ 404 -> SignUpError.GROUP_DOES_NOT_EXISTS
+ 409 -> SignUpError.ALREADY_EXISTS
+ else -> SignUpError.UNKNOWN
+ }
+ }
+
+ else -> SignUpError.UNKNOWN
+ }
+
+ if (error == SignUpError.UNKNOWN) {
+ Logger.getLogger("tryRegister")
+ .error("Unknown exception while trying to register!", it)
+ }
+
+ onError(error)
+ }).send()
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/AppIcons.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/AppIcons.kt
index 9e1ceaa..7d99ba7 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/AppIcons.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/AppIcons.kt
@@ -1,3 +1,3 @@
package ru.n08i40k.polytechnic.next.ui.icons
-object AppIcons
\ No newline at end of file
+object AppIcons
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/Filled.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/Filled.kt
index cda0c02..bccef7e 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/Filled.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/Filled.kt
@@ -6,4 +6,4 @@ object FilledGroup
@Suppress("UnusedReceiverParameter")
val AppIcons.Filled: FilledGroup
- get() = FilledGroup
\ No newline at end of file
+ get() = FilledGroup
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/filled/Download.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/filled/Download.kt
index fbbfe5b..2f21b4e 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/filled/Download.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/filled/Download.kt
@@ -1,3 +1,5 @@
+@file:Suppress("ObjectPropertyName", "UnusedReceiverParameter")
+
package ru.n08i40k.polytechnic.next.ui.icons.appicons.filled
import androidx.compose.ui.graphics.Color
@@ -11,14 +13,13 @@ import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
import ru.n08i40k.polytechnic.next.ui.icons.appicons.FilledGroup
-@Suppress("UnusedReceiverParameter")
val FilledGroup.Download: ImageVector
get() {
if (_download != null) {
return _download!!
}
_download = Builder(
- name = "Download", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp,
+ name = "Download", defaultWidth = 24.dp, defaultHeight = 24.dp,
viewportWidth = 24.0f, viewportHeight = 24.0f
).apply {
path(
@@ -55,5 +56,4 @@ val FilledGroup.Download: ImageVector
return _download!!
}
-@Suppress("ObjectPropertyName")
private var _download: ImageVector? = null
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/filled/Error.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/filled/Error.kt
new file mode 100644
index 0000000..04e7f3e
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/filled/Error.kt
@@ -0,0 +1,64 @@
+@file:Suppress("ObjectPropertyName", "UnusedReceiverParameter")
+
+package ru.n08i40k.polytechnic.next.ui.icons.appicons.filled
+
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.PathFillType.Companion.EvenOdd
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
+import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.graphics.vector.ImageVector.Builder
+import androidx.compose.ui.graphics.vector.path
+import androidx.compose.ui.unit.dp
+import ru.n08i40k.polytechnic.next.ui.icons.appicons.FilledGroup
+
+val FilledGroup.Error: ImageVector
+ get() {
+ if (_error != null) {
+ return _error!!
+ }
+ _error = Builder(
+ name = "Error", defaultWidth = 24.dp, defaultHeight = 24.dp,
+ viewportWidth = 24.0f, viewportHeight = 24.0f
+ ).apply {
+ path(
+ fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f,
+ strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
+ pathFillType = EvenOdd
+ ) {
+ moveTo(1.25f, 8.0f)
+ curveTo(1.25f, 4.2721f, 4.2721f, 1.25f, 8.0f, 1.25f)
+ lineTo(16.0f, 1.25f)
+ curveTo(19.7279f, 1.25f, 22.75f, 4.2721f, 22.75f, 8.0f)
+ lineTo(22.75f, 16.0f)
+ curveTo(22.75f, 19.7279f, 19.7279f, 22.75f, 16.0f, 22.75f)
+ lineTo(8.0f, 22.75f)
+ curveTo(4.2721f, 22.75f, 1.25f, 19.7279f, 1.25f, 16.0f)
+ lineTo(1.25f, 8.0f)
+ close()
+ moveTo(8.4697f, 8.4697f)
+ curveTo(8.7626f, 8.1768f, 9.2374f, 8.1768f, 9.5303f, 8.4697f)
+ lineTo(12.0f, 10.9393f)
+ lineTo(14.4697f, 8.4697f)
+ curveTo(14.7626f, 8.1768f, 15.2374f, 8.1768f, 15.5303f, 8.4697f)
+ curveTo(15.8232f, 8.7626f, 15.8232f, 9.2374f, 15.5303f, 9.5303f)
+ lineTo(13.0606f, 12.0f)
+ lineTo(15.5303f, 14.4697f)
+ curveTo(15.8232f, 14.7626f, 15.8232f, 15.2374f, 15.5303f, 15.5303f)
+ curveTo(15.2374f, 15.8232f, 14.7625f, 15.8232f, 14.4696f, 15.5303f)
+ lineTo(12.0f, 13.0606f)
+ lineTo(9.5303f, 15.5303f)
+ curveTo(9.2374f, 15.8232f, 8.7626f, 15.8232f, 8.4697f, 15.5303f)
+ curveTo(8.1768f, 15.2374f, 8.1768f, 14.7625f, 8.4697f, 14.4696f)
+ lineTo(10.9393f, 12.0f)
+ lineTo(8.4697f, 9.5303f)
+ curveTo(8.1768f, 9.2374f, 8.1768f, 8.7626f, 8.4697f, 8.4697f)
+ close()
+ }
+ }
+ .build()
+ return _error!!
+ }
+
+private var _error: ImageVector? = null
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/filled/Info.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/filled/Info.kt
new file mode 100644
index 0000000..9324ef8
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/filled/Info.kt
@@ -0,0 +1,49 @@
+@file:Suppress("ObjectPropertyName", "UnusedReceiverParameter")
+
+package ru.n08i40k.polytechnic.next.ui.icons.appicons.filled
+
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
+import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.graphics.vector.ImageVector.Builder
+import androidx.compose.ui.graphics.vector.path
+import androidx.compose.ui.unit.dp
+import ru.n08i40k.polytechnic.next.ui.icons.appicons.FilledGroup
+
+val FilledGroup.Info: ImageVector
+ get() {
+ if (_info != null) {
+ return _info!!
+ }
+ _info = Builder(
+ name = "Info", defaultWidth = 24.dp, defaultHeight = 24.dp,
+ viewportWidth = 24.0f, viewportHeight = 24.0f
+ ).apply {
+ path(
+ fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f,
+ strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
+ pathFillType = NonZero
+ ) {
+ moveTo(12.0f, 2.0f)
+ arcTo(10.0f, 10.0f, 0.0f, true, false, 22.0f, 12.0f)
+ arcTo(10.0f, 10.0f, 0.0f, false, false, 12.0f, 2.0f)
+ close()
+ moveTo(13.0f, 17.0f)
+ arcToRelative(1.0f, 1.0f, 0.0f, false, true, -2.0f, 0.0f)
+ lineTo(11.0f, 11.0f)
+ arcToRelative(1.0f, 1.0f, 0.0f, false, true, 2.0f, 0.0f)
+ close()
+ moveTo(12.0f, 8.0f)
+ arcToRelative(1.5f, 1.5f, 0.0f, true, true, 1.5f, -1.5f)
+ arcTo(1.5f, 1.5f, 0.0f, false, true, 12.0f, 8.0f)
+ close()
+ }
+ }
+ .build()
+ return _info!!
+ }
+
+private var _info: ImageVector? = null
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/filled/Telegram.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/filled/Telegram.kt
index 5490f6f..d04eac6 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/filled/Telegram.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/filled/Telegram.kt
@@ -1,3 +1,5 @@
+@file:Suppress("ObjectPropertyName", "UnusedReceiverParameter")
+
package ru.n08i40k.polytechnic.next.ui.icons.appicons.filled
import androidx.compose.ui.graphics.Color
@@ -11,14 +13,13 @@ import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
import ru.n08i40k.polytechnic.next.ui.icons.appicons.FilledGroup
-@Suppress("UnusedReceiverParameter")
val FilledGroup.Telegram: ImageVector
get() {
if (_telegram != null) {
return _telegram!!
}
_telegram = Builder(
- name = "Telegram", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp,
+ name = "Telegram", defaultWidth = 24.dp, defaultHeight = 24.dp,
viewportWidth = 24.0f, viewportHeight = 24.0f
).apply {
path(
@@ -49,5 +50,4 @@ val FilledGroup.Telegram: ImageVector
return _telegram!!
}
-@Suppress("ObjectPropertyName")
private var _telegram: ImageVector? = null
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/filled/Warning.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/filled/Warning.kt
new file mode 100644
index 0000000..49d9594
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/icons/appicons/filled/Warning.kt
@@ -0,0 +1,59 @@
+@file:Suppress("ObjectPropertyName", "UnusedReceiverParameter")
+
+package ru.n08i40k.polytechnic.next.ui.icons.appicons.filled
+
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.PathFillType.Companion.EvenOdd
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
+import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.graphics.vector.ImageVector.Builder
+import androidx.compose.ui.graphics.vector.path
+import androidx.compose.ui.unit.dp
+import ru.n08i40k.polytechnic.next.ui.icons.appicons.FilledGroup
+
+val FilledGroup.Warning: ImageVector
+ get() {
+ if (_warning != null) {
+ return _warning!!
+ }
+ _warning = Builder(
+ name = "Warning", defaultWidth = 24.dp, defaultHeight = 24.dp,
+ viewportWidth = 512.0f, viewportHeight = 512.0f
+ ).apply {
+ path(
+ fill = SolidColor(Color(0xFF000000)), stroke = SolidColor(Color(0x00000000)),
+ strokeLineWidth = 1.0f, strokeLineCap = Butt, strokeLineJoin = Miter,
+ strokeLineMiter = 4.0f, pathFillType = EvenOdd
+ ) {
+ moveTo(278.313f, 48.296f)
+ curveTo(284.928f, 52.075f, 290.41f, 57.557f, 294.189f, 64.172f)
+ lineTo(476.667f, 383.508f)
+ curveTo(488.358f, 403.967f, 481.25f, 430.03f, 460.791f, 441.722f)
+ curveTo(454.344f, 445.405f, 447.047f, 447.343f, 439.622f, 447.343f)
+ lineTo(74.667f, 447.343f)
+ curveTo(51.103f, 447.343f, 32.0f, 428.241f, 32.0f, 404.677f)
+ curveTo(32.0f, 397.251f, 33.938f, 389.955f, 37.622f, 383.508f)
+ lineTo(220.099f, 64.172f)
+ curveTo(231.79f, 43.713f, 257.854f, 36.605f, 278.313f, 48.296f)
+ close()
+ moveTo(256.0f, 314.667f)
+ curveTo(240.762f, 314.667f, 229.333f, 325.931f, 229.333f, 340.949f)
+ curveTo(229.333f, 356.651f, 240.416f, 367.915f, 256.0f, 367.915f)
+ curveTo(271.238f, 367.915f, 282.667f, 356.651f, 282.667f, 341.291f)
+ curveTo(282.667f, 325.931f, 271.238f, 314.667f, 256.0f, 314.667f)
+ close()
+ moveTo(277.333f, 149.333f)
+ lineTo(234.667f, 149.333f)
+ lineTo(234.667f, 277.333f)
+ lineTo(277.333f, 277.333f)
+ lineTo(277.333f, 149.333f)
+ close()
+ }
+ }
+ .build()
+ return _warning!!
+ }
+
+private var _warning: ImageVector? = null
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/MainScreen.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/MainScreen.kt
index 983cf87..c340d7d 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/MainScreen.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/MainScreen.kt
@@ -116,21 +116,21 @@ private fun NavHostContainer(
)
)
},
- builder = {
- composable("profile") {
- ProfileScreen(LocalContext.current.profileViewModel!!) { context.profileViewModel!!.refreshProfile() }
- }
+ ) {
+ composable("profile") {
+ ProfileScreen(LocalContext.current.profileViewModel!!) { context.profileViewModel!!.refreshProfile() }
+ }
- composable("schedule") {
- ScheduleScreen(scheduleViewModel) { scheduleViewModel.refreshGroup() }
- }
+ composable("schedule") {
+ ScheduleScreen(scheduleViewModel) { scheduleViewModel.refreshGroup() }
+ }
- if (scheduleReplacerViewModel != null) {
- composable("replacer") {
- ReplacerScreen(scheduleReplacerViewModel) { scheduleReplacerViewModel.refresh() }
- }
+ if (scheduleReplacerViewModel != null) {
+ composable("replacer") {
+ ReplacerScreen(scheduleReplacerViewModel) { scheduleReplacerViewModel.refresh() }
}
- })
+ }
+ }
}
private fun openLink(context: Context, link: String) {
@@ -260,7 +260,11 @@ fun MainScreen(
viewModel(
factory = ProfileViewModel.provideFactory(
profileRepository = mainViewModel.appContainer.profileRepository,
- onUnauthorized = { appNavController.navigate("auth") })
+ onUnauthorized = {
+ appNavController.navigate("auth") {
+ popUpTo("main") { inclusive = true }
+ }
+ })
)
LocalContext.current.profileViewModel = profileViewModel
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/profile/ChangeGroupDialog.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/profile/ChangeGroupDialog.kt
index 8b5e499..a8cbcbb 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/profile/ChangeGroupDialog.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/profile/ChangeGroupDialog.kt
@@ -1,26 +1,14 @@
package ru.n08i40k.polytechnic.next.ui.main.profile
import android.content.Context
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Email
import androidx.compose.material3.Button
import androidx.compose.material3.Card
-import androidx.compose.material3.DropdownMenuItem
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.ExposedDropdownMenuBox
-import androidx.compose.material3.ExposedDropdownMenuDefaults
-import androidx.compose.material3.Icon
-import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.Text
-import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -36,7 +24,7 @@ import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.data.users.impl.FakeProfileRepository
import ru.n08i40k.polytechnic.next.model.Profile
import ru.n08i40k.polytechnic.next.network.request.profile.ProfileChangeGroup
-import ru.n08i40k.polytechnic.next.network.request.schedule.ScheduleGetGroupNames
+import ru.n08i40k.polytechnic.next.ui.widgets.GroupSelector
private enum class ChangeGroupError {
NOT_EXISTS
@@ -57,80 +45,6 @@ private fun tryChangeGroup(
}).send()
}
-@Composable
-private fun getGroups(context: Context): ArrayList {
- val groupPlaceholder = stringResource(R.string.loading)
-
- val groups = remember { arrayListOf(groupPlaceholder) }
-
- LaunchedEffect(groups) {
- ScheduleGetGroupNames(context, {
- groups.clear()
- groups.addAll(it.names)
- }, {
- throw it
- }).send()
- }
-
- return groups
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Preview(showBackground = true)
-@Composable
-private fun GroupSelector(
- value: String = "ИС-214/24",
- onValueChange: (String) -> Unit = {},
- isError: Boolean = false,
- readOnly: Boolean = false,
-) {
- var expanded by remember { mutableStateOf(false) }
-
- Box(
- modifier = Modifier.wrapContentSize()
- ) {
- ExposedDropdownMenuBox(
- expanded = expanded,
- onExpandedChange = {
- expanded = !readOnly && !expanded
- }
- ) {
- TextField(
- label = { Text(stringResource(R.string.group)) },
- modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryEditable),
- value = value,
- leadingIcon = {
- Icon(
- imageVector = Icons.Filled.Email,
- contentDescription = "group"
- )
- },
- onValueChange = {},
- isError = isError,
- readOnly = true,
- trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }
- )
-
- val context = LocalContext.current
- val groups = getGroups(context)
-
- ExposedDropdownMenu(
- expanded = expanded,
- onDismissRequest = { expanded = false }) {
- groups.forEach {
- DropdownMenuItem(
- text = { Text(it) },
- onClick = {
- onValueChange(it)
- expanded = false
- }
- )
- }
- }
- }
- }
-}
-
@Preview(showBackground = true)
@Composable
internal fun ChangeGroupDialog(
@@ -141,7 +55,7 @@ internal fun ChangeGroupDialog(
) {
Dialog(onDismissRequest = onDismiss) {
Card {
- var group by remember { mutableStateOf("ИС-214/23") }
+ var group by remember { mutableStateOf(profile.group) }
var groupError by remember { mutableStateOf(false) }
var processing by remember { mutableStateOf(false) }
@@ -165,7 +79,7 @@ internal fun ChangeGroupDialog(
tryChangeGroup(
context = context,
- group = group,
+ group = group!!,
onError = {
when (it) {
ChangeGroupError.NOT_EXISTS -> {
@@ -178,7 +92,7 @@ internal fun ChangeGroupDialog(
onSuccess = onChange
)
},
- enabled = !(groupError || processing)
+ enabled = !(groupError || processing) && group != null
) {
Text(stringResource(R.string.change_group))
}
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/profile/ProfileCard.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/profile/ProfileCard.kt
index 2b7a557..754a83e 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/profile/ProfileCard.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/profile/ProfileCard.kt
@@ -33,6 +33,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import kotlinx.coroutines.runBlocking
+import ru.n08i40k.polytechnic.next.PolytechnicApplication
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.data.users.impl.FakeProfileRepository
import ru.n08i40k.polytechnic.next.model.Profile
@@ -187,6 +188,10 @@ internal fun ProfileCard(profile: Profile = FakeProfileRepository.exampleProfile
context.settingsDataStore.updateData {
it.toBuilder().setGroup(group).build()
}
+ (context.applicationContext as PolytechnicApplication)
+ .container
+ .networkCacheRepository
+ .clear()
}
context.profileViewModel!!.refreshProfile {
scheduleViewModel.refreshGroup()
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/profile/ProfileScreen.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/profile/ProfileScreen.kt
index f33925f..9aaedae 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/profile/ProfileScreen.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/profile/ProfileScreen.kt
@@ -14,7 +14,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.data.MockAppContainer
-import ru.n08i40k.polytechnic.next.ui.LoadingContent
+import ru.n08i40k.polytechnic.next.ui.widgets.LoadingContent
import ru.n08i40k.polytechnic.next.ui.model.ProfileUiState
import ru.n08i40k.polytechnic.next.ui.model.ProfileViewModel
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/replacer/ReplacerScreen.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/replacer/ReplacerScreen.kt
index e2168b7..ddd021d 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/replacer/ReplacerScreen.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/replacer/ReplacerScreen.kt
@@ -43,7 +43,7 @@ import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.data.MockAppContainer
import ru.n08i40k.polytechnic.next.data.scheduleReplacer.impl.FakeScheduleReplacerRepository
import ru.n08i40k.polytechnic.next.model.ScheduleReplacer
-import ru.n08i40k.polytechnic.next.ui.LoadingContent
+import ru.n08i40k.polytechnic.next.ui.widgets.LoadingContent
import ru.n08i40k.polytechnic.next.ui.model.ScheduleReplacerUiState
import ru.n08i40k.polytechnic.next.ui.model.ScheduleReplacerViewModel
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/schedule/DayCard.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/schedule/DayCard.kt
index db412e6..f23d3b4 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/schedule/DayCard.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/schedule/DayCard.kt
@@ -33,23 +33,15 @@ import kotlinx.coroutines.flow.flow
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.data.schedule.impl.FakeScheduleRepository
import ru.n08i40k.polytechnic.next.model.Day
-import ru.n08i40k.polytechnic.next.model.Lesson
import ru.n08i40k.polytechnic.next.model.LessonType
-import java.util.Calendar
-
-private fun getCurrentMinutes(): Int {
- return Calendar.getInstance()
- .get(Calendar.HOUR_OF_DAY) * 60 + Calendar.getInstance()
- .get(Calendar.MINUTE)
-}
@Composable
-private fun getMinutes(): Flow {
+private fun getCurrentLessonIdx(day: Day?): Flow {
val value by remember {
derivedStateOf {
flow {
while (true) {
- emit(getCurrentMinutes())
+ emit(day?.currentIdx ?: -1)
delay(5_000)
}
}
@@ -59,22 +51,6 @@ private fun getMinutes(): Flow {
return value
}
-@Composable
-fun calculateCurrentLessonIdx(lessons: ArrayList): Int {
- val currentMinutes by getMinutes().collectAsStateWithLifecycle(0)
-
- val filteredLessons = lessons
- .filterNotNull()
- .filter {
- it.time.start <= currentMinutes && it.time.end >= currentMinutes
- }
-
- if (filteredLessons.isEmpty())
- return -1
-
- return lessons.indexOf(filteredLessons[0])
-}
-
@Preview(showBackground = true)
@Composable
fun DayCard(
@@ -99,8 +75,8 @@ fun DayCard(
modifier = modifier,
colors = CardDefaults.cardColors(
containerColor =
- if (current) MaterialTheme.colorScheme.inverseSurface
- else MaterialTheme.colorScheme.surface
+ if (current) MaterialTheme.colorScheme.primaryContainer
+ else MaterialTheme.colorScheme.secondaryContainer
),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.inverseSurface)
) {
@@ -109,14 +85,10 @@ fun DayCard(
modifier = Modifier.fillMaxWidth(),
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
- text = stringResource(R.string.day_null),
- color =
- if (current) MaterialTheme.colorScheme.inverseOnSurface
- else MaterialTheme.colorScheme.onSurface
+ text = stringResource(R.string.day_null)
)
return@Card
}
-
Text(
modifier = Modifier.fillMaxWidth(),
fontWeight = FontWeight.Bold,
@@ -124,7 +96,8 @@ fun DayCard(
text = day.name,
)
- val currentLessonIdx = calculateCurrentLessonIdx(day.lessons)
+ val currentLessonIdx by getCurrentLessonIdx(if (current) day else null)
+ .collectAsStateWithLifecycle(0)
Column(
modifier = Modifier.fillMaxWidth(),
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/schedule/DayPager.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/schedule/DayPager.kt
index dace29c..f614e06 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/schedule/DayPager.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/schedule/DayPager.kt
@@ -1,21 +1,50 @@
package ru.n08i40k.polytechnic.next.ui.main.schedule
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
+import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.data.schedule.impl.FakeScheduleRepository
import ru.n08i40k.polytechnic.next.model.Group
+import ru.n08i40k.polytechnic.next.ui.widgets.NotificationCard
+import java.time.LocalDate
+import java.time.format.DateTimeFormatter
+import java.time.temporal.WeekFields
import java.util.Calendar
+import java.util.Locale
+import java.util.logging.Level
import kotlin.math.absoluteValue
+private fun isCurrentWeek(group: Group): Boolean {
+ if (group.days.size == 0 || group.days[0] == null)
+ return true
+
+ val dateString = group.days[0]!!.name
+
+ val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy", Locale("ru"))
+ val datePart = dateString.split(" ").getOrNull(1) ?: return true
+
+ val date = LocalDate.parse(datePart, formatter)
+ val currentDate = LocalDate.now()
+
+ val weekField = WeekFields.of(Locale.getDefault()).weekOfWeekBasedYear()
+ val currentWeek = currentDate.get(weekField)
+ val dateWeek = date.get(weekField)
+
+ return dateWeek >= currentWeek
+}
+
@Preview
@Composable
fun DayPager(group: Group = FakeScheduleRepository.exampleGroup) {
@@ -23,31 +52,38 @@ fun DayPager(group: Group = FakeScheduleRepository.exampleGroup) {
val calendarDay = currentDay
.coerceAtLeast(0)
.coerceAtMost(group.days.size - 1)
-
val pagerState = rememberPagerState(initialPage = calendarDay, pageCount = { group.days.size })
- HorizontalPager(
- state = pagerState,
- contentPadding = PaddingValues(horizontal = 20.dp),
- verticalAlignment = Alignment.Top,
- modifier = Modifier.height(600.dp)
- ) { page ->
- DayCard(
- modifier = Modifier.graphicsLayer {
- val offset = pagerState.getOffsetDistanceInPages(page).absoluteValue
+ Column {
+ if (!isCurrentWeek(group)) {
+ NotificationCard(
+ level = Level.WARNING,
+ title = stringResource(R.string.outdated_schedule)
+ )
+ }
+ HorizontalPager(
+ state = pagerState,
+ contentPadding = PaddingValues(horizontal = 20.dp),
+ verticalAlignment = Alignment.Top,
+ modifier = Modifier.height(600.dp).padding(top = 5.dp)
+ ) { page ->
+ DayCard(
+ modifier = Modifier.graphicsLayer {
+ val offset = pagerState.getOffsetDistanceInPages(page).absoluteValue
- lerp(
- start = 1f, stop = 0.95f, fraction = 1f - offset.coerceIn(0f, 1f)
- ).also { scale ->
- scaleX = scale
- scaleY = scale
- }
- alpha = lerp(
- start = 0.5f, stop = 1f, fraction = 1f - offset.coerceIn(0f, 1f)
- )
- },
- day = group.days[page],
- current = currentDay == page
- )
+ lerp(
+ start = 1f, stop = 0.95f, fraction = 1f - offset.coerceIn(0f, 1f)
+ ).also { scale ->
+ scaleX = scale
+ scaleY = scale
+ }
+ alpha = lerp(
+ start = 0.5f, stop = 1f, fraction = 1f - offset.coerceIn(0f, 1f)
+ )
+ },
+ day = group.days[page],
+ current = currentDay == page
+ )
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/schedule/ScheduleScreen.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/schedule/ScheduleScreen.kt
index 416b44f..60a37f9 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/schedule/ScheduleScreen.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/schedule/ScheduleScreen.kt
@@ -26,7 +26,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.delay
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.data.MockAppContainer
-import ru.n08i40k.polytechnic.next.ui.LoadingContent
+import ru.n08i40k.polytechnic.next.ui.widgets.LoadingContent
import ru.n08i40k.polytechnic.next.ui.model.ScheduleUiState
import ru.n08i40k.polytechnic.next.ui.model.ScheduleViewModel
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/schedule/UpdateInfo.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/schedule/UpdateInfo.kt
index 96a1d8e..b7bf22b 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/schedule/UpdateInfo.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/schedule/UpdateInfo.kt
@@ -19,7 +19,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.UpdateDates
-import ru.n08i40k.polytechnic.next.ui.ExpandableCard
+import ru.n08i40k.polytechnic.next.ui.widgets.ExpandableCard
+import ru.n08i40k.polytechnic.next.ui.widgets.ExpandableCardTitle
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@@ -49,7 +50,7 @@ fun UpdateInfo(
ExpandableCard(
expanded = expanded,
onExpandedChange = { expanded = !expanded },
- title = stringResource(R.string.update_info_header)
+ title = { ExpandableCardTitle(stringResource(R.string.update_info_header)) }
) {
Column(
modifier = Modifier
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/theme/Color.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/theme/Color.kt
index 94ca7e3..5b86b32 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/theme/Color.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/theme/Color.kt
@@ -1,5 +1,4 @@
package ru.n08i40k.polytechnic.next.ui.theme
-
import androidx.compose.ui.graphics.Color
val primaryLight = Color(0xFF4C662B)
@@ -218,9 +217,33 @@ val surfaceContainerDarkHighContrast = Color(0xFF1E201A)
val surfaceContainerHighDarkHighContrast = Color(0xFF282B24)
val surfaceContainerHighestDarkHighContrast = Color(0xFF33362E)
+val warningLight = Color(0xFF7B580C)
+val onWarningLight = Color(0xFFFFFFFF)
+val warningContainerLight = Color(0xFFFFDEA8)
+val onWarningContainerLight = Color(0xFF271900)
+val warningLightMediumContrast = Color(0xFF593E00)
+val onWarningLightMediumContrast = Color(0xFFFFFFFF)
+val warningContainerLightMediumContrast = Color(0xFF946E24)
+val onWarningContainerLightMediumContrast = Color(0xFFFFFFFF)
+val warningLightHighContrast = Color(0xFF2F1F00)
+val onWarningLightHighContrast = Color(0xFFFFFFFF)
+val warningContainerLightHighContrast = Color(0xFF593E00)
+val onWarningContainerLightHighContrast = Color(0xFFFFFFFF)
+val warningDark = Color(0xFFEEBF6D)
+val onWarningDark = Color(0xFF422D00)
+val warningContainerDark = Color(0xFF5E4200)
+val onWarningContainerDark = Color(0xFFFFDEA8)
+val warningDarkMediumContrast = Color(0xFFF2C470)
+val onWarningDarkMediumContrast = Color(0xFF201400)
+val warningContainerDarkMediumContrast = Color(0xFFB38A3D)
+val onWarningContainerDarkMediumContrast = Color(0xFF000000)
+val warningDarkHighContrast = Color(0xFFFFFAF7)
+val onWarningDarkHighContrast = Color(0xFF000000)
+val warningContainerDarkHighContrast = Color(0xFFF2C470)
+val onWarningContainerDarkHighContrast = Color(0xFF000000)
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/theme/Theme.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/theme/Theme.kt
index 3eccab2..00e0de7 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/theme/Theme.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/theme/Theme.kt
@@ -14,6 +14,11 @@ import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
+@Immutable
+data class ExtendedColorScheme(
+ val warning: ColorFamily,
+)
+
private val lightScheme = lightColorScheme(
primary = primaryLight,
onPrimary = onPrimaryLight,
@@ -242,6 +247,60 @@ private val highContrastDarkColorScheme = darkColorScheme(
surfaceContainerHighest = surfaceContainerHighestDarkHighContrast,
)
+val extendedLight = ExtendedColorScheme(
+ warning = ColorFamily(
+ warningLight,
+ onWarningLight,
+ warningContainerLight,
+ onWarningContainerLight,
+ ),
+)
+
+val extendedDark = ExtendedColorScheme(
+ warning = ColorFamily(
+ warningDark,
+ onWarningDark,
+ warningContainerDark,
+ onWarningContainerDark,
+ ),
+)
+
+val extendedLightMediumContrast = ExtendedColorScheme(
+ warning = ColorFamily(
+ warningLightMediumContrast,
+ onWarningLightMediumContrast,
+ warningContainerLightMediumContrast,
+ onWarningContainerLightMediumContrast,
+ ),
+)
+
+val extendedLightHighContrast = ExtendedColorScheme(
+ warning = ColorFamily(
+ warningLightHighContrast,
+ onWarningLightHighContrast,
+ warningContainerLightHighContrast,
+ onWarningContainerLightHighContrast,
+ ),
+)
+
+val extendedDarkMediumContrast = ExtendedColorScheme(
+ warning = ColorFamily(
+ warningDarkMediumContrast,
+ onWarningDarkMediumContrast,
+ warningContainerDarkMediumContrast,
+ onWarningContainerDarkMediumContrast,
+ ),
+)
+
+val extendedDarkHighContrast = ExtendedColorScheme(
+ warning = ColorFamily(
+ warningDarkHighContrast,
+ onWarningDarkHighContrast,
+ warningContainerDarkHighContrast,
+ onWarningContainerDarkHighContrast,
+ ),
+)
+
@Immutable
data class ColorFamily(
val color: Color,
@@ -254,12 +313,17 @@ val unspecified_scheme = ColorFamily(
Color.Unspecified, Color.Unspecified, Color.Unspecified, Color.Unspecified
)
+@Composable
+fun extendedColorScheme(): ExtendedColorScheme {
+ return if (isSystemInDarkTheme()) extendedDark else extendedLight
+}
+
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
- content: @Composable () -> Unit
+ content: @Composable() () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/ExpandableCard.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/ExpandableCard.kt
similarity index 80%
rename from app/src/main/java/ru/n08i40k/polytechnic/next/ui/ExpandableCard.kt
rename to app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/ExpandableCard.kt
index f04c88e..9f62a99 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/ExpandableCard.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/ExpandableCard.kt
@@ -1,4 +1,4 @@
-package ru.n08i40k.polytechnic.next.ui
+package ru.n08i40k.polytechnic.next.ui.widgets
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.MutableTransitionState
@@ -31,7 +31,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
-import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -39,9 +38,14 @@ import androidx.compose.ui.unit.dp
@Composable
fun ExpandableCard(
modifier: Modifier = Modifier,
- expanded: Boolean,
+ colors: CardColors = CardDefaults.cardColors(),
+ border: BorderStroke = BorderStroke(
+ Dp.Hairline,
+ MaterialTheme.colorScheme.inverseSurface
+ ),
+ expanded: Boolean = false,
onExpandedChange: () -> Unit,
- title: String,
+ title: @Composable () -> Unit,
content: @Composable () -> Unit
) {
val transitionState = remember {
@@ -57,8 +61,8 @@ fun ExpandableCard(
onExpandedChange()
transitionState.targetState = expanded
},
- colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
- border = BorderStroke(Dp.Hairline, MaterialTheme.colorScheme.inverseSurface)
+ colors = colors,
+ border = border
) {
Column {
ExpandableCardHeader(title, transition)
@@ -67,6 +71,25 @@ fun ExpandableCard(
}
}
+@Composable
+fun ExpandableCard(
+ modifier: Modifier = Modifier,
+ title: @Composable () -> Unit,
+ colors: CardColors = CardDefaults.cardColors(),
+ border: BorderStroke = BorderStroke(
+ Dp.Hairline,
+ MaterialTheme.colorScheme.inverseSurface
+ ),
+) {
+ Card(
+ modifier = modifier,
+ colors = colors,
+ border = border
+ ) {
+ ExpandableCardHeader(title, null)
+ }
+}
+
@Composable
private fun ExpandableCardContent(
visible: Boolean = true,
@@ -99,7 +122,7 @@ private fun ExpandableCardContent(
}
@Composable
-private fun ExpandableCardTitle(text: String) {
+fun ExpandableCardTitle(text: String) {
Text(
text = text,
modifier = Modifier
@@ -130,8 +153,8 @@ private fun ExpandableCardArrow(
@Composable
private fun ExpandableCardHeader(
- title: String = "TODO",
- transition: Transition
+ title: @Composable () -> Unit,
+ transition: Transition?
) {
Box(
modifier = Modifier
@@ -139,7 +162,8 @@ private fun ExpandableCardHeader(
.padding(10.dp, 0.dp),
contentAlignment = Alignment.CenterEnd,
) {
- ExpandableCardArrow(transition)
- ExpandableCardTitle(title)
+ if (transition != null)
+ ExpandableCardArrow(transition)
+ title()
}
}
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/GroupSelector.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/GroupSelector.kt
new file mode 100644
index 0000000..0e8dba4
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/GroupSelector.kt
@@ -0,0 +1,109 @@
+package ru.n08i40k.polytechnic.next.ui.widgets
+
+import android.content.Context
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Email
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuBox
+import androidx.compose.material3.ExposedDropdownMenuDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MenuAnchorType
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import ru.n08i40k.polytechnic.next.R
+import ru.n08i40k.polytechnic.next.network.request.schedule.ScheduleGetGroupNames
+
+@Composable
+private fun getGroups(context: Context, onUpdated: (String?) -> Unit): ArrayList {
+ val groupPlaceholder = stringResource(R.string.loading)
+
+ val groups = remember { arrayListOf(null, groupPlaceholder) }
+
+ LaunchedEffect(groups) {
+ ScheduleGetGroupNames(context, {
+ groups.clear()
+ groups.addAll(it.names)
+ onUpdated(groups.getOrElse(0) { "TODO" }!!)
+ }, {
+ groups.clear()
+ groups.add(null)
+ groups.add(context.getString(R.string.failed_to_fetch_group_names))
+ onUpdated(groups[1]!!)
+ }).send()
+ }
+
+ return groups
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview(showBackground = true)
+@Composable
+fun GroupSelector(
+ value: String? = "ИС-214/24",
+ isError: Boolean = false,
+ readOnly: Boolean = false,
+ onValueChange: (String?) -> Unit = {},
+) {
+ var expanded by remember { mutableStateOf(false) }
+
+ Box(
+ modifier = Modifier.wrapContentSize()
+ ) {
+ ExposedDropdownMenuBox(
+ expanded = expanded,
+ onExpandedChange = {
+ expanded = !readOnly && !expanded
+ }
+ ) {
+ val groups = getGroups(LocalContext.current, onValueChange)
+
+ TextField(
+ label = { Text(stringResource(R.string.group)) },
+ modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryEditable),
+ value = value ?: groups.getOrElse(1) { "TODO" }!!,
+ leadingIcon = {
+ Icon(
+ imageVector = Icons.Filled.Email,
+ contentDescription = "group"
+ )
+ },
+ onValueChange = {},
+ isError = isError,
+ readOnly = true,
+ trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }
+ )
+
+ ExposedDropdownMenu(
+ expanded = expanded,
+ onDismissRequest = { expanded = false }
+ ) {
+ groups.forEach {
+ if (it == null)
+ return@forEach
+
+ DropdownMenuItem(
+ text = { Text(it) },
+ onClick = {
+ if (groups.size > 0 && groups[0] != null)
+ onValueChange(it)
+ expanded = false
+ }
+ )
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/LoadingContent.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/LoadingContent.kt
similarity index 96%
rename from app/src/main/java/ru/n08i40k/polytechnic/next/ui/LoadingContent.kt
rename to app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/LoadingContent.kt
index 33cb13e..1432f3c 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/LoadingContent.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/LoadingContent.kt
@@ -1,4 +1,4 @@
-package ru.n08i40k.polytechnic.next.ui
+package ru.n08i40k.polytechnic.next.ui.widgets
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/NotificationCard.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/NotificationCard.kt
new file mode 100644
index 0000000..9d52a81
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/NotificationCard.kt
@@ -0,0 +1,101 @@
+package ru.n08i40k.polytechnic.next.ui.widgets
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import ru.n08i40k.polytechnic.next.ui.icons.AppIcons
+import ru.n08i40k.polytechnic.next.ui.icons.appicons.Filled
+import ru.n08i40k.polytechnic.next.ui.icons.appicons.filled.Error
+import ru.n08i40k.polytechnic.next.ui.icons.appicons.filled.Info
+import ru.n08i40k.polytechnic.next.ui.icons.appicons.filled.Warning
+import ru.n08i40k.polytechnic.next.ui.theme.extendedColorScheme
+import java.util.logging.Level
+
+@Preview(showBackground = true)
+@Composable
+fun NotificationCard(
+ level: Level = Level.SEVERE,
+ title: String = "Test",
+ content: (@Composable () -> Unit)? = null
+) {
+ val titleComposable = @Composable {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(10.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ val icon = when (level) {
+ Level.SEVERE -> AppIcons.Filled.Error
+ Level.WARNING -> AppIcons.Filled.Warning
+ else -> AppIcons.Filled.Info
+ }
+
+ Icon(imageVector = icon, contentDescription = "Level")
+ Icon(imageVector = icon, contentDescription = "Level")
+ }
+ Text(
+ text = title,
+ modifier = Modifier
+ .fillMaxWidth(),
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.titleMedium,
+ )
+ }
+ }
+
+ val colors = when (level) {
+ Level.WARNING -> {
+ val colorFamily = extendedColorScheme().warning
+ CardDefaults.cardColors(
+ containerColor = colorFamily.colorContainer,
+ contentColor = colorFamily.onColorContainer
+ )
+ }
+
+ Level.SEVERE -> CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.errorContainer,
+ contentColor = MaterialTheme.colorScheme.onErrorContainer,
+ )
+
+ else -> CardDefaults.cardColors()
+ }
+
+ if (content != null) {
+ var expanded by remember { mutableStateOf(false) }
+
+ ExpandableCard(
+ expanded = expanded,
+ onExpandedChange = { expanded = !expanded },
+ content = content,
+ title = titleComposable,
+ colors = colors
+ )
+ } else {
+ ExpandableCard(
+ title = titleComposable,
+ colors = colors
+ )
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/RoleSelector.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/RoleSelector.kt
new file mode 100644
index 0000000..23635e3
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/widgets/RoleSelector.kt
@@ -0,0 +1,76 @@
+package ru.n08i40k.polytechnic.next.ui.widgets
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuBox
+import androidx.compose.material3.ExposedDropdownMenuDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MenuAnchorType
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import ru.n08i40k.polytechnic.next.R
+import ru.n08i40k.polytechnic.next.model.UserRole
+import ru.n08i40k.polytechnic.next.model.UserRole.Companion.AcceptableUserRoles
+
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview(showBackground = true)
+@Composable
+fun RoleSelector(
+ value: UserRole = UserRole.STUDENT,
+ isError: Boolean = false,
+ readOnly: Boolean = false,
+ onValueChange: (UserRole) -> Unit = {},
+) {
+ var expanded by remember { mutableStateOf(false) }
+
+ Box(
+ modifier = Modifier.wrapContentSize()
+ ) {
+ ExposedDropdownMenuBox(
+ expanded = expanded,
+ onExpandedChange = { expanded = !readOnly && !expanded }
+ ) {
+ TextField(
+ label = { Text(stringResource(R.string.role)) },
+ modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryEditable),
+ value = stringResource(value.stringId),
+ leadingIcon = {
+ Icon(
+ imageVector = value.icon,
+ contentDescription = "role icon"
+ )
+ },
+ onValueChange = {},
+ isError = isError,
+ readOnly = true,
+ trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }
+ )
+
+ ExposedDropdownMenu(
+ expanded = expanded,
+ onDismissRequest = { expanded = false }) {
+ AcceptableUserRoles.forEach {
+ DropdownMenuItem(
+ leadingIcon = { Icon(it.icon, contentDescription = "Role icon") },
+ text = { Text(stringResource(it.stringId)) },
+ onClick = {
+ expanded = false
+ onValueChange(it)
+ }
+ )
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/work/ScheduleClvAlarm.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/work/ScheduleClvAlarm.kt
deleted file mode 100644
index 8056640..0000000
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/work/ScheduleClvAlarm.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-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()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/work/StartClvService.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/work/StartClvService.kt
deleted file mode 100644
index 1610fff..0000000
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/work/StartClvService.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-package ru.n08i40k.polytechnic.next.work
-
-import android.content.Context
-import android.content.Intent
-import androidx.core.content.ContextCompat.startForegroundService
-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
-import ru.n08i40k.polytechnic.next.service.CurrentLessonViewService
-
-class StartClvService(context: Context, workerParams: WorkerParameters) :
- Worker(context, workerParams) {
- override fun doWork(): Result {
- val schedule = runBlocking {
- (applicationContext as PolytechnicApplication)
- .container
- .scheduleRepository
- .getGroup()
- }
-
- if (schedule is MyResult.Failure)
- return Result.success()
-
- val intent = Intent(applicationContext, CurrentLessonViewService::class.java)
- .apply {
- putExtra("group", (schedule as MyResult.Success).data)
- }
-
- applicationContext.stopService(
- Intent(
- applicationContext,
- CurrentLessonViewService::class.java
- )
- )
- startForegroundService(applicationContext, intent)
-
- return Result.success()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher.xml b/app/src/main/res/drawable/ic_launcher.xml
deleted file mode 100644
index d7dbdae..0000000
--- a/app/src/main/res/drawable/ic_launcher.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..95bdda1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_round.xml b/app/src/main/res/drawable/ic_launcher_round.xml
deleted file mode 100644
index d7dbdae..0000000
--- a/app/src/main/res/drawable/ic_launcher_round.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/logo.xml b/app/src/main/res/drawable/logo.xml
deleted file mode 100644
index 321679e..0000000
--- a/app/src/main/res/drawable/logo.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/drawable/schedule.xml b/app/src/main/res/drawable/schedule.xml
new file mode 100644
index 0000000..8d121c8
--- /dev/null
+++ b/app/src/main/res/drawable/schedule.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..ef49c99
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..ef49c99
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..e46f5a1
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..3b7114e
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..633c509
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..76297f2
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1f9c63d
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 36d44b2..d856840 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -3,10 +3,9 @@
Политехникум
Имя пользователя
Пароль
- Зарегистрироваться
- Авторизоваться
- Авторизация
- Регистрация
+ Продолжить
+ Авторизация
+ Регистрация
Не зарегистрированы?
Уже зарегистрированы?
Перезагрузить
@@ -23,7 +22,7 @@
Администратор
Группа
Роль
- Расписание ещё не обновилось.
+ На этот день расписания ещё нет!
Старый пароль
Новый пароль
Загрузка…
@@ -65,4 +64,13 @@
в спорт-зале
Пары ещё не начались
До начала пар %1$d ч. %2$d мин.
+ Вы просматриваете устаревшее расписание!
+ Некорректное имя пользователя или пароль!
+ Неудалось отправить запрос, попробуйте позже!
+ Пожалуйста обновите приложение!
+ Произошла неизвестная ошибка! Попробуйте позже.
+ Не удалось получить список названий групп!
+ Пользователь с таким именем уже зарегистрирован!
+ Группа с таким названием не существует!
+ Нет подключения к интернету!
\ No newline at end of file
diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..c5d5899
--- /dev/null
+++ b/app/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e3a855b..8f4dbff 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -3,10 +3,9 @@
Username
Password
- Register
- Login
- Login
- Registration
+ Proceed
+ Sign In
+ Sign Up
Not registered?
Already registered?
Reload
@@ -23,7 +22,7 @@
Administrator
Group
Role
- Schedule not updated yet.
+ There is no schedule for this day yet!
Old password
New password
Change password
@@ -65,4 +64,13 @@
in gym
Lessons haven\'t started yet
%1$d h. %2$d min. before lessons start
+ You are viewing an outdated schedule!
+ Invalid credentials!
+ Failed to send request, try again later!
+ Please update the application!
+ An unknown error has occurred! Please try again later.
+ Failed to get list of group names!
+ A user with this name is already registered!
+ A group with this name does not exist!
+ No internet connection!
\ No newline at end of file