Дополнительная информация о последних обновлениях.
This commit is contained in:
2024-09-28 01:45:02 +04:00
parent c651f4ba01
commit f2724d275b
14 changed files with 328 additions and 17 deletions

View File

@@ -17,6 +17,15 @@
</option>
<option name="signal" value="SIGNAL_UNSPECIFIED" />
<option name="timeIntervalDays" value="THIRTY_DAYS" />
<option name="versions">
<list>
<VersionSetting>
<option name="buildVersion" value="3" />
<option name="displayName" value="1.2 (3)" />
<option name="displayVersion" value="1.2" />
</VersionSetting>
</list>
</option>
<option name="visibilityType" value="ALL" />
</InsightsFilterSettings>
</value>

View File

@@ -32,8 +32,8 @@ android {
applicationId = "ru.n08i40k.polytechnic.next"
minSdk = 26
targetSdk = 35
versionCode = 6
versionName = "1.3.0"
versionCode = 7
versionName = "1.3.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {

View File

@@ -1,6 +1,7 @@
package ru.n08i40k.polytechnic.next.data.cache
import ru.n08i40k.polytechnic.next.CachedResponse
import ru.n08i40k.polytechnic.next.UpdateDates
interface NetworkCacheRepository {
suspend fun put(url: String, data: String)
@@ -12,4 +13,8 @@ interface NetworkCacheRepository {
suspend fun isHashPresent(): Boolean
suspend fun setHash(hash: String)
suspend fun getUpdateDates(): UpdateDates
suspend fun setUpdateDates(cache: Long, schedule: Long)
}

View File

@@ -1,6 +1,7 @@
package ru.n08i40k.polytechnic.next.data.cache.impl
import ru.n08i40k.polytechnic.next.CachedResponse
import ru.n08i40k.polytechnic.next.UpdateDates
import ru.n08i40k.polytechnic.next.data.cache.NetworkCacheRepository
class FakeNetworkCacheRepository : NetworkCacheRepository {
@@ -17,4 +18,10 @@ class FakeNetworkCacheRepository : NetworkCacheRepository {
}
override suspend fun setHash(hash: String) {}
override suspend fun getUpdateDates(): UpdateDates {
return UpdateDates.newBuilder().build()
}
override suspend fun setUpdateDates(cache: Long, schedule: Long) {}
}

View File

@@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import ru.n08i40k.polytechnic.next.CachedResponse
import ru.n08i40k.polytechnic.next.UpdateDates
import ru.n08i40k.polytechnic.next.data.cache.NetworkCacheRepository
import ru.n08i40k.polytechnic.next.settings.settingsDataStore
import javax.inject.Inject
@@ -14,6 +15,7 @@ import javax.inject.Inject
class LocalNetworkCacheRepository
@Inject constructor(private val applicationContext: Context) : NetworkCacheRepository {
private val cacheMap: MutableMap<String, CachedResponse> = mutableMapOf()
private var updateDates: UpdateDates = UpdateDates.newBuilder().build()
private var hash: String? = null
init {
@@ -75,12 +77,33 @@ class LocalNetworkCacheRepository
this.cacheMap
.mapNotNull { if (it.value.hash != this.hash) it.key else null }
.forEach { this.cacheMap.remove(it) }
save()
}
}
override suspend fun getUpdateDates(): UpdateDates {
return this.updateDates
}
override suspend fun setUpdateDates(cache: Long, schedule: Long) {
updateDates = UpdateDates
.newBuilder()
.setCache(cache)
.setSchedule(schedule).build()
withContext(Dispatchers.IO) {
applicationContext.settingsDataStore.updateData {
it
.toBuilder()
.setUpdateDates(updateDates)
.build()
}
}
save()
}
private suspend fun save() {
withContext(Dispatchers.IO) {
runBlocking {
applicationContext.settingsDataStore.updateData {
it
.toBuilder()
@@ -90,4 +113,3 @@ class LocalNetworkCacheRepository
}
}
}
}

View File

@@ -77,6 +77,7 @@ open class RequestBase(
override fun getHeaders(): MutableMap<String, String> {
val headers = mutableMapOf<String, String>()
headers["Content-Type"] = "application/json; charset=utf-8"
headers["version"] = "1"
return headers
}

View File

@@ -97,7 +97,10 @@ open class CachedRequest(
if (!response.cacheUpdateRequired) {
logger.info("Cache update was not required!")
runBlocking { repository.setHash(response.cacheHash) }
runBlocking {
repository.setUpdateDates(response.lastCacheUpdate, response.lastScheduleUpdate)
repository.setHash(response.cacheHash)
}
} else {
logger.info("Cache update was required!")
val updateResult = runBlocking { updateMainPage() }
@@ -105,7 +108,13 @@ open class CachedRequest(
when (updateResult) {
is MyResult.Success -> {
logger.info("Cache update was successful!")
runBlocking { repository.setHash(updateResult.data.cacheHash) }
runBlocking {
repository.setUpdateDates(
updateResult.data.lastCacheUpdate,
updateResult.data.lastScheduleUpdate
)
repository.setHash(updateResult.data.cacheHash)
}
}
is MyResult.Failure -> {

View File

@@ -6,4 +6,6 @@ import kotlinx.serialization.Serializable
data class ScheduleGetCacheStatusResponse(
val cacheUpdateRequired: Boolean,
val cacheHash: String,
val lastCacheUpdate: Long,
val lastScheduleUpdate: Long,
)

View File

@@ -0,0 +1,134 @@
package ru.n08i40k.polytechnic.next.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.rememberTransition
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@Composable
fun ExpandableCard(
modifier: Modifier = Modifier,
expanded: Boolean,
onExpandedChange: () -> Unit,
title: String,
content: @Composable () -> Unit
) {
val transitionState = remember {
MutableTransitionState(expanded).apply {
targetState = !expanded
}
}
val transition = rememberTransition(transitionState)
Card(modifier = modifier.clickable {
onExpandedChange()
transitionState.targetState = expanded
}) {
Column {
ExpandableCardHeader(title, transition)
ExpandableCardContent(visible = expanded, content = content)
}
}
}
@Composable
private fun ExpandableCardContent(
visible: Boolean = true,
content: @Composable () -> Unit
) {
val enterTransition = remember {
expandVertically(
expandFrom = Alignment.Top,
animationSpec = tween(durationMillis = 250)
) + fadeIn(animationSpec = tween(durationMillis = 250, delayMillis = 250))
}
val exitTransition = remember {
fadeOut(
animationSpec = tween(durationMillis = 250)
) + shrinkVertically(
shrinkTowards = Alignment.Top,
animationSpec = tween(durationMillis = 250, delayMillis = 250)
)
}
AnimatedVisibility(
visible = visible,
enter = enterTransition,
exit = exitTransition
) {
content()
}
}
@Composable
private fun ExpandableCardTitle(text: String) {
Text(
text = text,
modifier = Modifier
.fillMaxWidth()
.padding(0.dp, 10.dp),
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center
)
}
@Composable
private fun ExpandableCardArrow(
transition: Transition<Boolean>
) {
val rotationDegree by transition.animateFloat(
{ tween(durationMillis = 250) },
label = "Arrow Rotation"
) {
if (it) 360F else 180F
}
Icon(
modifier = Modifier.rotate(rotationDegree),
imageVector = Icons.Filled.ArrowDropDown,
contentDescription = "Expandable Arrow"
)
}
@Composable
private fun ExpandableCardHeader(
title: String = "TODO",
transition: Transition<Boolean>
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp, 0.dp),
contentAlignment = Alignment.CenterEnd,
) {
ExpandableCardArrow(transition)
ExpandableCardTitle(title)
}
}

View File

@@ -1,6 +1,12 @@
package ru.n08i40k.polytechnic.next.ui.main.schedule
import androidx.activity.ComponentActivity
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@@ -10,7 +16,10 @@ 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.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ru.n08i40k.polytechnic.next.MainViewModel
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.data.MockAppContainer
import ru.n08i40k.polytechnic.next.ui.LoadingContent
@@ -26,17 +35,27 @@ fun ScheduleScreen(
val uiState by scheduleViewModel.uiState.collectAsStateWithLifecycle()
LoadingContent(
empty = when (uiState) {
is ScheduleUiState.NoSchedule -> uiState.isLoading
is ScheduleUiState.HasSchedule -> false
},
empty = uiState.isLoading,
loading = uiState.isLoading,
onRefresh = onRefreshSchedule
onRefresh = { onRefreshSchedule() },
verticalArrangement = Arrangement.Top
) {
when (uiState) {
is ScheduleUiState.HasSchedule -> {
Box {
val networkCacheRepository =
hiltViewModel<MainViewModel>(LocalContext.current as ComponentActivity)
.appContainer
.networkCacheRepository
UpdateInfo(networkCacheRepository)
Column {
Spacer(modifier = Modifier.height(200.dp))
DayPager((uiState as ScheduleUiState.HasSchedule).group)
}
}
}
is ScheduleUiState.NoSchedule -> {
if (!uiState.isLoading) {

View File

@@ -0,0 +1,89 @@
package ru.n08i40k.polytechnic.next.ui.main.schedule
import androidx.activity.ComponentActivity
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import kotlinx.coroutines.runBlocking
import ru.n08i40k.polytechnic.next.MainViewModel
import ru.n08i40k.polytechnic.next.R
import ru.n08i40k.polytechnic.next.data.cache.NetworkCacheRepository
import ru.n08i40k.polytechnic.next.data.cache.impl.FakeNetworkCacheRepository
import ru.n08i40k.polytechnic.next.ui.ExpandableCard
import ru.n08i40k.polytechnic.next.ui.model.ScheduleViewModel
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
fun Date.toString(format: String, locale: Locale = Locale.getDefault()): String {
val formatter = SimpleDateFormat(format, locale)
return formatter.format(this)
}
fun getCurrentDateTime(): Date {
return Calendar.getInstance().time
}
val expanded = mutableStateOf(false)
@Preview(showBackground = true)
@Composable
fun UpdateInfo(networkCacheRepository: NetworkCacheRepository = FakeNetworkCacheRepository()) {
var expanded by remember { expanded }
val format = "hh:mm:ss dd.MM.yyyy"
val updateDates = remember { runBlocking { networkCacheRepository.getUpdateDates() } }
val currentDate = remember { getCurrentDateTime().toString(format) }
val cacheUpdateDate = remember { Date(updateDates.cache).toString(format) }
val scheduleUpdateDate = remember { Date(updateDates.schedule).toString(format) }
ExpandableCard(
expanded = expanded,
onExpandedChange = { expanded = !expanded },
title = stringResource(R.string.update_info_header)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
) {
Row(horizontalArrangement = Arrangement.Center) {
Text(text = stringResource(R.string.last_local_update) + " - ")
Text(text = currentDate, fontWeight = FontWeight.Bold)
}
Row(horizontalArrangement = Arrangement.Center) {
Text(text = stringResource(R.string.last_server_cache_update) + " - ")
Text(text = cacheUpdateDate, fontWeight = FontWeight.Bold)
}
Row(horizontalArrangement = Arrangement.Center) {
Text(text = stringResource(R.string.last_server_schedule_update) + " - ")
Text(text = scheduleUpdateDate, fontWeight = FontWeight.Bold)
}
}
}
}

View File

@@ -8,9 +8,15 @@ message CachedResponse {
string data = 2;
}
message UpdateDates {
int64 cache = 1;
int64 schedule = 2;
}
message Settings {
string user_id = 1;
string access_token = 2;
string group = 3;
map<string, CachedResponse> cache_storage = 4;
UpdateDates update_dates = 5;
}

View File

@@ -32,4 +32,8 @@
<string name="change_group">Сменить группу</string>
<string name="sign_out">Выйти с аккаунта</string>
<string name="cabinets">Кабинеты</string>
<string name="last_local_update">Последнее локальное обновление</string>
<string name="last_server_cache_update">Последнее обновление кеша</string>
<string name="last_server_schedule_update">Последнее обновление расписания</string>
<string name="update_info_header">Дополнительная информация</string>
</resources>

View File

@@ -32,4 +32,8 @@
<string name="change_group">Change group</string>
<string name="sign_out">Sign out</string>
<string name="cabinets">Cabinets</string>
<string name="last_local_update">Last local update</string>
<string name="last_server_cache_update">Last server cache update</string>
<string name="last_server_schedule_update">Last server schedule update</string>
<string name="update_info_header">Additional information</string>
</resources>