diff --git a/.gitignore b/.gitignore
index aa724b7..32aa5b0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,5 @@
.externalNativeBuild
.cxx
local.properties
+.kotlin
+app/release
diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml
index f23fb71..a1d7d06 100644
--- a/.idea/appInsightsSettings.xml
+++ b/.idea/appInsightsSettings.xml
@@ -20,9 +20,9 @@
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 4c85420..2904b84 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -7,7 +7,7 @@
-
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 0b7bda7..2f3aee5 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -32,8 +32,8 @@ android {
applicationId = "ru.n08i40k.polytechnic.next"
minSdk = 26
targetSdk = 35
- versionCode = 8
- versionName = "1.3.2"
+ versionCode = 9
+ versionName = "1.4.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/data/AppContainer.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/data/AppContainer.kt
index e67388b..0d2a0a9 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/data/AppContainer.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/data/AppContainer.kt
@@ -10,8 +10,11 @@ import ru.n08i40k.polytechnic.next.data.cache.NetworkCacheRepository
import ru.n08i40k.polytechnic.next.data.cache.impl.FakeNetworkCacheRepository
import ru.n08i40k.polytechnic.next.data.cache.impl.LocalNetworkCacheRepository
import ru.n08i40k.polytechnic.next.data.schedule.ScheduleRepository
+import ru.n08i40k.polytechnic.next.data.schedule.impl.FakeScheduleReplacerRepository
import ru.n08i40k.polytechnic.next.data.schedule.impl.FakeScheduleRepository
+import ru.n08i40k.polytechnic.next.data.schedule.impl.RemoteScheduleReplacerRepository
import ru.n08i40k.polytechnic.next.data.schedule.impl.RemoteScheduleRepository
+import ru.n08i40k.polytechnic.next.data.scheduleReplacer.ScheduleReplacerRepository
import ru.n08i40k.polytechnic.next.data.users.ProfileRepository
import ru.n08i40k.polytechnic.next.data.users.impl.FakeProfileRepository
import ru.n08i40k.polytechnic.next.data.users.impl.RemoteProfileRepository
@@ -19,33 +22,42 @@ import javax.inject.Singleton
interface AppContainer {
val applicationContext: Context
+
val networkCacheRepository: NetworkCacheRepository
+
val scheduleRepository: ScheduleRepository
+
+ val scheduleReplacerRepository: ScheduleReplacerRepository
+
val profileRepository: ProfileRepository
}
class MockAppContainer(override val applicationContext: Context) : AppContainer {
- override val networkCacheRepository: NetworkCacheRepository by lazy { FakeNetworkCacheRepository() }
- override val scheduleRepository: ScheduleRepository by lazy { FakeScheduleRepository() }
- override val profileRepository: ProfileRepository by lazy { FakeProfileRepository() }
+ override val networkCacheRepository: NetworkCacheRepository
+ by lazy { FakeNetworkCacheRepository() }
+
+ override val scheduleRepository: ScheduleRepository
+ by lazy { FakeScheduleRepository() }
+
+ override val scheduleReplacerRepository: ScheduleReplacerRepository
+ by lazy { FakeScheduleReplacerRepository() }
+
+ override val profileRepository: ProfileRepository
+ by lazy { FakeProfileRepository() }
}
class RemoteAppContainer(override val applicationContext: Context) : AppContainer {
- override val networkCacheRepository: NetworkCacheRepository by lazy {
- LocalNetworkCacheRepository(
- applicationContext
- )
- }
- override val scheduleRepository: ScheduleRepository by lazy {
- RemoteScheduleRepository(
- applicationContext
- )
- }
- override val profileRepository: ProfileRepository by lazy {
- RemoteProfileRepository(
- applicationContext
- )
- }
+ override val networkCacheRepository: NetworkCacheRepository
+ by lazy { LocalNetworkCacheRepository(applicationContext) }
+
+ override val scheduleRepository: ScheduleRepository
+ by lazy { RemoteScheduleRepository(applicationContext) }
+
+ override val scheduleReplacerRepository: ScheduleReplacerRepository
+ by lazy { RemoteScheduleReplacerRepository(applicationContext) }
+
+ override val profileRepository: ProfileRepository
+ by lazy { RemoteProfileRepository(applicationContext) }
}
@Module
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/data/MyResult.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/data/MyResult.kt
index 79c83c9..0876334 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/data/MyResult.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/data/MyResult.kt
@@ -4,4 +4,3 @@ sealed interface MyResult {
data class Success(val data: T) : MyResult
data class Failure(val exception: Exception) : MyResult
}
-
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 9d42c3b..e2c2449 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
@@ -9,8 +9,8 @@ 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.data.schedule.ScheduleGetRequest
-import ru.n08i40k.polytechnic.next.network.data.schedule.ScheduleGetRequestData
+import ru.n08i40k.polytechnic.next.network.data.schedule.ScheduleGetReq
+import ru.n08i40k.polytechnic.next.network.data.schedule.ScheduleGetReqData
import ru.n08i40k.polytechnic.next.network.tryFuture
import ru.n08i40k.polytechnic.next.settings.settingsDataStore
@@ -27,8 +27,8 @@ class RemoteScheduleRepository(private val context: Context) : ScheduleRepositor
return@withContext MyResult.Failure(IllegalArgumentException("No group name provided!"))
val response = tryFuture {
- ScheduleGetRequest(
- ScheduleGetRequestData(groupName),
+ ScheduleGetReq(
+ ScheduleGetReqData(groupName),
context,
it,
it
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/data/scheduleReplacer/ScheduleReplacerRepository.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/data/scheduleReplacer/ScheduleReplacerRepository.kt
new file mode 100644
index 0000000..9d22296
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/data/scheduleReplacer/ScheduleReplacerRepository.kt
@@ -0,0 +1,16 @@
+package ru.n08i40k.polytechnic.next.data.scheduleReplacer
+
+import ru.n08i40k.polytechnic.next.data.MyResult
+import ru.n08i40k.polytechnic.next.model.ScheduleReplacer
+
+interface ScheduleReplacerRepository {
+ suspend fun getAll(): MyResult>
+
+ suspend fun setCurrent(
+ fileName: String,
+ fileData: ByteArray,
+ fileType: String
+ ): MyResult
+
+ suspend fun clear(): MyResult
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/data/scheduleReplacer/impl/FakeScheduleReplacerRepository.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/data/scheduleReplacer/impl/FakeScheduleReplacerRepository.kt
new file mode 100644
index 0000000..27aa71e
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/data/scheduleReplacer/impl/FakeScheduleReplacerRepository.kt
@@ -0,0 +1,31 @@
+package ru.n08i40k.polytechnic.next.data.schedule.impl
+
+import ru.n08i40k.polytechnic.next.data.MyResult
+import ru.n08i40k.polytechnic.next.data.scheduleReplacer.ScheduleReplacerRepository
+import ru.n08i40k.polytechnic.next.model.ScheduleReplacer
+
+class FakeScheduleReplacerRepository : ScheduleReplacerRepository {
+ companion object {
+ @Suppress("SpellCheckingInspection")
+ val exampleReplacers: List = listOf(
+ ScheduleReplacer("test-etag", 236 * 1024),
+ ScheduleReplacer("frgsjkfhg", 623 * 1024),
+ )
+ }
+
+ override suspend fun getAll(): MyResult> {
+ return MyResult.Success(exampleReplacers)
+ }
+
+ override suspend fun setCurrent(
+ fileName: String,
+ fileData: ByteArray,
+ fileType: String
+ ): MyResult {
+ return MyResult.Success(Unit)
+ }
+
+ override suspend fun clear(): MyResult {
+ return MyResult.Success(1)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/data/scheduleReplacer/impl/RemoteScheduleReplacerRepository.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/data/scheduleReplacer/impl/RemoteScheduleReplacerRepository.kt
new file mode 100644
index 0000000..6a95dd0
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/data/scheduleReplacer/impl/RemoteScheduleReplacerRepository.kt
@@ -0,0 +1,41 @@
+package ru.n08i40k.polytechnic.next.data.schedule.impl
+
+import android.content.Context
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import ru.n08i40k.polytechnic.next.data.MyResult
+import ru.n08i40k.polytechnic.next.data.scheduleReplacer.ScheduleReplacerRepository
+import ru.n08i40k.polytechnic.next.model.ScheduleReplacer
+import ru.n08i40k.polytechnic.next.network.data.scheduleReplacer.ScheduleReplacerClearReq
+import ru.n08i40k.polytechnic.next.network.data.scheduleReplacer.ScheduleReplacerGetReq
+import ru.n08i40k.polytechnic.next.network.data.scheduleReplacer.ScheduleReplacerSetReq
+import ru.n08i40k.polytechnic.next.network.tryFuture
+
+class RemoteScheduleReplacerRepository(private val context: Context) : ScheduleReplacerRepository {
+ override suspend fun getAll(): MyResult> =
+ withContext(Dispatchers.IO) {
+ tryFuture { ScheduleReplacerGetReq(context, it, it) }
+ }
+
+
+ override suspend fun setCurrent(
+ fileName: String,
+ fileData: ByteArray,
+ fileType: String
+ ): MyResult =
+ withContext(Dispatchers.IO) {
+ tryFuture { ScheduleReplacerSetReq(context, fileName, fileData, fileType, it, it) }
+ }
+
+ override suspend fun clear(): MyResult {
+ val response = withContext(Dispatchers.IO) {
+ tryFuture { ScheduleReplacerClearReq(context, it, it) }
+ }
+
+ return when (response) {
+ is MyResult.Failure -> response
+ is MyResult.Success -> MyResult.Success(response.data.count)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/data/users/impl/RemoteProfileRepository.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/data/users/impl/RemoteProfileRepository.kt
index 53916ec..85108ed 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/data/users/impl/RemoteProfileRepository.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/data/users/impl/RemoteProfileRepository.kt
@@ -10,8 +10,8 @@ import ru.n08i40k.polytechnic.next.network.data.profile.UsersMeRequest
import ru.n08i40k.polytechnic.next.network.tryFuture
class RemoteProfileRepository(private val context: Context) : ProfileRepository {
- override suspend fun getProfile(): MyResult {
- return withContext(Dispatchers.IO) {
+ override suspend fun getProfile(): MyResult =
+ withContext(Dispatchers.IO) {
tryFuture {
UsersMeRequest(
context,
@@ -20,5 +20,4 @@ class RemoteProfileRepository(private val context: Context) : ProfileRepository
)
}
}
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/model/Day.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/model/Day.kt
new file mode 100644
index 0000000..9b32e4a
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/model/Day.kt
@@ -0,0 +1,13 @@
+package ru.n08i40k.polytechnic.next.model
+
+import kotlinx.serialization.Serializable
+
+@Suppress("unused")
+@Serializable
+class Day(
+ val name: String,
+ val nonNullIndices: ArrayList,
+ val defaultIndices: ArrayList,
+ val customIndices: ArrayList,
+ val lessons: ArrayList
+)
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/model/Group.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/model/Group.kt
index 225f4c5..71542de 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/model/Group.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/model/Group.kt
@@ -1,42 +1,6 @@
-@file:Suppress("unused")
-
package ru.n08i40k.polytechnic.next.model
import kotlinx.serialization.Serializable
-import ru.n08i40k.polytechnic.next.utils.EnumAsIntSerializer
-
-@Serializable
-data class LessonTime(val start: Int, val end: Int)
-
-private class LessonTypeIntSerializer : EnumAsIntSerializer(
- "LessonType",
- { it.value },
- { v -> LessonType.entries.first { it.value == v } }
-)
-
-@Serializable(with = LessonTypeIntSerializer::class)
-enum class LessonType(val value: Int) {
- DEFAULT(0), CUSTOM(1)
-}
-
-@Serializable
-data class Lesson(
- val type: LessonType,
- val defaultIndex: Int,
- val name: String,
- val time: LessonTime?,
- val cabinets: ArrayList,
- val teacherNames: ArrayList
-)
-
-@Serializable
-class Day(
- val name: String,
- val nonNullIndices: ArrayList,
- val defaultIndices: ArrayList,
- val customIndices: ArrayList,
- val lessons: ArrayList
-)
@Serializable
class Group(
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/model/Lesson.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/model/Lesson.kt
new file mode 100644
index 0000000..e942679
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/model/Lesson.kt
@@ -0,0 +1,13 @@
+package ru.n08i40k.polytechnic.next.model
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Lesson(
+ val type: LessonType,
+ val defaultIndex: Int,
+ val name: String,
+ val time: LessonTime?,
+ val cabinets: ArrayList,
+ val teacherNames: ArrayList
+)
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/model/LessonTime.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/model/LessonTime.kt
new file mode 100644
index 0000000..0dcb84b
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/model/LessonTime.kt
@@ -0,0 +1,6 @@
+package ru.n08i40k.polytechnic.next.model
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class LessonTime(val start: Int, val end: Int)
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/model/LessonType.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/model/LessonType.kt
new file mode 100644
index 0000000..9af9e4d
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/model/LessonType.kt
@@ -0,0 +1,15 @@
+package ru.n08i40k.polytechnic.next.model
+
+import kotlinx.serialization.Serializable
+import ru.n08i40k.polytechnic.next.utils.EnumAsIntSerializer
+
+private class LessonTypeIntSerializer : EnumAsIntSerializer(
+ "LessonType",
+ { it.value },
+ { v -> LessonType.entries.first { it.value == v } }
+)
+
+@Serializable(with = LessonTypeIntSerializer::class)
+enum class LessonType(val value: Int) {
+ DEFAULT(0), CUSTOM(1)
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/model/Profile.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/model/Profile.kt
index d2b5928..cff0dbf 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/model/Profile.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/model/Profile.kt
@@ -1,29 +1,6 @@
package ru.n08i40k.polytechnic.next.model
-import androidx.annotation.StringRes
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Face
-import androidx.compose.material.icons.filled.Person
-import androidx.compose.material.icons.filled.Settings
-import androidx.compose.ui.graphics.vector.ImageVector
import kotlinx.serialization.Serializable
-import ru.n08i40k.polytechnic.next.R
-import ru.n08i40k.polytechnic.next.utils.EnumAsStringSerializer
-
-private class UserRoleStringSerializer : EnumAsStringSerializer(
- "UserRole",
- { it.value },
- { v -> UserRole.entries.first { it.value == v } }
-)
-
-@Serializable(with = UserRoleStringSerializer::class)
-enum class UserRole(val value: String, val icon: ImageVector, @StringRes val stringId: Int) {
- STUDENT("STUDENT", Icons.Filled.Face, R.string.role_student),
- TEACHER("TEACHER", Icons.Filled.Person, R.string.role_teacher),
- ADMIN("ADMIN", Icons.Filled.Settings, R.string.role_admin)
-}
-
-val AcceptableUserRoles = listOf(UserRole.STUDENT, UserRole.TEACHER)
@Serializable
data class Profile(
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/model/ScheduleReplacer.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/model/ScheduleReplacer.kt
new file mode 100644
index 0000000..0ac8f86
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/model/ScheduleReplacer.kt
@@ -0,0 +1,9 @@
+package ru.n08i40k.polytechnic.next.model
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ScheduleReplacer(
+ val etag: String,
+ val size: Int
+)
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/model/UserRole.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/model/UserRole.kt
new file mode 100644
index 0000000..f25a153
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/model/UserRole.kt
@@ -0,0 +1,29 @@
+package ru.n08i40k.polytechnic.next.model
+
+import androidx.annotation.StringRes
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Face
+import androidx.compose.material.icons.filled.Person
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.ui.graphics.vector.ImageVector
+import kotlinx.serialization.Serializable
+import ru.n08i40k.polytechnic.next.R
+import ru.n08i40k.polytechnic.next.utils.EnumAsStringSerializer
+
+private class UserRoleStringSerializer : EnumAsStringSerializer(
+ "UserRole",
+ { it.value },
+ { v -> UserRole.entries.first { it.value == v } }
+)
+
+
+@Serializable(with = UserRoleStringSerializer::class)
+enum class UserRole(val value: String, val icon: ImageVector, @StringRes val stringId: Int) {
+ STUDENT("STUDENT", Icons.Filled.Face, R.string.role_student),
+ TEACHER("TEACHER", Icons.Filled.Person, R.string.role_teacher),
+ ADMIN("ADMIN", Icons.Filled.Settings, R.string.role_admin);
+
+ companion object {
+ val AcceptableUserRoles = listOf(STUDENT, TEACHER)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/AuthorizedMultipartRequest.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/AuthorizedMultipartRequest.kt
new file mode 100644
index 0000000..4a907a5
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/AuthorizedMultipartRequest.kt
@@ -0,0 +1,130 @@
+package ru.n08i40k.polytechnic.next.network
+
+import android.content.Context
+import com.android.volley.Response
+import ru.n08i40k.polytechnic.next.network.data.AuthorizedRequest
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.DataOutputStream
+import java.io.IOException
+import java.io.UnsupportedEncodingException
+import kotlin.math.min
+
+open class AuthorizedMultipartRequest(
+ context: Context,
+ method: Int,
+ url: String,
+ listener: Response.Listener,
+ errorListener: Response.ErrorListener?,
+ canBeUnauthorized: Boolean = false
+) : AuthorizedRequest(context, method, url, listener, errorListener, canBeUnauthorized) {
+ private val twoHyphens = "--"
+ private val lineEnd = "\r\n"
+ private val boundary = "apiclient-" + System.currentTimeMillis()
+
+ protected open val byteData: Map? get() = null
+
+ override fun getBodyContentType(): String {
+ return "multipart/form-data;boundary=$boundary"
+ }
+
+ override fun getHeaders(): MutableMap {
+ val headers = super.getHeaders()
+ headers["Content-Type"] = bodyContentType
+
+ return headers
+ }
+
+ override fun getBody(): ByteArray {
+ val bos = ByteArrayOutputStream()
+ val dos = DataOutputStream(bos)
+
+ try {
+ val params = params
+ if (!params.isNullOrEmpty()) {
+ textParse(dos, params, paramsEncoding)
+ }
+
+ val data = byteData
+ if (!data.isNullOrEmpty()) {
+ dataParse(dos, data)
+ }
+
+ dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd)
+
+ return bos.toByteArray()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ return ByteArray(0)
+ }
+
+ @Throws(IOException::class)
+ private fun textParse(
+ dataOutputStream: DataOutputStream, params: Map, encoding: String
+ ) {
+ try {
+ for ((key, value) in params) {
+ buildTextPart(dataOutputStream, key, value)
+ }
+ } catch (uee: UnsupportedEncodingException) {
+ throw RuntimeException("Encoding not supported: $encoding", uee)
+ }
+ }
+
+ @Throws(IOException::class)
+ private fun dataParse(dataOutputStream: DataOutputStream, data: Map) {
+ for ((key, value) in data) {
+ buildDataPart(dataOutputStream, value, key)
+ }
+ }
+
+ @Throws(IOException::class)
+ private fun buildTextPart(
+ dataOutputStream: DataOutputStream, parameterName: String, parameterValue: String
+ ) {
+ dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd)
+ dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"$parameterName\"$lineEnd")
+ dataOutputStream.writeBytes(lineEnd)
+ dataOutputStream.writeBytes(parameterValue + lineEnd)
+ }
+
+ @Throws(IOException::class)
+ private fun buildDataPart(
+ dataOutputStream: DataOutputStream, dataFile: DataPart, inputName: String
+ ) {
+ dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd)
+ dataOutputStream.writeBytes(
+ "Content-Disposition: form-data; name=\"" + inputName + "\"; filename=\"" + dataFile.fileName + "\"" + lineEnd
+ )
+
+ if (dataFile.type != null && dataFile.type!!.trim { it <= ' ' }
+ .isNotEmpty()) dataOutputStream.writeBytes("Content-Type: " + dataFile.type + lineEnd)
+
+ dataOutputStream.writeBytes(lineEnd)
+
+ val fileInputStream = ByteArrayInputStream(dataFile.content)
+ var bytesAvailable = fileInputStream.available()
+
+ val maxBufferSize = 1024 * 1024
+ var bufferSize = min(bytesAvailable.toDouble(), maxBufferSize.toDouble()).toInt()
+ val buffer = ByteArray(bufferSize)
+
+ var bytesRead = fileInputStream.read(buffer, 0, bufferSize)
+
+ while (bytesRead > 0) {
+ dataOutputStream.write(buffer, 0, bufferSize)
+ bytesAvailable = fileInputStream.available()
+ bufferSize = min(bytesAvailable.toDouble(), maxBufferSize.toDouble()).toInt()
+ bytesRead = fileInputStream.read(buffer, 0, bufferSize)
+ }
+
+ dataOutputStream.writeBytes(lineEnd)
+ }
+
+ inner class DataPart(name: String?, data: ByteArray, mimeType: String? = null) {
+ var fileName: String? = name
+ var content: ByteArray = data
+ var type: String? = mimeType
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/Request.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/NetworkConnection.kt
similarity index 53%
rename from app/src/main/java/ru/n08i40k/polytechnic/next/network/Request.kt
rename to app/src/main/java/ru/n08i40k/polytechnic/next/network/NetworkConnection.kt
index f296e89..af2e1a1 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/Request.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/NetworkConnection.kt
@@ -4,23 +4,14 @@ import android.annotation.SuppressLint
import android.content.Context
import com.android.volley.Request
import com.android.volley.RequestQueue
-import com.android.volley.Response
-import com.android.volley.VolleyError
import com.android.volley.toolbox.HurlStack
-import com.android.volley.toolbox.RequestFuture
-import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
-import ru.n08i40k.polytechnic.next.data.MyResult
import java.security.cert.X509Certificate
-import java.util.concurrent.ExecutionException
-import java.util.concurrent.TimeoutException
-import java.util.logging.Logger
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager
-
class NetworkConnection(ctx: Context) {
companion object {
@Volatile
@@ -65,45 +56,4 @@ class NetworkConnection(ctx: Context) {
fun addToRequestQueue(req: Request) {
requestQueue.add(req)
}
-}
-
-open class RequestBase(
- protected val context: Context,
- method: Int,
- url: String?,
- listener: Response.Listener,
- errorListener: Response.ErrorListener?
-) : StringRequest(method, NetworkValues.API_HOST + url, listener, errorListener) {
- open fun send() {
- Logger.getLogger("RequestBase").info("Sending request to $url")
- NetworkConnection.getInstance(context).addToRequestQueue(this)
- }
-
- override fun getHeaders(): MutableMap {
- val headers = mutableMapOf()
- headers["Content-Type"] = "application/json; charset=utf-8"
- headers["version"] = "1"
-
- return headers
- }
-}
-
-fun tryFuture(
- buildRequest: (RequestFuture) -> RequestT
-): MyResult {
- val future = RequestFuture.newFuture()
- buildRequest(future).send()
- return tryGet(future)
-}
-
-fun tryGet(future: RequestFuture): MyResult {
- return try {
- MyResult.Success(future.get())
- } catch (exception: VolleyError) {
- MyResult.Failure(exception)
- } catch (exception: ExecutionException) {
- MyResult.Failure(exception.cause as VolleyError)
- } catch (exception: TimeoutException) {
- MyResult.Failure(exception)
- }
}
\ 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
new file mode 100644
index 0000000..d24a638
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/RequestBase.kt
@@ -0,0 +1,27 @@
+package ru.n08i40k.polytechnic.next.network
+
+import android.content.Context
+import com.android.volley.Response
+import com.android.volley.toolbox.StringRequest
+import java.util.logging.Logger
+
+open class RequestBase(
+ protected val context: Context,
+ method: Int,
+ url: String?,
+ listener: Response.Listener,
+ errorListener: Response.ErrorListener?
+) : StringRequest(method, NetworkValues.API_HOST + url, listener, errorListener) {
+ open fun send() {
+ Logger.getLogger("RequestBase").info("Sending request to $url")
+ NetworkConnection.getInstance(context).addToRequestQueue(this)
+ }
+
+ override fun getHeaders(): MutableMap {
+ val headers = mutableMapOf()
+ headers["Content-Type"] = "application/json; charset=utf-8"
+ headers["version"] = "1"
+
+ return headers
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..27d5fdf
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/RequestUtils.kt
@@ -0,0 +1,27 @@
+package ru.n08i40k.polytechnic.next.network
+
+import com.android.volley.VolleyError
+import com.android.volley.toolbox.RequestFuture
+import ru.n08i40k.polytechnic.next.data.MyResult
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeoutException
+
+fun tryFuture(
+ buildRequest: (RequestFuture) -> RequestT
+): MyResult {
+ val future = RequestFuture.newFuture()
+ buildRequest(future).send()
+ return tryGet(future)
+}
+
+fun tryGet(future: RequestFuture): MyResult {
+ return try {
+ MyResult.Success(future.get())
+ } catch (exception: VolleyError) {
+ MyResult.Failure(exception)
+ } catch (exception: ExecutionException) {
+ MyResult.Failure(exception.cause as VolleyError)
+ } catch (exception: TimeoutException) {
+ MyResult.Failure(exception)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/CachedRequest.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/CachedRequest.kt
index 0012fc6..1a60fda 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/CachedRequest.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/CachedRequest.kt
@@ -11,10 +11,10 @@ import ru.n08i40k.polytechnic.next.PolytechnicApplication
import ru.n08i40k.polytechnic.next.data.AppContainer
import ru.n08i40k.polytechnic.next.data.MyResult
import ru.n08i40k.polytechnic.next.network.NetworkConnection
-import ru.n08i40k.polytechnic.next.network.data.schedule.ScheduleGetCacheStatusRequest
-import ru.n08i40k.polytechnic.next.network.data.schedule.ScheduleGetCacheStatusResponse
-import ru.n08i40k.polytechnic.next.network.data.schedule.ScheduleUpdateRequest
-import ru.n08i40k.polytechnic.next.network.data.schedule.ScheduleUpdateRequestData
+import ru.n08i40k.polytechnic.next.network.data.schedule.ScheduleGetCacheStatusReq
+import ru.n08i40k.polytechnic.next.network.data.schedule.ScheduleGetCacheStatusResData
+import ru.n08i40k.polytechnic.next.network.data.schedule.ScheduleUpdateReq
+import ru.n08i40k.polytechnic.next.network.data.schedule.ScheduleUpdateReqData
import ru.n08i40k.polytechnic.next.network.tryFuture
import ru.n08i40k.polytechnic.next.network.tryGet
import java.util.logging.Logger
@@ -58,14 +58,14 @@ open class CachedRequest(
}
}
- private suspend fun updateMainPage(): MyResult {
+ private suspend fun updateMainPage(): MyResult {
return withContext(Dispatchers.IO) {
when (val mainPage = getMainPage()) {
is MyResult.Failure -> mainPage
is MyResult.Success -> {
tryFuture {
- ScheduleUpdateRequest(
- ScheduleUpdateRequestData(mainPage.data),
+ ScheduleUpdateReq(
+ ScheduleUpdateReqData(mainPage.data),
context,
it,
it
@@ -83,7 +83,7 @@ open class CachedRequest(
logger.info("Getting cache status...")
val cacheStatusResult = tryFuture {
- ScheduleGetCacheStatusRequest(context, it, it)
+ ScheduleGetCacheStatusReq(context, it, it)
}
if (cacheStatusResult is MyResult.Success) {
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/auth/Login.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/auth/Login.kt
index c0539bd..ddd1479 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/auth/Login.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/auth/Login.kt
@@ -15,7 +15,7 @@ class LoginRequest(
context,
Method.POST,
"auth/sign-in",
- Response.Listener { response -> listener.onResponse(Json.decodeFromString(response)) },
+ { listener.onResponse(Json.decodeFromString(it)) },
errorListener
) {
override fun getBody(): ByteArray {
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/auth/Register.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/auth/Register.kt
index a48aaad..d82baf8 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/auth/Register.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/auth/Register.kt
@@ -15,7 +15,7 @@ class RegisterRequest(
context,
Method.POST,
"auth/sign-up",
- Response.Listener { response -> listener.onResponse(Json.decodeFromString(response)) },
+ { listener.onResponse(Json.decodeFromString(it)) },
errorListener
) {
override fun getBody(): ByteArray {
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/profile/UsersMe.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/profile/UsersMe.kt
index 175beb2..3c4b83c 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/profile/UsersMe.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/profile/UsersMe.kt
@@ -11,9 +11,9 @@ class UsersMeRequest(
listener: Response.Listener,
errorListener: Response.ErrorListener?
) : AuthorizedRequest(
- context, Method.GET, "users/me", Response.Listener { response ->
- listener.onResponse(
- Json.decodeFromString(response)
- )
- }, errorListener
+ context,
+ Method.GET,
+ "users/me",
+ { listener.onResponse(Json.decodeFromString(it)) },
+ errorListener
)
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetCacheStatusRequest.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetCacheStatusReq.kt
similarity index 51%
rename from app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetCacheStatusRequest.kt
rename to app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetCacheStatusReq.kt
index a6717f1..3b2f592 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetCacheStatusRequest.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetCacheStatusReq.kt
@@ -5,12 +5,14 @@ import com.android.volley.Response
import kotlinx.serialization.json.Json
import ru.n08i40k.polytechnic.next.network.data.AuthorizedRequest
-class ScheduleGetCacheStatusRequest(
+class ScheduleGetCacheStatusReq(
context: Context,
- listener: Response.Listener,
+ listener: Response.Listener,
errorListener: Response.ErrorListener? = null
) : AuthorizedRequest(
- context, Method.GET, "schedule/cache-status", Response.Listener { response ->
- listener.onResponse(Json.decodeFromString(response))
- }, errorListener
+ context,
+ Method.GET,
+ "schedule/cache-status",
+ { listener.onResponse(Json.decodeFromString(it)) },
+ errorListener
)
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetCacheStatusResponse.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetCacheStatusResData.kt
similarity index 85%
rename from app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetCacheStatusResponse.kt
rename to app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetCacheStatusResData.kt
index 6d8de40..741a44c 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetCacheStatusResponse.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetCacheStatusResData.kt
@@ -3,7 +3,7 @@ package ru.n08i40k.polytechnic.next.network.data.schedule
import kotlinx.serialization.Serializable
@Serializable
-data class ScheduleGetCacheStatusResponse(
+data class ScheduleGetCacheStatusResData(
val cacheUpdateRequired: Boolean,
val cacheHash: String,
val lastCacheUpdate: Long,
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetGroupNames.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetGroupNamesReq.kt
similarity index 50%
rename from app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetGroupNames.kt
rename to app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetGroupNamesReq.kt
index 44c4289..a61ba11 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetGroupNames.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetGroupNamesReq.kt
@@ -5,12 +5,14 @@ import com.android.volley.Response
import kotlinx.serialization.json.Json
import ru.n08i40k.polytechnic.next.network.data.CachedRequest
-class ScheduleGetGroupNamesRequest(
+class ScheduleGetGroupNamesReq(
context: Context,
- listener: Response.Listener,
+ listener: Response.Listener,
errorListener: Response.ErrorListener? = null
) : CachedRequest(
- context, Method.GET, "schedule/get-group-names", Response.Listener { response ->
- listener.onResponse(Json.decodeFromString(response))
- }, errorListener
+ context,
+ Method.GET,
+ "schedule/get-group-names",
+ { listener.onResponse(Json.decodeFromString(it)) },
+ errorListener
)
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetGroupNamesResponseData.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetGroupNamesResData.kt
similarity index 76%
rename from app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetGroupNamesResponseData.kt
rename to app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetGroupNamesResData.kt
index 221d698..1ab02f2 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetGroupNamesResponseData.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetGroupNamesResData.kt
@@ -3,6 +3,6 @@ package ru.n08i40k.polytechnic.next.network.data.schedule
import kotlinx.serialization.Serializable
@Serializable
-data class ScheduleGetGroupNamesResponseData(
+data class ScheduleGetGroupNamesResData(
val names: ArrayList,
)
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGet.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetReq.kt
similarity index 59%
rename from app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGet.kt
rename to app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetReq.kt
index b24aabc..03937ee 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGet.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetReq.kt
@@ -6,15 +6,17 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import ru.n08i40k.polytechnic.next.network.data.CachedRequest
-class ScheduleGetRequest(
- private val data: ScheduleGetRequestData,
+class ScheduleGetReq(
+ private val data: ScheduleGetReqData,
context: Context,
- listener: Response.Listener,
+ listener: Response.Listener,
errorListener: Response.ErrorListener? = null
) : CachedRequest(
- context, Method.POST, "schedule/get-group", Response.Listener { response ->
- listener.onResponse(Json.decodeFromString(response))
- }, errorListener
+ context,
+ Method.POST,
+ "schedule/get-group",
+ { listener.onResponse(Json.decodeFromString(it)) },
+ errorListener
) {
override fun getBody(): ByteArray {
return Json.encodeToString(data).toByteArray()
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetRequestData.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetReqData.kt
similarity index 69%
rename from app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetRequestData.kt
rename to app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetReqData.kt
index 2d29190..44af8f1 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetRequestData.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetReqData.kt
@@ -3,4 +3,4 @@ package ru.n08i40k.polytechnic.next.network.data.schedule
import kotlinx.serialization.Serializable
@Serializable
-data class ScheduleGetRequestData(val name: String)
\ No newline at end of file
+data class ScheduleGetReqData(val name: String)
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetResponse.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetResData.kt
similarity index 88%
rename from app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetResponse.kt
rename to app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetResData.kt
index d2885f4..a9ccfe0 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetResponse.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleGetResData.kt
@@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable
import ru.n08i40k.polytechnic.next.model.Group
@Serializable
-data class ScheduleGetResponse(
+data class ScheduleGetResData(
val updatedAt: String,
val group: Group,
val lastChangedDays: ArrayList,
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleUpdate.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleUpdateReq.kt
similarity index 58%
rename from app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleUpdate.kt
rename to app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleUpdateReq.kt
index e4e1942..8e13f7c 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleUpdate.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleUpdateReq.kt
@@ -6,15 +6,17 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import ru.n08i40k.polytechnic.next.network.data.AuthorizedRequest
-class ScheduleUpdateRequest(
- private val data: ScheduleUpdateRequestData,
+class ScheduleUpdateReq(
+ private val data: ScheduleUpdateReqData,
context: Context,
- listener: Response.Listener,
+ listener: Response.Listener,
errorListener: Response.ErrorListener? = null
) : AuthorizedRequest(
- context, Method.POST, "schedule/update-site-main-page", Response.Listener {
- listener.onResponse(Json.decodeFromString(it))
- }, errorListener
+ context,
+ Method.POST,
+ "schedule/update-site-main-page",
+ { listener.onResponse(Json.decodeFromString(it)) },
+ errorListener
) {
override fun getBody(): ByteArray {
return Json.encodeToString(data).toByteArray()
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleUpdateRequestData.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleUpdateReqData.kt
similarity index 66%
rename from app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleUpdateRequestData.kt
rename to app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleUpdateReqData.kt
index 092c41d..a703f70 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleUpdateRequestData.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/schedule/ScheduleUpdateReqData.kt
@@ -3,4 +3,4 @@ package ru.n08i40k.polytechnic.next.network.data.schedule
import kotlinx.serialization.Serializable
@Serializable
-data class ScheduleUpdateRequestData(val mainPage: String)
\ No newline at end of file
+data class ScheduleUpdateReqData(val mainPage: String)
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/scheduleReplacer/ScheduleReplacerClearReq.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/scheduleReplacer/ScheduleReplacerClearReq.kt
new file mode 100644
index 0000000..23b10d0
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/scheduleReplacer/ScheduleReplacerClearReq.kt
@@ -0,0 +1,18 @@
+package ru.n08i40k.polytechnic.next.network.data.scheduleReplacer
+
+import android.content.Context
+import com.android.volley.Response
+import kotlinx.serialization.json.Json
+import ru.n08i40k.polytechnic.next.network.data.AuthorizedRequest
+
+class ScheduleReplacerClearReq(
+ context: Context,
+ listener: Response.Listener,
+ errorListener: Response.ErrorListener?
+) : AuthorizedRequest(
+ context,
+ Method.POST,
+ "schedule-replacer/clear",
+ { listener.onResponse(Json.decodeFromString(it)) },
+ errorListener
+)
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/scheduleReplacer/ScheduleReplacerClearResData.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/scheduleReplacer/ScheduleReplacerClearResData.kt
new file mode 100644
index 0000000..1218ed0
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/scheduleReplacer/ScheduleReplacerClearResData.kt
@@ -0,0 +1,8 @@
+package ru.n08i40k.polytechnic.next.network.data.scheduleReplacer
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ScheduleReplacerClearResData(
+ val count: Int
+)
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/scheduleReplacer/ScheduleReplacerGetReq.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/scheduleReplacer/ScheduleReplacerGetReq.kt
new file mode 100644
index 0000000..7ea030e
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/scheduleReplacer/ScheduleReplacerGetReq.kt
@@ -0,0 +1,18 @@
+package ru.n08i40k.polytechnic.next.network.data.scheduleReplacer
+
+import android.content.Context
+import com.android.volley.Response
+import kotlinx.serialization.json.Json
+import ru.n08i40k.polytechnic.next.network.data.AuthorizedRequest
+
+class ScheduleReplacerGetReq(
+ context: Context,
+ listener: Response.Listener,
+ errorListener: Response.ErrorListener?
+) : AuthorizedRequest(
+ context,
+ Method.GET,
+ "schedule-replacer/get",
+ { listener.onResponse(Json.decodeFromString(it)) },
+ errorListener
+)
\ No newline at end of file
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/scheduleReplacer/ScheduleReplacerGetResData.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/scheduleReplacer/ScheduleReplacerGetResData.kt
new file mode 100644
index 0000000..799e22d
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/scheduleReplacer/ScheduleReplacerGetResData.kt
@@ -0,0 +1,5 @@
+package ru.n08i40k.polytechnic.next.network.data.scheduleReplacer
+
+import ru.n08i40k.polytechnic.next.model.ScheduleReplacer
+
+typealias ScheduleReplacerGetResData = List
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/scheduleReplacer/ScheduleReplacerSetReq.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/scheduleReplacer/ScheduleReplacerSetReq.kt
new file mode 100644
index 0000000..771f372
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/network/data/scheduleReplacer/ScheduleReplacerSetReq.kt
@@ -0,0 +1,28 @@
+package ru.n08i40k.polytechnic.next.network.data.scheduleReplacer
+
+import android.content.Context
+import com.android.volley.Response
+import ru.n08i40k.polytechnic.next.network.AuthorizedMultipartRequest
+
+class ScheduleReplacerSetReq(
+ context: Context,
+ private val fileName: String,
+ private val fileData: ByteArray,
+ private val fileType: String,
+ private val listener: Response.Listener,
+ errorListener: Response.ErrorListener?
+) : AuthorizedMultipartRequest(
+ context,
+ Method.POST,
+ "schedule-replacer/set",
+ { listener.onResponse(null) },
+ errorListener
+) {
+ override val byteData: Map
+ get() = mapOf(
+ Pair(
+ "file",
+ DataPart(fileName, fileData, fileType)
+ )
+ )
+}
\ 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/LoadingContent.kt
index a3e9b2a..33cb13e 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/LoadingContent.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/LoadingContent.kt
@@ -23,15 +23,15 @@ fun LoadingContent(
verticalArrangement: Arrangement.Vertical = Arrangement.Center,
content: @Composable () -> Unit
) {
- if (empty) emptyContent()
- else {
- PullToRefreshBox(
- isRefreshing = loading, onRefresh = onRefresh
- ) {
- LazyColumn(Modifier.fillMaxSize(), verticalArrangement = verticalArrangement) {
- item {
- content()
- }
+ if (empty) {
+ emptyContent()
+ return
+ }
+
+ PullToRefreshBox(isRefreshing = loading, onRefresh = onRefresh) {
+ LazyColumn(Modifier.fillMaxSize(), verticalArrangement = verticalArrangement) {
+ item {
+ content()
}
}
}
@@ -44,7 +44,5 @@ fun FullScreenLoading() {
modifier = Modifier
.fillMaxSize()
.wrapContentSize(Alignment.Center)
- ) {
- CircularProgressIndicator()
- }
+ ) { CircularProgressIndicator() }
}
\ No newline at end of file
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 4f9f7bb..0cea497 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
@@ -57,8 +57,8 @@ 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.AcceptableUserRoles
import ru.n08i40k.polytechnic.next.model.UserRole
+import ru.n08i40k.polytechnic.next.model.UserRole.Companion.AcceptableUserRoles
import ru.n08i40k.polytechnic.next.network.data.auth.LoginRequest
import ru.n08i40k.polytechnic.next.network.data.auth.LoginRequestData
import ru.n08i40k.polytechnic.next.network.data.auth.RegisterRequest
@@ -125,27 +125,29 @@ private fun LoginForm(
Text(text = stringResource(R.string.not_registered))
}
- Button(onClick = {
- if (username.length < 4) usernameError = true
- if (password.isEmpty()) passwordError = true
+ Button(
+ enabled = !mutableIsLoading.value,
+ onClick = {
+ if (username.length < 4) usernameError = true
+ if (password.isEmpty()) passwordError = true
- if (usernameError || passwordError) return@Button
+ if (usernameError || passwordError) return@Button
- tryLogin(
- username,
- password,
- mutableUsernameError,
- mutablePasswordError,
- mutableIsLoading,
- context,
- snackbarHostState,
- scope,
- navController
- )
+ tryLogin(
+ username,
+ password,
+ mutableUsernameError,
+ mutablePasswordError,
+ mutableIsLoading,
+ context,
+ snackbarHostState,
+ scope,
+ navController
+ )
- mutableIsLoading.value = true
- focusManager.clearFocus()
- }) {
+ focusManager.clearFocus()
+ }
+ ) {
Text(
text = stringResource(R.string.login),
style = MaterialTheme.typography.bodyLarge
@@ -253,9 +255,9 @@ private fun RegisterForm(
navController
)
- mutableIsLoading.value = true
focusManager.clearFocus()
- }) {
+ }
+ ) {
Text(
text = stringResource(R.string.register),
style = MaterialTheme.typography.bodyLarge
@@ -364,9 +366,9 @@ fun tryLogin(
) {
var isLoading by mutableIsLoading
- LoginRequest(LoginRequestData(username, password), context, {
- scope.launch { snackbarHostState.showSnackbar("Cool!") }
+ isLoading = true
+ LoginRequest(LoginRequestData(username, password), context, {
runBlocking {
context.settingsDataStore.updateData { currentSettings ->
currentSettings
@@ -378,6 +380,8 @@ fun tryLogin(
}
UsersMeRequest(context, {
+ scope.launch { snackbarHostState.showSnackbar("Cool!") }
+
runBlocking {
context.settingsDataStore.updateData { currentSettings ->
currentSettings
@@ -388,7 +392,7 @@ fun tryLogin(
}
navController.navigate("main")
- }, {}).send()
+ }, null).send()
}, {
isLoading = false
@@ -431,6 +435,8 @@ fun tryRegister(
) {
var isLoading by mutableIsLoading
+ isLoading = true
+
RegisterRequest(
RegisterRequestData(
username,
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/Constants.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/Constants.kt
index 467caf2..feda080 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/Constants.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/Constants.kt
@@ -3,17 +3,22 @@ package ru.n08i40k.polytechnic.next.ui.main
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
+import androidx.compose.material.icons.filled.Create
import androidx.compose.material.icons.filled.DateRange
import androidx.compose.ui.graphics.vector.ImageVector
import ru.n08i40k.polytechnic.next.R
data class BottomNavItem(
- @StringRes val label: Int, val icon: ImageVector, val route: String
+ @StringRes val label: Int,
+ val icon: ImageVector,
+ val route: String,
+ val isAdmin: Boolean = false
)
object Constants {
val bottomNavItem = listOf(
BottomNavItem(R.string.profile, Icons.Filled.AccountCircle, "profile"),
+ BottomNavItem(R.string.replacer, Icons.Filled.Create, "replacer", true),
BottomNavItem(R.string.schedule, Icons.Filled.DateRange, "schedule")
)
}
\ No newline at end of file
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 c1db41e..8ae780d 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
@@ -21,6 +21,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.IntOffset
import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
@@ -31,10 +32,14 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import ru.n08i40k.polytechnic.next.MainViewModel
+import ru.n08i40k.polytechnic.next.model.UserRole
import ru.n08i40k.polytechnic.next.settings.settingsDataStore
import ru.n08i40k.polytechnic.next.ui.main.profile.ProfileScreen
+import ru.n08i40k.polytechnic.next.ui.main.replacer.ReplacerScreen
import ru.n08i40k.polytechnic.next.ui.main.schedule.ScheduleScreen
+import ru.n08i40k.polytechnic.next.ui.model.ProfileUiState
import ru.n08i40k.polytechnic.next.ui.model.ProfileViewModel
+import ru.n08i40k.polytechnic.next.ui.model.ScheduleReplacerViewModel
import ru.n08i40k.polytechnic.next.ui.model.ScheduleViewModel
import ru.n08i40k.polytechnic.next.ui.model.profileViewModel
@@ -43,13 +48,14 @@ import ru.n08i40k.polytechnic.next.ui.model.profileViewModel
private fun NavHostContainer(
navController: NavHostController,
padding: PaddingValues,
- scheduleViewModel: ScheduleViewModel
+ scheduleViewModel: ScheduleViewModel,
+ scheduleReplacerViewModel: ScheduleReplacerViewModel?
) {
val context = LocalContext.current
NavHost(
navController = navController,
- startDestination = Constants.bottomNavItem[1].route,
+ startDestination = "schedule",
modifier = Modifier.padding(paddingValues = padding),
enterTransition = {
slideIn(
@@ -76,27 +82,36 @@ private fun NavHostContainer(
composable("schedule") {
ScheduleScreen(scheduleViewModel) { scheduleViewModel.refreshGroup() }
}
+
+ if (scheduleReplacerViewModel != null) {
+ composable("replacer") {
+ ReplacerScreen(scheduleReplacerViewModel) { scheduleReplacerViewModel.refresh() }
+ }
+ }
})
}
@Composable
-private fun BottomNavBar(navController: NavHostController) {
+private fun BottomNavBar(navController: NavHostController, isAdmin: Boolean) {
NavigationBar {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
- Constants.bottomNavItem.forEach { navItem ->
+ Constants.bottomNavItem.forEach {
+ if (it.isAdmin && !isAdmin)
+ return@forEach
+
NavigationBarItem(
- selected = navItem.route == currentRoute,
- onClick = { if (navItem.route != currentRoute) navController.navigate(navItem.route) },
+ selected = it.route == currentRoute,
+ onClick = { if (it.route != currentRoute) navController.navigate(it.route) },
icon = {
Icon(
- imageVector = navItem.icon,
- contentDescription = stringResource(navItem.label)
+ imageVector = it.icon,
+ contentDescription = stringResource(it.label)
)
},
- label = { Text(stringResource(navItem.label)) })
+ label = { Text(stringResource(it.label)) })
}
}
}
@@ -126,14 +141,23 @@ fun MainScreen(
onUnauthorized = { appNavController.navigate("auth") })
)
+ val profileUiState by LocalContext.current.profileViewModel!!.uiState.collectAsStateWithLifecycle()
+ val isAdmin = (profileUiState is ProfileUiState.HasProfile) &&
+ (profileUiState as ProfileUiState.HasProfile).profile.role == UserRole.ADMIN
+
+ val scheduleReplacerViewModel: ScheduleReplacerViewModel? =
+ if (isAdmin) hiltViewModel(LocalContext.current as ComponentActivity)
+ else null
+
val navController = rememberNavController()
Scaffold(
- bottomBar = { BottomNavBar(navController = navController) }
+ bottomBar = { BottomNavBar(navController, isAdmin) }
) { paddingValues ->
NavHostContainer(
navController,
paddingValues,
- scheduleViewModel
+ scheduleViewModel,
+ scheduleReplacerViewModel
)
}
}
\ No newline at end of file
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 b53c4bb..e0af3d9 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
@@ -37,7 +37,7 @@ import ru.n08i40k.polytechnic.next.data.users.impl.FakeProfileRepository
import ru.n08i40k.polytechnic.next.model.Profile
import ru.n08i40k.polytechnic.next.network.data.profile.ChangeGroupRequest
import ru.n08i40k.polytechnic.next.network.data.profile.ChangeGroupRequestData
-import ru.n08i40k.polytechnic.next.network.data.schedule.ScheduleGetGroupNamesRequest
+import ru.n08i40k.polytechnic.next.network.data.schedule.ScheduleGetGroupNamesReq
private enum class ChangeGroupError {
NOT_EXISTS
@@ -65,7 +65,7 @@ private fun getGroups(context: Context): ArrayList {
val groups = remember { arrayListOf(groupPlaceholder) }
LaunchedEffect(groups) {
- ScheduleGetGroupNamesRequest(context, {
+ ScheduleGetGroupNamesReq(context, {
groups.clear()
groups.addAll(it.names)
}, {
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
new file mode 100644
index 0000000..d00f03a
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/main/replacer/ReplacerScreen.kt
@@ -0,0 +1,245 @@
+package ru.n08i40k.polytechnic.next.ui.main.replacer
+
+import android.net.Uri
+import android.provider.OpenableColumns
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+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.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import ru.n08i40k.polytechnic.next.R
+import ru.n08i40k.polytechnic.next.data.MockAppContainer
+import ru.n08i40k.polytechnic.next.data.schedule.impl.FakeScheduleReplacerRepository
+import ru.n08i40k.polytechnic.next.model.ScheduleReplacer
+import ru.n08i40k.polytechnic.next.ui.LoadingContent
+import ru.n08i40k.polytechnic.next.ui.model.ScheduleReplacerUiState
+import ru.n08i40k.polytechnic.next.ui.model.ScheduleReplacerViewModel
+
+@Preview(showBackground = true, showSystemUi = true)
+@Composable
+fun ReplacerScreen(
+ scheduleReplacerViewModel: ScheduleReplacerViewModel = ScheduleReplacerViewModel(
+ MockAppContainer(
+ LocalContext.current
+ )
+ ),
+ refresh: () -> Unit = {}
+) {
+ val uiState by scheduleReplacerViewModel.uiState.collectAsStateWithLifecycle()
+
+ var uri by remember { mutableStateOf(null) }
+ val launcher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
+ uri = it
+ }
+
+ UploadFile(scheduleReplacerViewModel, uri) { uri = null }
+
+ LoadingContent(
+ empty = when (uiState) {
+ is ScheduleReplacerUiState.NoData -> uiState.isLoading
+ is ScheduleReplacerUiState.HasData -> false
+ },
+ loading = uiState.isLoading,
+ onRefresh = refresh,
+ verticalArrangement = Arrangement.Top,
+ content = {
+ when (uiState) {
+ is ScheduleReplacerUiState.NoData -> {
+ if (!uiState.isLoading) {
+ TextButton(onClick = refresh, modifier = Modifier.fillMaxSize()) {
+ Text(stringResource(R.string.reload), textAlign = TextAlign.Center)
+ }
+ }
+ }
+
+ is ScheduleReplacerUiState.HasData -> {
+ Column {
+ Row(modifier = Modifier.fillMaxWidth()) {
+ ClearButton(Modifier.fillMaxWidth(0.5F)) {
+ scheduleReplacerViewModel.clear()
+ }
+
+ SetNewButton(Modifier.fillMaxWidth()) {
+ launcher.launch(arrayOf("application/vnd.ms-excel"))
+ }
+ }
+
+ ReplacerList((uiState as ScheduleReplacerUiState.HasData).replacers)
+ }
+ }
+ }
+ }
+ )
+}
+
+@Composable
+fun UploadFile(
+ scheduleReplacerViewModel: ScheduleReplacerViewModel,
+ uri: Uri?,
+ onFinish: () -> Unit
+) {
+ if (uri == null)
+ return
+
+ val context = LocalContext.current
+ val contentResolver = context.contentResolver
+
+ // get file name
+ val query = contentResolver.query(uri, null, null, null, null)
+ if (query == null) {
+ onFinish()
+ return
+ }
+
+ val fileName = query.use { cursor ->
+ val nameIdx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
+ cursor.moveToFirst()
+
+ cursor.getString(nameIdx)
+ }
+
+ // get file type
+ val fileType: String? = contentResolver.getType(uri)
+ if (fileType == null) {
+ onFinish()
+ return
+ }
+
+ // get file data
+ val inputStream = contentResolver.openInputStream(uri)
+ if (inputStream == null) {
+ onFinish()
+ return
+ }
+
+ val fileData = inputStream.readBytes()
+
+ inputStream.close()
+
+ scheduleReplacerViewModel.set(fileName, fileData, fileType)
+ onFinish()
+}
+
+//@Preview(showBackground = true)
+//@Composable
+//private fun UploadFileDialog(
+// opened: Boolean = true,
+// onClose: () -> Unit = {}
+//) {
+// Dialog(onDismissRequest = onClose) {
+// Card {
+// Button
+// }
+// }
+//}
+
+@Preview(showBackground = true)
+@Composable
+private fun SetNewButton(modifier: Modifier = Modifier, onClick: () -> Unit = {}) {
+ Button(modifier = modifier, onClick = onClick) {
+ val setReplacerText = stringResource(R.string.set_replacer)
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(0.dp, 5.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Icon(imageVector = Icons.Filled.Add, contentDescription = setReplacerText)
+ Text(text = setReplacerText)
+ Icon(imageVector = Icons.Filled.Add, contentDescription = setReplacerText)
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun ClearButton(modifier: Modifier = Modifier, onClick: () -> Unit = {}) {
+ Button(modifier = modifier, onClick = onClick) {
+ val clearReplacersText = stringResource(R.string.clear_replacers)
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(0.dp, 5.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Icon(imageVector = Icons.Filled.Delete, contentDescription = clearReplacersText)
+ Text(text = clearReplacersText)
+ Icon(imageVector = Icons.Filled.Delete, contentDescription = clearReplacersText)
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun ReplacerElement(replacer: ScheduleReplacer = FakeScheduleReplacerRepository.exampleReplacers[0]) {
+ Column(
+ modifier = Modifier.border(
+ BorderStroke(
+ Dp.Hairline,
+ MaterialTheme.colorScheme.inverseSurface
+ )
+ )
+ ) {
+ val modifier = Modifier.fillMaxWidth()
+
+ Text(modifier = modifier, textAlign = TextAlign.Center, text = replacer.etag)
+ Text(modifier = modifier, textAlign = TextAlign.Center, text = buildString {
+ append(replacer.size)
+ append(" ")
+ append(stringResource(R.string.bytes))
+ })
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun ReplacerList(replacers: List = FakeScheduleReplacerRepository.exampleReplacers) {
+ Surface {
+ LazyColumn(
+ contentPadding = PaddingValues(0.dp, 5.dp),
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(500.dp)
+ ) {
+ items(replacers) {
+ ReplacerElement(it)
+ }
+ }
+ }
+}
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 d1a41a3..3e0c0af 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
@@ -16,7 +16,7 @@ import ru.n08i40k.polytechnic.next.model.Group
import java.util.Calendar
import kotlin.math.absoluteValue
-@Preview(showBackground = true, showSystemUi = true)
+@Preview
@Composable
fun DayPager(group: Group = FakeScheduleRepository.exampleGroup) {
val currentDay = (Calendar.getInstance().get(Calendar.DAY_OF_WEEK) - 2)
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/model/ScheduleReplacerViewModel.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/model/ScheduleReplacerViewModel.kt
new file mode 100644
index 0000000..6ad8245
--- /dev/null
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/model/ScheduleReplacerViewModel.kt
@@ -0,0 +1,115 @@
+package ru.n08i40k.polytechnic.next.ui.model
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import ru.n08i40k.polytechnic.next.data.AppContainer
+import ru.n08i40k.polytechnic.next.data.MyResult
+import ru.n08i40k.polytechnic.next.model.ScheduleReplacer
+import javax.inject.Inject
+
+sealed interface ScheduleReplacerUiState {
+ val isLoading: Boolean
+
+ data class NoData(
+ override val isLoading: Boolean,
+ ) : ScheduleReplacerUiState
+
+ data class HasData(
+ override val isLoading: Boolean,
+ val replacers: List,
+ ) : ScheduleReplacerUiState
+}
+
+private data class ScheduleReplacerViewModelState(
+ val isLoading: Boolean = false,
+ val replacers: List? = null,
+) {
+ fun toUiState(): ScheduleReplacerUiState =
+ if (replacers == null)
+ ScheduleReplacerUiState.NoData(isLoading)
+ else
+ ScheduleReplacerUiState.HasData(isLoading, replacers)
+}
+
+@HiltViewModel
+class ScheduleReplacerViewModel @Inject constructor(
+ appContainer: AppContainer
+) : ViewModel() {
+ private val scheduleReplacerRepository = appContainer.scheduleReplacerRepository
+ private val viewModelState = MutableStateFlow(ScheduleReplacerViewModelState(isLoading = true))
+
+ val uiState = viewModelState
+ .map(ScheduleReplacerViewModelState::toUiState)
+ .stateIn(viewModelScope, SharingStarted.Eagerly, viewModelState.value.toUiState())
+
+ init {
+ refresh()
+ }
+
+ fun refresh() {
+ setLoading()
+
+ viewModelScope.launch { update() }
+ }
+
+ fun set(
+ fileName: String,
+ fileData: ByteArray,
+ fileType: String
+ ) {
+ setLoading()
+
+ viewModelScope.launch {
+ val result = scheduleReplacerRepository.setCurrent(fileName, fileData, fileType)
+
+ if (result is MyResult.Success) update()
+ else setLoading(false)
+ }
+ }
+
+ fun clear() {
+ setLoading()
+
+ viewModelScope.launch {
+ val result = scheduleReplacerRepository.clear()
+
+ viewModelState.update {
+ when (result) {
+ is MyResult.Failure -> it.copy(isLoading = false)
+ is MyResult.Success -> it.copy(isLoading = false, replacers = emptyList())
+ }
+ }
+ }
+ }
+
+ private fun setLoading(loading: Boolean = true) {
+ viewModelState.update { it.copy(isLoading = loading) }
+ }
+
+ private suspend fun update() {
+ val result = scheduleReplacerRepository.getAll()
+
+ viewModelState.update {
+ when (result) {
+ is MyResult.Success -> {
+ it.copy(
+ replacers = result.data,
+ isLoading = false
+ )
+ }
+
+ is MyResult.Failure -> it.copy(
+ replacers = null,
+ isLoading = false
+ )
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/model/ScheduleViewModel.kt b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/model/ScheduleViewModel.kt
index 7e45e7c..c3e73b4 100644
--- a/app/src/main/java/ru/n08i40k/polytechnic/next/ui/model/ScheduleViewModel.kt
+++ b/app/src/main/java/ru/n08i40k/polytechnic/next/ui/model/ScheduleViewModel.kt
@@ -1,6 +1,6 @@
package ru.n08i40k.polytechnic.next.ui.model
-import androidx.lifecycle.ViewModel
+кimport androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -14,7 +14,6 @@ import ru.n08i40k.polytechnic.next.data.AppContainer
import ru.n08i40k.polytechnic.next.data.MyResult
import ru.n08i40k.polytechnic.next.model.Group
import java.util.Date
-import java.util.logging.Logger
import javax.inject.Inject
sealed interface ScheduleUiState {
@@ -72,8 +71,6 @@ class ScheduleViewModel @Inject constructor(
is MyResult.Success -> {
val updateDates = networkCacheRepository.getUpdateDates()
- Logger.getLogger("ScheduleViewModel").info("Updating...")
-
it.copy(
group = result.data,
updateDates = updateDates,
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index e72358c..1d5faa3 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -36,4 +36,8 @@
Последнее обновление кеша
Последнее обновление расписания
Дополнительная информация
+ Заменитель
+ байт
+ Удалить всё
+ Загрузить новое расписание
\ 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 9acfb9c..8ed35e0 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -36,4 +36,8 @@
Last server cache update
Last server schedule update
Additional information
+ Replacer
+ bytes
+ Clear
+ Set new
\ No newline at end of file