Верии ниже этой больше не совместимы (т.е не работают).

Уменьшение размера приложения.
- Существенное)

Адекватное кеширование.
- Улучшено кеширование на стороне сервера (до этого сервер не выставлял флаг отвечающий за потребность в обновлении).
- Добавлено кеширование ответов от сервера в хранилище приложения.
This commit is contained in:
2024-09-26 04:12:19 +04:00
parent f7596749e3
commit c651f4ba01
21 changed files with 331 additions and 122 deletions

View File

@@ -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>

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View 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)
}

View 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) {}
}

View 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()
}
}
}
}
}

View File

@@ -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)
} }

View File

@@ -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)
} }

View File

@@ -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

View File

@@ -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()
}
}

View File

@@ -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

View File

@@ -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
)

View File

@@ -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,
)

View File

@@ -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

View File

@@ -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
) )

View File

@@ -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 {

View File

@@ -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()

View File

@@ -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()

View File

@@ -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;
} }

View File

@@ -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"