mirror of
https://github.com/n08i40k/polytechnic-android.git
synced 2025-12-06 09:47:48 +03:00
1.3.0
Верии ниже этой больше не совместимы (т.е не работают). Уменьшение размера приложения. - Существенное) Адекватное кеширование. - Улучшено кеширование на стороне сервера (до этого сервер не выставлял флаг отвечающий за потребность в обновлении). - Добавлено кеширование ответов от сервера в хранилище приложения.
This commit is contained in:
9
.idea/appInsightsSettings.xml
generated
9
.idea/appInsightsSettings.xml
generated
@@ -17,15 +17,6 @@
|
|||||||
</option>
|
</option>
|
||||||
<option name="signal" value="SIGNAL_UNSPECIFIED" />
|
<option name="signal" value="SIGNAL_UNSPECIFIED" />
|
||||||
<option name="timeIntervalDays" value="THIRTY_DAYS" />
|
<option name="timeIntervalDays" value="THIRTY_DAYS" />
|
||||||
<option name="versions">
|
|
||||||
<list>
|
|
||||||
<VersionSetting>
|
|
||||||
<option name="buildVersion" value="2" />
|
|
||||||
<option name="displayName" value="1.1 (2)" />
|
|
||||||
<option name="displayVersion" value="1.1" />
|
|
||||||
</VersionSetting>
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
<option name="visibilityType" value="ALL" />
|
<option name="visibilityType" value="ALL" />
|
||||||
</InsightsFilterSettings>
|
</InsightsFilterSettings>
|
||||||
</value>
|
</value>
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ android {
|
|||||||
applicationId = "ru.n08i40k.polytechnic.next"
|
applicationId = "ru.n08i40k.polytechnic.next"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 5
|
versionCode = 6
|
||||||
versionName = "1.2.2"
|
versionName = "1.3.0"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
@@ -43,7 +43,7 @@ android {
|
|||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = true
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
|
|||||||
4
app/proguard-rules.pro
vendored
4
app/proguard-rules.pro
vendored
@@ -14,8 +14,8 @@
|
|||||||
|
|
||||||
# Uncomment this to preserve the line number information for
|
# Uncomment this to preserve the line number information for
|
||||||
# debugging stack traces.
|
# debugging stack traces.
|
||||||
#-keepattributes SourceFile,LineNumberTable
|
-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
# If you keep the line number information, uncomment this to
|
||||||
# hide the original source file name.
|
# hide the original source file name.
|
||||||
#-renamesourcefileattribute SourceFile
|
-renamesourcefileattribute SourceFile
|
||||||
@@ -6,6 +6,9 @@ import dagger.Module
|
|||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
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.ScheduleRepository
|
||||||
import ru.n08i40k.polytechnic.next.data.schedule.impl.FakeScheduleRepository
|
import ru.n08i40k.polytechnic.next.data.schedule.impl.FakeScheduleRepository
|
||||||
import ru.n08i40k.polytechnic.next.data.schedule.impl.RemoteScheduleRepository
|
import ru.n08i40k.polytechnic.next.data.schedule.impl.RemoteScheduleRepository
|
||||||
@@ -15,16 +18,24 @@ import ru.n08i40k.polytechnic.next.data.users.impl.RemoteProfileRepository
|
|||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
interface AppContainer {
|
interface AppContainer {
|
||||||
|
val applicationContext: Context
|
||||||
|
val networkCacheRepository: NetworkCacheRepository
|
||||||
val scheduleRepository: ScheduleRepository
|
val scheduleRepository: ScheduleRepository
|
||||||
val profileRepository: ProfileRepository
|
val profileRepository: ProfileRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockAppContainer : AppContainer {
|
class MockAppContainer(override val applicationContext: Context) : AppContainer {
|
||||||
|
override val networkCacheRepository: NetworkCacheRepository by lazy { FakeNetworkCacheRepository() }
|
||||||
override val scheduleRepository: ScheduleRepository by lazy { FakeScheduleRepository() }
|
override val scheduleRepository: ScheduleRepository by lazy { FakeScheduleRepository() }
|
||||||
override val profileRepository: ProfileRepository by lazy { FakeProfileRepository() }
|
override val profileRepository: ProfileRepository by lazy { FakeProfileRepository() }
|
||||||
}
|
}
|
||||||
|
|
||||||
class RemoteAppContainer(private val applicationContext: Context) : AppContainer {
|
class RemoteAppContainer(override val applicationContext: Context) : AppContainer {
|
||||||
|
override val networkCacheRepository: NetworkCacheRepository by lazy {
|
||||||
|
LocalNetworkCacheRepository(
|
||||||
|
applicationContext
|
||||||
|
)
|
||||||
|
}
|
||||||
override val scheduleRepository: ScheduleRepository by lazy {
|
override val scheduleRepository: ScheduleRepository by lazy {
|
||||||
RemoteScheduleRepository(
|
RemoteScheduleRepository(
|
||||||
applicationContext
|
applicationContext
|
||||||
|
|||||||
15
app/src/main/java/ru/n08i40k/polytechnic/next/data/cache/NetworkCacheRepository.kt
vendored
Normal file
15
app/src/main/java/ru/n08i40k/polytechnic/next/data/cache/NetworkCacheRepository.kt
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package ru.n08i40k.polytechnic.next.data.cache
|
||||||
|
|
||||||
|
import ru.n08i40k.polytechnic.next.CachedResponse
|
||||||
|
|
||||||
|
interface NetworkCacheRepository {
|
||||||
|
suspend fun put(url: String, data: String)
|
||||||
|
|
||||||
|
suspend fun get(url: String): CachedResponse?
|
||||||
|
|
||||||
|
suspend fun clear()
|
||||||
|
|
||||||
|
suspend fun isHashPresent(): Boolean
|
||||||
|
|
||||||
|
suspend fun setHash(hash: String)
|
||||||
|
}
|
||||||
20
app/src/main/java/ru/n08i40k/polytechnic/next/data/cache/impl/FakeNetworkCacheRepository.kt
vendored
Normal file
20
app/src/main/java/ru/n08i40k/polytechnic/next/data/cache/impl/FakeNetworkCacheRepository.kt
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package ru.n08i40k.polytechnic.next.data.cache.impl
|
||||||
|
|
||||||
|
import ru.n08i40k.polytechnic.next.CachedResponse
|
||||||
|
import ru.n08i40k.polytechnic.next.data.cache.NetworkCacheRepository
|
||||||
|
|
||||||
|
class FakeNetworkCacheRepository : NetworkCacheRepository {
|
||||||
|
override suspend fun get(url: String): CachedResponse? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun put(url: String, data: String) {}
|
||||||
|
|
||||||
|
override suspend fun clear() {}
|
||||||
|
|
||||||
|
override suspend fun isHashPresent(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setHash(hash: String) {}
|
||||||
|
}
|
||||||
93
app/src/main/java/ru/n08i40k/polytechnic/next/data/cache/impl/LocalNetworkCacheRepository.kt
vendored
Normal file
93
app/src/main/java/ru/n08i40k/polytechnic/next/data/cache/impl/LocalNetworkCacheRepository.kt
vendored
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package ru.n08i40k.polytechnic.next.data.cache.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.CachedResponse
|
||||||
|
import ru.n08i40k.polytechnic.next.data.cache.NetworkCacheRepository
|
||||||
|
import ru.n08i40k.polytechnic.next.settings.settingsDataStore
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class LocalNetworkCacheRepository
|
||||||
|
@Inject constructor(private val applicationContext: Context) : NetworkCacheRepository {
|
||||||
|
private val cacheMap: MutableMap<String, CachedResponse> = mutableMapOf()
|
||||||
|
private var hash: String? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
cacheMap.clear()
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
cacheMap.putAll(
|
||||||
|
applicationContext
|
||||||
|
.settingsDataStore
|
||||||
|
.data
|
||||||
|
.map { settings -> settings.cacheStorageMap }.first()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun get(url: String): CachedResponse? {
|
||||||
|
if (this.hash == null)
|
||||||
|
return null
|
||||||
|
|
||||||
|
val response = cacheMap[url]
|
||||||
|
|
||||||
|
if (response?.hash != this.hash)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun put(url: String, data: String) {
|
||||||
|
if (hash == null)
|
||||||
|
throw IllegalStateException("Не установлен хеш!")
|
||||||
|
|
||||||
|
cacheMap[url] = CachedResponse
|
||||||
|
.newBuilder()
|
||||||
|
.setHash(this.hash)
|
||||||
|
.setData(data)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clear() {
|
||||||
|
this.cacheMap.clear()
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun isHashPresent(): Boolean {
|
||||||
|
return this.hash != null
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setHash(hash: String) {
|
||||||
|
val freshHash = this.hash == null
|
||||||
|
|
||||||
|
if (!freshHash && this.hash != hash)
|
||||||
|
clear()
|
||||||
|
|
||||||
|
this.hash = hash
|
||||||
|
|
||||||
|
if (freshHash) {
|
||||||
|
this.cacheMap
|
||||||
|
.mapNotNull { if (it.value.hash != this.hash) it.key else null }
|
||||||
|
.forEach { this.cacheMap.remove(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun save() {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
runBlocking {
|
||||||
|
applicationContext.settingsDataStore.updateData {
|
||||||
|
it
|
||||||
|
.toBuilder()
|
||||||
|
.putAllCacheStorage(cacheMap)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
package ru.n08i40k.polytechnic.next.data.schedule.impl
|
package ru.n08i40k.polytechnic.next.data.schedule.impl
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.android.volley.Request
|
|
||||||
import com.android.volley.ServerError
|
|
||||||
import com.android.volley.toolbox.RequestFuture
|
import com.android.volley.toolbox.RequestFuture
|
||||||
import com.android.volley.toolbox.StringRequest
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@@ -13,67 +10,16 @@ import kotlinx.coroutines.withContext
|
|||||||
import ru.n08i40k.polytechnic.next.data.MyResult
|
import ru.n08i40k.polytechnic.next.data.MyResult
|
||||||
import ru.n08i40k.polytechnic.next.data.schedule.ScheduleRepository
|
import ru.n08i40k.polytechnic.next.data.schedule.ScheduleRepository
|
||||||
import ru.n08i40k.polytechnic.next.model.Group
|
import ru.n08i40k.polytechnic.next.model.Group
|
||||||
import ru.n08i40k.polytechnic.next.network.NetworkConnection
|
|
||||||
import ru.n08i40k.polytechnic.next.network.data.schedule.ScheduleGetRequest
|
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.ScheduleGetRequestData
|
||||||
import ru.n08i40k.polytechnic.next.network.data.schedule.ScheduleGetResponse
|
import ru.n08i40k.polytechnic.next.network.data.schedule.ScheduleGetResponse
|
||||||
import ru.n08i40k.polytechnic.next.network.data.schedule.ScheduleUpdateRequest
|
|
||||||
import ru.n08i40k.polytechnic.next.network.data.schedule.ScheduleUpdateRequestData
|
|
||||||
import ru.n08i40k.polytechnic.next.settings.settingsDataStore
|
import ru.n08i40k.polytechnic.next.settings.settingsDataStore
|
||||||
import java.util.logging.Logger
|
|
||||||
import kotlin.io.encoding.Base64
|
|
||||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
|
||||||
|
|
||||||
class RemoteScheduleRepository(private val context: Context) : ScheduleRepository {
|
class RemoteScheduleRepository(private val context: Context) : ScheduleRepository {
|
||||||
@OptIn(ExperimentalEncodingApi::class)
|
|
||||||
suspend fun getMainPage(): MyResult<String> {
|
|
||||||
return withContext(Dispatchers.IO) {
|
|
||||||
val mainPageFuture = RequestFuture.newFuture<String>()
|
|
||||||
val request = StringRequest(
|
|
||||||
Request.Method.GET,
|
|
||||||
"https://politehnikum-eng.ru/index/raspisanie_zanjatij/0-409",
|
|
||||||
mainPageFuture,
|
|
||||||
mainPageFuture
|
|
||||||
)
|
|
||||||
NetworkConnection.getInstance(context).addToRequestQueue(request)
|
|
||||||
|
|
||||||
try {
|
|
||||||
val encodedMainPage =
|
|
||||||
Base64.Default.encode(mainPageFuture.get().encodeToByteArray())
|
|
||||||
MyResult.Success(encodedMainPage)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
MyResult.Failure(exception)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun updateMainPage(): MyResult<Nothing> {
|
|
||||||
return withContext(Dispatchers.IO) {
|
|
||||||
val mainPage = getMainPage()
|
|
||||||
|
|
||||||
if (mainPage is MyResult.Failure)
|
|
||||||
return@withContext mainPage
|
|
||||||
|
|
||||||
val updateFuture = RequestFuture.newFuture<Nothing>()
|
|
||||||
ScheduleUpdateRequest(
|
|
||||||
ScheduleUpdateRequestData((mainPage as MyResult.Success<String>).data),
|
|
||||||
context,
|
|
||||||
updateFuture,
|
|
||||||
updateFuture
|
|
||||||
).send()
|
|
||||||
|
|
||||||
try {
|
|
||||||
MyResult.Success(updateFuture.get())
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
MyResult.Failure(exception)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getGroup(): MyResult<Group> {
|
override suspend fun getGroup(): MyResult<Group> {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
val logger = Logger.getLogger("RemoteScheduleRepository")
|
|
||||||
|
|
||||||
val groupName = runBlocking {
|
val groupName = runBlocking {
|
||||||
context.settingsDataStore.data.map { settings -> settings.group }.first()
|
context.settingsDataStore.data.map { settings -> settings.group }.first()
|
||||||
}
|
}
|
||||||
@@ -81,49 +27,16 @@ class RemoteScheduleRepository(private val context: Context) : ScheduleRepositor
|
|||||||
if (groupName.isEmpty())
|
if (groupName.isEmpty())
|
||||||
return@withContext MyResult.Failure(IllegalArgumentException("No group name provided!"))
|
return@withContext MyResult.Failure(IllegalArgumentException("No group name provided!"))
|
||||||
|
|
||||||
val firstPassFuture = RequestFuture.newFuture<ScheduleGetResponse>()
|
val future = RequestFuture.newFuture<ScheduleGetResponse>()
|
||||||
ScheduleGetRequest(
|
ScheduleGetRequest(
|
||||||
ScheduleGetRequestData(groupName),
|
ScheduleGetRequestData(groupName),
|
||||||
context,
|
context,
|
||||||
firstPassFuture,
|
future,
|
||||||
firstPassFuture
|
future
|
||||||
).send()
|
|
||||||
|
|
||||||
var firstPassResponse: ScheduleGetResponse? = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
firstPassResponse = firstPassFuture.get()
|
|
||||||
if (!firstPassResponse.updateRequired) {
|
|
||||||
logger.info("Successfully get group schedule!")
|
|
||||||
return@withContext MyResult.Success(firstPassFuture.get().group)
|
|
||||||
}
|
|
||||||
logger.info("Successfully get group schedule, but it needs to update!")
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
if (exception.cause !is ServerError)
|
|
||||||
return@withContext MyResult.Failure(exception)
|
|
||||||
logger.info("Can't get group schedule, because it needs to first update!")
|
|
||||||
}
|
|
||||||
|
|
||||||
val updateResult = updateMainPage()
|
|
||||||
if (updateResult is MyResult.Failure) {
|
|
||||||
logger.info("Can't update site main page!")
|
|
||||||
if (firstPassResponse != null)
|
|
||||||
return@withContext MyResult.Success(firstPassResponse.group)
|
|
||||||
|
|
||||||
return@withContext updateResult
|
|
||||||
}
|
|
||||||
logger.info("Site main page successfully updated!")
|
|
||||||
|
|
||||||
val secondPassFuture = RequestFuture.newFuture<ScheduleGetResponse>()
|
|
||||||
ScheduleGetRequest(
|
|
||||||
ScheduleGetRequestData(groupName),
|
|
||||||
context,
|
|
||||||
secondPassFuture,
|
|
||||||
secondPassFuture
|
|
||||||
).send()
|
).send()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
MyResult.Success(secondPassFuture.get().group)
|
MyResult.Success(future.get().group)
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
MyResult.Failure(exception)
|
MyResult.Failure(exception)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ open class RequestBase(
|
|||||||
listener: Response.Listener<String>,
|
listener: Response.Listener<String>,
|
||||||
errorListener: Response.ErrorListener?
|
errorListener: Response.ErrorListener?
|
||||||
) : StringRequest(method, NetworkValues.API_HOST + url, listener, errorListener) {
|
) : StringRequest(method, NetworkValues.API_HOST + url, listener, errorListener) {
|
||||||
fun send() {
|
open fun send() {
|
||||||
Logger.getLogger("RequestBase").info("Sending request to $url")
|
Logger.getLogger("RequestBase").info("Sending request to $url")
|
||||||
NetworkConnection.getInstance(context).addToRequestQueue(this)
|
NetworkConnection.getInstance(context).addToRequestQueue(this)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import ru.n08i40k.polytechnic.next.ui.model.profileViewModel
|
|||||||
open class AuthorizedRequest(
|
open class AuthorizedRequest(
|
||||||
context: Context,
|
context: Context,
|
||||||
method: Int,
|
method: Int,
|
||||||
url: String?,
|
url: String,
|
||||||
listener: Response.Listener<String>,
|
listener: Response.Listener<String>,
|
||||||
errorListener: Response.ErrorListener?,
|
errorListener: Response.ErrorListener?,
|
||||||
private val canBeUnauthorized: Boolean = false
|
private val canBeUnauthorized: Boolean = false
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package ru.n08i40k.polytechnic.next.network.data
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.android.volley.Response
|
||||||
|
import com.android.volley.VolleyError
|
||||||
|
import com.android.volley.toolbox.RequestFuture
|
||||||
|
import com.android.volley.toolbox.StringRequest
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
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 java.util.logging.Logger
|
||||||
|
import kotlin.io.encoding.Base64
|
||||||
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
|
|
||||||
|
open class CachedRequest(
|
||||||
|
context: Context,
|
||||||
|
method: Int,
|
||||||
|
private val url: String,
|
||||||
|
private val listener: Response.Listener<String>,
|
||||||
|
errorListener: Response.ErrorListener?,
|
||||||
|
) : AuthorizedRequest(context, method, url, {
|
||||||
|
runBlocking {
|
||||||
|
(context as PolytechnicApplication)
|
||||||
|
.container.networkCacheRepository.put(url, it)
|
||||||
|
}
|
||||||
|
listener.onResponse(it)
|
||||||
|
}, errorListener) {
|
||||||
|
private val appContainer: AppContainer = (context as PolytechnicApplication).container
|
||||||
|
|
||||||
|
@OptIn(ExperimentalEncodingApi::class)
|
||||||
|
suspend fun getMainPage(): MyResult<String> {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val mainPageFuture = RequestFuture.newFuture<String>()
|
||||||
|
val request = StringRequest(
|
||||||
|
Method.GET,
|
||||||
|
"https://politehnikum-eng.ru/index/raspisanie_zanjatij/0-409",
|
||||||
|
mainPageFuture,
|
||||||
|
mainPageFuture
|
||||||
|
)
|
||||||
|
NetworkConnection.getInstance(context).addToRequestQueue(request)
|
||||||
|
|
||||||
|
try {
|
||||||
|
val encodedMainPage =
|
||||||
|
Base64.Default.encode(mainPageFuture.get().encodeToByteArray())
|
||||||
|
MyResult.Success(encodedMainPage)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
MyResult.Failure(exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun updateMainPage(): MyResult<ScheduleGetCacheStatusResponse> {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val mainPage = getMainPage()
|
||||||
|
|
||||||
|
if (mainPage is MyResult.Failure)
|
||||||
|
return@withContext mainPage
|
||||||
|
|
||||||
|
val updateFuture = RequestFuture.newFuture<ScheduleGetCacheStatusResponse>()
|
||||||
|
ScheduleUpdateRequest(
|
||||||
|
ScheduleUpdateRequestData((mainPage as MyResult.Success<String>).data),
|
||||||
|
context,
|
||||||
|
updateFuture,
|
||||||
|
updateFuture
|
||||||
|
).send()
|
||||||
|
|
||||||
|
try {
|
||||||
|
MyResult.Success(updateFuture.get())
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
MyResult.Failure(exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun send() {
|
||||||
|
val logger = Logger.getLogger("CachedRequest")
|
||||||
|
|
||||||
|
val repository = appContainer.networkCacheRepository
|
||||||
|
|
||||||
|
val future = RequestFuture.newFuture<ScheduleGetCacheStatusResponse>()
|
||||||
|
|
||||||
|
logger.info("Getting cache status...")
|
||||||
|
ScheduleGetCacheStatusRequest(context, future, future).send()
|
||||||
|
|
||||||
|
try {
|
||||||
|
val response = future.get()
|
||||||
|
|
||||||
|
logger.info("Cache status received successfully!")
|
||||||
|
|
||||||
|
if (!response.cacheUpdateRequired) {
|
||||||
|
logger.info("Cache update was not required!")
|
||||||
|
runBlocking { repository.setHash(response.cacheHash) }
|
||||||
|
} else {
|
||||||
|
logger.info("Cache update was required!")
|
||||||
|
val updateResult = runBlocking { updateMainPage() }
|
||||||
|
|
||||||
|
when (updateResult) {
|
||||||
|
is MyResult.Success -> {
|
||||||
|
logger.info("Cache update was successful!")
|
||||||
|
runBlocking { repository.setHash(updateResult.data.cacheHash) }
|
||||||
|
}
|
||||||
|
|
||||||
|
is MyResult.Failure -> {
|
||||||
|
logger.warning("Failed to update cache!")
|
||||||
|
super.getErrorListener()
|
||||||
|
?.onErrorResponse(updateResult.exception.cause as VolleyError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
logger.warning("Failed to get cache status!")
|
||||||
|
super.getErrorListener()?.onErrorResponse(exception.cause as VolleyError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val cachedResponse = runBlocking { repository.get(url) }
|
||||||
|
if (cachedResponse != null) {
|
||||||
|
logger.info("Found cached response!")
|
||||||
|
listener.onResponse(cachedResponse.data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Cached response doesn't exists!")
|
||||||
|
super.send()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,14 +4,14 @@ import android.content.Context
|
|||||||
import com.android.volley.Response
|
import com.android.volley.Response
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import ru.n08i40k.polytechnic.next.network.data.AuthorizedRequest
|
import ru.n08i40k.polytechnic.next.network.data.CachedRequest
|
||||||
|
|
||||||
class ScheduleGetRequest(
|
class ScheduleGetRequest(
|
||||||
private val data: ScheduleGetRequestData,
|
private val data: ScheduleGetRequestData,
|
||||||
context: Context,
|
context: Context,
|
||||||
listener: Response.Listener<ScheduleGetResponse>,
|
listener: Response.Listener<ScheduleGetResponse>,
|
||||||
errorListener: Response.ErrorListener? = null
|
errorListener: Response.ErrorListener? = null
|
||||||
) : AuthorizedRequest(
|
) : CachedRequest(
|
||||||
context, Method.POST, "schedule/get-group", Response.Listener<String> { response ->
|
context, Method.POST, "schedule/get-group", Response.Listener<String> { response ->
|
||||||
listener.onResponse(Json.decodeFromString<ScheduleGetResponse>(response))
|
listener.onResponse(Json.decodeFromString<ScheduleGetResponse>(response))
|
||||||
}, errorListener
|
}, errorListener
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package ru.n08i40k.polytechnic.next.network.data.schedule
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.android.volley.Response
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import ru.n08i40k.polytechnic.next.network.data.AuthorizedRequest
|
||||||
|
|
||||||
|
class ScheduleGetCacheStatusRequest(
|
||||||
|
context: Context,
|
||||||
|
listener: Response.Listener<ScheduleGetCacheStatusResponse>,
|
||||||
|
errorListener: Response.ErrorListener? = null
|
||||||
|
) : AuthorizedRequest(
|
||||||
|
context, Method.GET, "schedule/cache-status", Response.Listener<String> { response ->
|
||||||
|
listener.onResponse(Json.decodeFromString<ScheduleGetCacheStatusResponse>(response))
|
||||||
|
}, errorListener
|
||||||
|
)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package ru.n08i40k.polytechnic.next.network.data.schedule
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ScheduleGetCacheStatusResponse(
|
||||||
|
val cacheUpdateRequired: Boolean,
|
||||||
|
val cacheHash: String,
|
||||||
|
)
|
||||||
@@ -3,13 +3,13 @@ package ru.n08i40k.polytechnic.next.network.data.schedule
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.android.volley.Response
|
import com.android.volley.Response
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import ru.n08i40k.polytechnic.next.network.data.AuthorizedRequest
|
import ru.n08i40k.polytechnic.next.network.data.CachedRequest
|
||||||
|
|
||||||
class ScheduleGetGroupNamesRequest(
|
class ScheduleGetGroupNamesRequest(
|
||||||
context: Context,
|
context: Context,
|
||||||
listener: Response.Listener<ScheduleGetGroupNamesResponseData>,
|
listener: Response.Listener<ScheduleGetGroupNamesResponseData>,
|
||||||
errorListener: Response.ErrorListener? = null
|
errorListener: Response.ErrorListener? = null
|
||||||
) : AuthorizedRequest(
|
) : CachedRequest(
|
||||||
context, Method.GET, "schedule/get-group-names", Response.Listener<String> { response ->
|
context, Method.GET, "schedule/get-group-names", Response.Listener<String> { response ->
|
||||||
listener.onResponse(Json.decodeFromString<ScheduleGetGroupNamesResponseData>(response))
|
listener.onResponse(Json.decodeFromString<ScheduleGetGroupNamesResponseData>(response))
|
||||||
}, errorListener
|
}, errorListener
|
||||||
|
|||||||
@@ -7,7 +7,5 @@ import ru.n08i40k.polytechnic.next.model.Group
|
|||||||
data class ScheduleGetResponse(
|
data class ScheduleGetResponse(
|
||||||
val updatedAt: String,
|
val updatedAt: String,
|
||||||
val group: Group,
|
val group: Group,
|
||||||
val etag: String,
|
|
||||||
val lastChangedDays: ArrayList<Int>,
|
val lastChangedDays: ArrayList<Int>,
|
||||||
val updateRequired: Boolean
|
|
||||||
)
|
)
|
||||||
@@ -9,11 +9,11 @@ import ru.n08i40k.polytechnic.next.network.data.AuthorizedRequest
|
|||||||
class ScheduleUpdateRequest(
|
class ScheduleUpdateRequest(
|
||||||
private val data: ScheduleUpdateRequestData,
|
private val data: ScheduleUpdateRequestData,
|
||||||
context: Context,
|
context: Context,
|
||||||
listener: Response.Listener<Nothing>,
|
listener: Response.Listener<ScheduleGetCacheStatusResponse>,
|
||||||
errorListener: Response.ErrorListener? = null
|
errorListener: Response.ErrorListener? = null
|
||||||
) : AuthorizedRequest(
|
) : AuthorizedRequest(
|
||||||
context, Method.POST, "schedule/update-site-main-page", Response.Listener<String> {
|
context, Method.POST, "schedule/update-site-main-page", Response.Listener<String> {
|
||||||
listener.onResponse(null)
|
listener.onResponse(Json.decodeFromString<ScheduleGetCacheStatusResponse>(it))
|
||||||
}, errorListener
|
}, errorListener
|
||||||
) {
|
) {
|
||||||
override fun getBody(): ByteArray {
|
override fun getBody(): ByteArray {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import androidx.compose.material3.TextButton
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
@@ -21,7 +22,7 @@ import ru.n08i40k.polytechnic.next.ui.model.ProfileViewModel
|
|||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
fun ProfileScreen(
|
fun ProfileScreen(
|
||||||
profileViewModel: ProfileViewModel = ProfileViewModel(MockAppContainer().profileRepository) {},
|
profileViewModel: ProfileViewModel = ProfileViewModel(MockAppContainer(LocalContext.current).profileRepository) {},
|
||||||
onRefreshProfile: () -> Unit = {}
|
onRefreshProfile: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val uiState by profileViewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by profileViewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.compose.material3.TextButton
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
@@ -19,7 +20,7 @@ import ru.n08i40k.polytechnic.next.ui.model.ScheduleViewModel
|
|||||||
@Preview(showBackground = true, showSystemUi = true)
|
@Preview(showBackground = true, showSystemUi = true)
|
||||||
@Composable
|
@Composable
|
||||||
fun ScheduleScreen(
|
fun ScheduleScreen(
|
||||||
scheduleViewModel: ScheduleViewModel = ScheduleViewModel(MockAppContainer()),
|
scheduleViewModel: ScheduleViewModel = ScheduleViewModel(MockAppContainer(LocalContext.current)),
|
||||||
onRefreshSchedule: () -> Unit = {}
|
onRefreshSchedule: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val uiState by scheduleViewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by scheduleViewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
|||||||
@@ -3,8 +3,14 @@ syntax = "proto3";
|
|||||||
option java_package = "ru.n08i40k.polytechnic.next";
|
option java_package = "ru.n08i40k.polytechnic.next";
|
||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
message CachedResponse {
|
||||||
|
string hash = 1;
|
||||||
|
string data = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message Settings {
|
message Settings {
|
||||||
string user_id = 1;
|
string user_id = 1;
|
||||||
string access_token = 2;
|
string access_token = 2;
|
||||||
string group = 3;
|
string group = 3;
|
||||||
|
map<string, CachedResponse> cache_storage = 4;
|
||||||
}
|
}
|
||||||
@@ -2,15 +2,15 @@
|
|||||||
accompanistSwiperefresh = "0.36.0"
|
accompanistSwiperefresh = "0.36.0"
|
||||||
agp = "8.6.1"
|
agp = "8.6.1"
|
||||||
firebaseBom = "33.3.0"
|
firebaseBom = "33.3.0"
|
||||||
hiltAndroid = "2.51.1"
|
hiltAndroid = "2.52"
|
||||||
hiltAndroidCompiler = "2.51.1"
|
hiltAndroidCompiler = "2.52"
|
||||||
hiltNavigationCompose = "1.2.0"
|
hiltNavigationCompose = "1.2.0"
|
||||||
kotlin = "2.0.10"
|
kotlin = "2.0.10"
|
||||||
coreKtx = "1.13.1"
|
coreKtx = "1.13.1"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.2.1"
|
junitVersion = "1.2.1"
|
||||||
espressoCore = "3.6.1"
|
espressoCore = "3.6.1"
|
||||||
kotlinxSerializationJson = "1.7.2"
|
kotlinxSerializationJson = "1.7.3"
|
||||||
lifecycleRuntimeKtx = "2.8.6"
|
lifecycleRuntimeKtx = "2.8.6"
|
||||||
activityCompose = "1.9.2"
|
activityCompose = "1.9.2"
|
||||||
composeBom = "2024.09.02"
|
composeBom = "2024.09.02"
|
||||||
|
|||||||
Reference in New Issue
Block a user