mirror of
https://github.com/n08i40k/polytechnic-android.git
synced 2025-12-06 09:47:48 +03:00
1.3.1
Дополнительная информация о последних обновлениях.
This commit is contained in:
9
.idea/appInsightsSettings.xml
generated
9
.idea/appInsightsSettings.xml
generated
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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()
|
||||
@@ -89,5 +112,4 @@ class LocalNetworkCacheRepository
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -6,4 +6,6 @@ import kotlinx.serialization.Serializable
|
||||
data class ScheduleGetCacheStatusResponse(
|
||||
val cacheUpdateRequired: Boolean,
|
||||
val cacheHash: String,
|
||||
val lastCacheUpdate: Long,
|
||||
val lastScheduleUpdate: Long,
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user